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_idare 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
200withstatus: "cancelled"rather than an error, so a retry is safe. This is also why cancel needs noIdempotency-Key. completedcannot be cancelled —409 not_cancellable, withdetails.statusnaming the state. The trip already happened; voiding it would cancel a job the crew was already paid for.refundedis alwaysfalsefor 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.