# Sell as a reseller

A reseller sells the cars of many rentals through one key. Mellow gives you a test key and a live key. Your key reaches each rental that connected you, on the price list and car types that rental picked. A rental that disconnects you drops out of every answer at once.

## The steps

| Step | What you do                 | Call                          | Needed   |
| ---- | --------------------------- | ----------------------------- | -------- |
| 1    | Read the catalogue          | `GET /car-types` and two more | Yes      |
| 2    | Search for priced offers    | `GET /availability`           | Yes      |
| 3    | Hold a car while you charge | `POST /holds`                 | Optional |
| 4    | Book                        | `POST /bookings`              | Yes      |
| 5    | Keep up with changes        | `GET /bookings`               | Yes      |

Use only the paths on this page. None of them has a rental slug, and each row of an answer names its rental. The paths under `/rentals/{slug}/…`, `GET /rentals` and `POST /quotes` are for a rental's own website. Your key gets `403` with `use_reseller_routes` there.

## Price and prepaid

- You sell at the rental's price, from the price list the rental gave you, in that price list's currency. Mellow converts nothing. You add nothing on top.
- The customer pays you some of the price or all of it, and the rest at the rental. Send what the customer paid you as `prepaidMinor`: any amount from 0 to the booking's total.
- The rental's contract with you says who takes the money. Under a contract where both take money, by prepaid, each booking's line on the reseller statement is the prepaid amount less your reseller commission. Above 0, you owe the rental. Below 0, the rental owes you. A cancelled booking is a line of 0.

## Step 1: Read the catalogue

```
GET /car-types?updatedSince=2027-05-01T00:00:00Z
GET /products
GET /places
```

- `/car-types` gives each rental's car types, with names, details and images as full addresses. With `updatedSince`, you get only the car types changed after that time. Keep the time of your last call and send it the next time. A car type that leaves sale, or that the rental stops selling to you, is not a change: it is only missing from a full read. Read the whole list without `updatedSince` once a day, and drop the car types it no longer has.
- `/products` gives each rental's extras and Levels, each with the car types it goes with. A Level has `level` 1 (Basic), 2 (Premium) or 3 (Platinum). An extra has `level` null. Prices are not here, because a price depends on the span. Each offer carries them.
- `/places` gives your own places, such as `KEF` for KEF airport, and the rentals that serve each one. Mellow keeps your list of places. Each rental maps them to its own locations.

Each place carries the location of each rental that serves it. Show it to the customer before the pickup:

```json
{
  "code": "KEF",
  "name": "KEF airport",
  "rentals": ["generic-cars"],
  "locations": [
    {
      "rental": "generic-cars",
      "id": "loc_1",
      "name": "Keflavík Airport",
      "address": "Arrivals hall, 235 Keflavík",
      "pickupInstructions": "The shuttle driver meets you in the arrival hall.",
      "hours": [{ "weekday": 1, "opens": "08:00", "closes": "20:00" }],
      "closures": [{ "from": "2027-12-24", "to": "2027-12-26", "reason": "Christmas" }]
    }
  ]
}
```

- `hours` are local time at the rental. `weekday` 0 is Sunday and 6 is Saturday. Two spans on one weekday are a lunch close.
- `closures` are the days the location is shut, whatever its hours say, from today on.
- Send `locale`, such as `?locale=de`, for the texts in that language. A rental that does not use that language answers in its own.

## Step 2: Search

```
GET /availability?pickupAt=2027-06-01T10:00&returnAt=2027-06-04T10:00&pickupPlace=KEF&returnPlace=KEF&driverAge=30
```

Times are local time at the rental. `driverAge` is optional: whole years, from 16 to 120. The answer has one block for each rental that serves both places. Each block has an offer for each car type that is free for the whole span:

```json
{
  "data": [
    {
      "rental": "generic-cars",
      "rentalName": "Generic Cars",
      "pickupLocation": { "id": "loc_1", "name": "KEF airport" },
      "returnLocation": { "id": "loc_1", "name": "KEF airport" },
      "offers": [
        {
          "carTypeId": "c_duster",
          "carType": "Dacia Duster",
          "free": 3,
          "currency": "ISK",
          "days": 3,
          "totalMinor": 54000,
          "payOnArrivalMinor": 0,
          "lines": [],
          "includedLevelId": "p_basic",
          "minDriverAge": 20,
          "products": [
            { "productId": "p_premium", "priceMinor": 9000 },
            { "productId": "p_child_seat", "priceMinor": 4500 }
          ],
          "problems": [],
          "fuel": { "policy": "full_to_full", "pricePerLitreMinor": 350, "refuelFeeMinor": 2500 }
        }
      ]
    }
  ]
}
```

- `totalMinor` is the price with the included Level and no extras.
- Each product's `priceMinor` is what it adds to that total for this span. For a Level, it is what the Level adds over the included one.
- A booking of the same order costs exactly the offer's total plus the products you add.
- `fuel` gives the car type's fuel terms at the return. With `full_to_full`, the car goes out full and comes back full. With `same_to_same`, it comes back with as much fuel as it went out with. The rental charges `pricePerLitreMinor` for each litre short, and `refuelFeeMinor` once when any fuel is short. The total holds none of it. `fuel` is null when the rental charges nothing for fuel on the car type.
- An offer with `problems` cannot be booked as it stands. `driver_too_young` means the driver is under the car type's `minDriverAge`. Others, such as `too_short`, come from the rental's rules.
- Nothing is cached. Each search counts the cars and prices them at that moment.

## Step 3: Hold a car (optional)

A hold is optional. You can skip it and book straight from the search (step 4). Use a hold when you charge the customer before you book: it keeps the car and the price while the payment runs.

```
POST /holds
```

```json
{
  "rental": "generic-cars",
  "carTypeId": "c_duster",
  "pickupAt": "2027-06-01T10:00",
  "returnAt": "2027-06-04T10:00",
  "pickupPlace": "KEF",
  "returnPlace": "KEF",
  "levelId": "p_premium",
  "extras": [{ "productId": "p_child_seat", "quantity": 1 }],
  "driverAge": 30
}
```

- The hold keeps one car for 15 minutes at a fixed total.
- The answer is `201` with `holdId`, `expiresAt`, `totalMinor`, `currency` and the lines. Charge the customer that total, or the part of it the customer pays you.
- The hold sends no email, and the rental does not see it.
- When the customer leaves, give the car back at once with `DELETE /holds/{holdId}`.

| Without a hold                                    | With a hold                                       |
| ------------------------------------------------- | ------------------------------------------------- |
| One call less                                     | The car and the price stay fixed for 15 minutes   |
| Mellow counts the cars and prices them at booking | The booking takes the hold's car and total        |
| The car type can be full by then (`none_free`)    | After 15 minutes, the same risk as without a hold |

## Step 4: Book

```
POST /bookings
Idempotency-Key: 6b1e-…
```

With a hold, send `holdId`. Without a hold, send the order fields of step 3 (`rental`, `carTypeId`, `pickupAt`, `returnAt`, `pickupPlace`, `returnPlace`, and optional `levelId`, `extras`, `driverAge`) in its place. Never send both.

```json
{
  "holdId": "h_…",
  "customer": {
    "firstName": "Nora",
    "lastName": "Berg",
    "email": "nora.83212@user.example.com",
    "phone": "+49 30 1234567",
    "address": "Hauptstraße 1",
    "city": "Berlin",
    "country": "DE"
  },
  "prepaidMinor": 9450,
  "reference": "NB-10442",
  "flightNumber": "FI520",
  "note": "Arrives late."
}
```

- The answer is `201` with the booking, as reserved.
- The customer is the main driver. First name, last name and email are required. A relay address that forwards to the customer is fine.
- `reference` is your own booking number. It is unique among your bookings.
- Send an `Idempotency-Key` header. A retry with the same key and body gives the same booking, also after the first booking used the hold. The same key with another body answers `409`.

From a hold:

- The booking takes the hold's order and total. Send no order fields beside `holdId`.
- After `expiresAt`, the hold has no car of its own. The booking then needs a car that is free at that moment and the same total, or it answers `409` with `hold_expired`.
- A day after `expiresAt`, the hold is gone and answers `404` with `hold_not_found`.
- You keep at most 50 live holds at one rental at once. One more answers `429` with `too_many_holds`.

Without a hold:

- Mellow counts the cars and prices the order at that moment. A full car type answers `409` with `none_free`.

### Messages to the customer

The rental decides whether it writes to the customer's address. It picks one of three choices on your connection:

- **Their own address**: the rental's message rules send the confirmation, the online check-in link, the manage-booking link and the reminders.
- **A relay address**: only the message rules the rental set to relay send. A rule does not relay until the rental turns that on.
- **No contact**: the rental sends nothing.

Ask the rental which choice it made before you promise your customer these messages.

## Step 5: Keep up with changes

```
GET    /bookings?updatedSince=2027-05-30T12:00:00Z&afterId=bk_123
GET    /bookings?reference=NB-10442
GET    /bookings/{id}
PATCH  /bookings/{id}
POST   /bookings/{id}/cancel
```

### Poll

- `GET /bookings` gives your bookings on every rental, oldest change first, then by id, up to 200 at a time.
- Poll with `updatedSince` set to the last `updatedAt` you saw and `afterId` set to the `id` of that booking. A change the rental made, such as a cancel, new dates or a new car type, shows up there. Mellow sends a reseller no webhooks.
- Send both. Several bookings can share one `updatedAt`. With `updatedSince` alone, a booking with the same `updatedAt` on the next page is skipped.
- Each booking has its status, the span, the locations, the car type, the lines, the total, your commission, the prepaid amount, the balance the customer pays at the rental, and `updatedAt`.
- `pickupPlace` and `returnPlace` give your own code for each location, from the rental's map of your places, such as `KEF`. A location the rental maps to none of your places gives `null`.

### Change

`PATCH` changes only these fields, and only while the booking is reserved:

- `customer`: any of the customer fields above. The change is this booking's own: the customer's other bookings keep the details they were booked with. A new `email` puts the booking on the customer with that email and the same name, or on a new customer.
- `flightNumber` and `note`.
- `prepaidMinor`: the new total the customer paid you, up or down. Once the rental took a payment itself, such as at online check-in, the prepaid amount is fixed.

New dates, a new car type, other places, extras or a Level answer `422` with `not_allowed`. For those, cancel and book again, or ask the rental.

### Cancel

`POST /bookings/{id}/cancel` cancels a reserved booking. Mellow refunds nothing: you refund your customer what you charged.

After pickup, the API changes and cancels nothing, for you or for the rental's own website. A later change, such as a longer rental, goes through the rental.

## A paused rental

A rental can pause you on its page for you. You still read your bookings there. But its offers leave the search, and a hold, a booking, a change and a cancel at that rental answer `403` with `paused`. When the rental turns you on again, it sells as before.

A rental can also stop selling through resellers as a whole. It then leaves every list, as a rental that never connected you. A call that names one of your bookings or holds there answers `403` with `feature_off`.

## Limits

- Every key may make 600 calls a minute, a reseller's key as every other.
- Past that, a call answers `429` with the code `rate_limited` and does nothing. Its `Retry-After` header gives the seconds to wait, 60 at most.
- The count runs in each Cloudflare location on its own, so a key may now and then get a few more calls through. Do not build on that. Ask Mellow when you need more.

## Errors

Each error is a problem object with a `code`, as under [Errors](/errors). These codes are the reseller's own:

| Code                    | Status | What it means                                                                    |
| ----------------------- | ------ | -------------------------------------------------------------------------------- |
| `use_reseller_routes`   | 403    | A reseller's key called a path that is not on this page.                         |
| `reseller_only`         | 403    | A key that is not a reseller's called a reseller path.                           |
| `feature_off`           | 403    | The booking or the hold is at a rental that does not sell through resellers now. |
| `place_not_served`      | 422    | The rental does not serve that place.                                            |
| `driver_too_young`      | 422    | The driver is under the car type's minimum age.                                  |
| `prepaid_out_of_range`  | 422    | The prepaid amount is below 0 or above the booking's total.                      |
| `not_allowed`           | 422    | A field that cannot be sent here, such as new dates, or an order beside a hold.  |
| `duplicate_reference`   | 409    | You already have a booking with that reference.                                  |
| `hold_expired`          | 409    | The hold ended, and its car is gone or its price moved. Refund and search again. |
| `hold_not_found`        | 404    | No such hold, or it was used or given back.                                      |
| `too_many_holds`        | 429    | You keep 50 holds at this rental now. Book from one or give one back first.      |
| `rental_payment_exists` | 409    | The rental took a payment, so the prepaid amount is fixed.                       |

The other codes are the same as on a rental's own paths:

- A full car type answers `409` with `none_free`, on a hold and on a booking.
- A booking that is no longer reserved answers `409` with `wrong_status` to a change or a cancel.
- A `403` or a `404` is not kept under the `Idempotency-Key`.

## Testing

- A test key starts `sk_test_`. It reaches only demo rentals: rentals that send no email, SMS or webhook and take no card payment. Book, change and cancel there as much as you need.
- A live key starts `sk_live_` and reaches only real rentals, so a test never books a real car.
- Keep your test key after you go live, to try each new version of your integration.
- Mellow can rotate either key. The old key then works for 7 more days.
