GuidesReading and cancelling bookings

Reading and cancelling bookings

Every booking comes back under the same shape from the same endpoints, whichever route created it. This is where you read it.

Reads need bookings:read. Cancel needs bookings:cancel.

The booking shape

{
  "reference": "DC-8F2K91",
  "product": "car_rental",
  "status": "pending",
  "external_ref": "acme-order-99213",
  "starts_at": "2026-11-02T10:00:00.000Z",
  "ends_at": "2026-11-06T10:00:00.000Z",
  "total": 1840,
  "currency": "MAD",
  "partner_commission": 184,
  "net_payable": 1656,
  "customer": {
    "first_name": "Amina",
    "last_name": "Berrada",
    "email": "amina@example.com"
  },
  "created_at": "2026-11-01T09:12:44.000Z"
}

product is car_rental. external_ref is whatever you sent at booking time — your own order id, echoed back.

net_payable is the number you are actually invoiced. It is total - partner_commission, and the commission is money carved out of our margin rather than added on top of yours. It is computed from two persisted figures, not re-derived from a rate, so it cannot drift from what you are billed.

A rental has no driver in its response — it has none until a crew wins the job.

Some rentals carry a fulfilment block:

{ "fulfilment": { "state": "reserved", "reference": "AV-77213" } }

state is none, pending, reserved, failed or cancelled. While it is pending, status reads pending_confirmation: we have the order and your payment, but the rental itself is not yet secured. Read fulfilment.state rather than status alone before telling a customer their car is held.

Read one

curl -sS "https://{your-base-url}/v1/bookings/DC-8F2K91" \
  -H "Authorization: Bearer $TOKEN"

Or by your own reference, which saves you storing ours:

curl -sS "https://{your-base-url}/v1/bookings/ext/acme-order-99213" \
  -H "Authorization: Bearer $TOKEN"

Both 404 on a booking that is not yours. See why ownership answers 404 — and remember that a 404 on a reference you are certain you created usually means you are reading it with a credential for a different account.

Branch on confirmed alone

This is the rule that keeps your integration working through changes you were not told about.

A 200 or 201 from a book call means the order is placed with DriveCars and your payment is recorded. It does not mean the booking is confirmed. Poll this endpoint for status, which is the single field that says where the order stands.

More than one not-yet-confirmed status exists, they carry no obligation you need to act on differently, and new ones may be added without notice. So write the check as “is it confirmed?” and treat everything else as one bucket. Code that enumerates pending, pending_confirmation and the rest will break the first time a new one appears; code that asks about confirmed will not.

Filterable statuses on the list endpoint are draft, pending, confirmed, completed, cancelled.

List

curl -sS -G "https://{your-base-url}/v1/bookings" \
  -H "Authorization: Bearer $TOKEN" \
  --data-urlencode "product=car-rentals" \
  --data-urlencode "status=confirmed" \
  --data-urlencode "limit=50"
{
  "data": [{ "reference": "DC-8F2K91", "...": "..." }],
  "has_more": true,
  "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0xMS0wMVQwOToxMjo0NC4wMDBaIiwiaWQiOjkxfQ"
}

Filters: product (car-rentals — note the hyphenated plural, which differs from the car_rental value in the response body), status, external_ref, created_after and created_before (both ISO-8601 datetimes). Paging is limit (1–100, default 25) plus cursor — see Pagination.

Cancel

curl -sS -X POST \
  "https://{your-base-url}/v1/bookings/DC-8F2K91/cancel" \
  -H "Authorization: Bearer $TOKEN"
{
  "ok": true,
  "status": "cancelled",
  "refunded": false,
  "cancelledLegs": ["DC-T4M8X2", "DC-T4M8X3"]
}

cancelledLegs is camelCase, unlike the rest of the response. It is stated explicitly so you can confirm a round trip really did cancel as one rather than assuming only the leg you named came off.

Some notes on the semantics:

  • A round trip cancels as one. Two legs sharing a return_group_id are one payment, so cancelling either reference cancels both. Cancelling only the named leg would strand its sibling confirmed — a crew slot never freed, and a customer who thinks the whole trip is off.
  • Cancelling an already-cancelled booking succeeds. It answers 200 with status: "cancelled" rather than an error, so a retry is safe. This is also why cancel needs no Idempotency-Key.
  • completed cannot be cancelled409 not_cancellable, with details.status naming the state. The trip already happened; voiding it would cancel a job the crew was already paid for.
  • refunded is always false for you, and that is correct. You are the merchant of record: your PSP took the customer’s money, so DriveCars is holding none of it to return. Refunding the customer is yours to do, on your side, and no call to this API will do it for you.

Rather than polling

POST /v1/webhooks registers an endpoint we POST booking-lifecycle events to, so you learn about a status change when it happens instead of asking every minute. Managing them needs the webhooks:manage scope — deliberately separate from bookings:read, so a leaked read-only credential cannot redirect your event stream.

GET /v1/events and GET /v1/events/{event_id} read the event log directly under bookings:read. See the API Reference for both surfaces.