Every /v1 error responds with the error envelope: { "error": { "code", "message",
"request_id", "doc_url", "details"? } }. Clients branch on error.code, never on the
HTTP status alone. Client retry policy: retry only 429, 5xx,
409 idempotency_in_progress and network errors, with the same Idempotency-Key
on a write. Wait at least Retry-After seconds when the response carries it; otherwise back
off exponentially with full jitter — before retry n (from 0), a random wait up to 0.5 s × 2n,
capped at 8 s. Overload answers 503, never 401: a 401 is always about the key.
malformed_request HTTP 400The request body is not valid JSON, or the content-type is wrong.
Retry: No — fix the request.
What to do: Send a JSON body with Content-Type: application/json; check the payload parses before you send it, and look at details[] for the byte that broke it.
unauthenticated HTTP 401Missing or invalid X-API-Key. Keys are environment-scoped: a key presented to the environment it was not issued for (sandbox vs production) is refused with this same response — if a known-good key is refused, verify the base URL targets the environment the key was issued for. It is never a load signal: a valid key is not answered 401 because the service is busy — that is `503 service_unavailable`.
Retry: No — check the key (two keys per partner are active during rotation).
What to do: Present the key in X-API-Key, on the host it was issued for (chk_test_ keys work on the sandbox host only, chk_live_ keys on production only); during a rotation either of the two live keys is accepted.
forbidden HTTP 403The key is valid but lacks the required scope (e.g. write:bookings). One operation has a second reason: `PUT /v1/partner-codes` is also refused, with `details[].issue` `partner_codes_not_enabled`, when the supplier has not switched on your recording your own codes — the scope is held, the supplier has not said yes.
Retry: No — request the scope, or ask the supplier to enable it.
What to do: Read the scope the operation publishes (the reference names it per operation) and ask the operator to add it to your key; a new key is issued with the scope, the old one keeps working until you retire it. If details[].issue is partner_codes_not_enabled the key is fine: ask the supplier to switch on your recording your own codes (contract §3.15).
supplier_not_enabled HTTP 403RESERVED — not returned by any /v1 endpoint today, because a credential reaches exactly one supplier and it is always its own. It would mean: the supplier exists and you can see it, but your agreement does not permit you to sell it.
Retry: No — the agreement has to change. (Never returned today.)
What to do: This code is reserved and never returned today; if you ever receive it, stop selling that supplier and ask the operator to enable it for your account, then re-read GET /v1/suppliers to confirm.
not_found HTTP 404No booking or resource exists with that reference.
Retry: No.
What to do: Check the reference you sent — a booking ref is the one this platform returned when the booking was created; a reference-data id comes from the catalogue endpoints. Nothing exists under that value for your key.
ambiguous_ref HTTP 409The external_ref in the path names a booking at more than one of the suppliers your API key reaches, because the value was reused across suppliers (§1.8). Returned only to a key that reaches more than one supplier; a key that reaches one never receives it.
Retry: No — address the booking by booking_id, or name the supplier with X-Supplier-Id.
What to do: Use ?ref_type=booking_id with the booking_id you stored, or send X-Supplier-Id naming the supplier you mean. Then stop reusing an external_ref across suppliers: mint one value per rental, unique across your account.
availability_changed HTTP 409Availability moved between quote/expectation and booking; nothing was booked.
Retry: No — re-search or re-quote.
What to do: Nothing was booked. Search again with GET /v1/availability, quote the selection you find, and book the new quote_id; tell the customer the earlier option is gone.
booking_conflict HTTP 409This external_ref was already used to create a booking with a different intent hash (§1.8).
Retry: No — use a new external_ref, or fetch the existing booking.
What to do: You already created a booking under this external_ref with different content. Fetch the existing booking with GET /v1/bookings/{ref} and use it, or send a new external_ref if this is genuinely a different rental.
credential_inactive HTTP 409The key you presented is no longer active, so it cannot rotate or retire anything.
Retry: No — ask the operator for a new credential.
What to do: The key you presented is retired or revoked. Use the successor key you were issued, or ask the operator for a new credential; an inactive key cannot rotate or retire anything.
credential_state_changed HTTP 409The key changed state while this request was running; nothing was rotated.
Retry: No — this is not one of the four retryable answers (§1.8). Re-read GET /v1/credentials, then decide again.
What to do: Read GET /v1/credentials to see the current state of both keys, then repeat the operation once against what you read; nothing was rotated by the request that got this answer.
idempotency_in_progress HTTP 409A request with the same Idempotency-Key is still executing — or the supplier is moving your relationship with it into your account (the move that lets one key reach all your suppliers), which holds every write to that relationship, under any key, until the move completes.
Retry: Yes — after Retry-After, and again with backoff while it persists.
What to do: Wait the Retry-After seconds, then retry with the SAME Idempotency-Key and body, backing off between retries as the retry policy says. A retry can meet the original still executing, so keep backing off until the code changes: a duplicate then receives the original request's result once the original finishes, never a second execution, and a held write runs once the move completes. The message names the first cause in both cases: branch on the code.
invalid_transition HTTP 409The booking state does not allow this operation (e.g. modifying a cancelled booking).
Retry: No — re-read the booking.
What to do: Re-read the booking with GET /v1/bookings/{ref} and act on its current status; a cancelled or completed booking cannot be modified, and a modification cannot be applied twice.
no_webhook_secret HTTP 409This account has no webhook secret to rotate; the operator sets the first one.
Retry: No — rotation is available once the operator has set the first secret.
What to do: Ask the operator to set your first webhook signing secret from the console; once it exists you rotate it yourself with POST /v1/webhooks/secret/rotate.
not_your_predecessor HTTP 409A key may only retire the key it replaced. The key named is not this credential's predecessor.
Retry: No — present the successor of the key you want to retire, or ask the operator to revoke it.
What to do: Present the key that replaced the one you want to retire — only a successor retires its own predecessor; read GET /v1/credentials to see which key succeeded which.
price_changed HTTP 409The computed price no longer equals expected_price; the response carries the new full breakdown.
Retry: No — re-consent the customer and resubmit with the new price.
What to do: Show the customer the new full breakdown the response carries, obtain their consent to the new total, and resubmit the same request with expected_price set to that total.
quote_consumed HTTP 409This quote_id was already consumed by a booking.
Retry: No — re-quote for a new attempt.
What to do: A quote is booked at most once. Request a fresh quote with POST /v1/quote for the new attempt and book that quote_id; the earlier booking is still yours under its ref.
quote_expired HTTP 409The quote_id is past its expires_at.
Retry: No — re-quote.
What to do: Request a fresh quote with POST /v1/quote; read quote_ttl_seconds on GET /v1/capabilities and book inside that window instead of assuming a lifetime.
revision_conflict HTTP 409expected_revision is stale — the booking changed since it was last read; the response carries the current booking.
Retry: No — re-read, re-decide.
What to do: Re-read the booking with GET /v1/bookings/{ref}, decide again against its current state, and resubmit with the revision you just read as expected_revision.
rotation_in_progress HTTP 409This account already has an open rotation overlap; two live keys are the most it may hold.
Retry: No — retire the outgoing key with POST /v1/credentials/{key_id}/revoke, then rotate again.
What to do: Finish the rotation you started: switch your traffic to the new key, then retire the outgoing one with POST /v1/credentials/{key_id}/revoke; only then can you rotate again.
rotation_required HTTP 409The key you presented was not issued by a rotation, so it has no predecessor to retire.
Retry: No — rotate first (POST /v1/credentials/rotate), then present the key that rotation issues.
What to do: Call POST /v1/credentials/rotate with the key you hold, switch to the key it returns, then retire the old one with that new key; a key without a predecessor has nothing to retire.
self_revocation_refused HTTP 409A key cannot revoke itself — doing so would leave the account with no live credential.
Retry: No — present the successor key and revoke this one with it.
What to do: Rotate first so a successor exists, present the successor key, and revoke this one with it; a key never revokes itself, so the account always keeps one live credential.
supplier_paused HTTP 409RESERVED — not returned by any /v1 endpoint today, because every supplier this platform serves is selling. It would mean: the supplier you named is not selling right now, so nothing may be quoted or booked against it.
Retry: No — search again instead. (Never returned today.)
What to do: This code is reserved and never returned today; if you ever receive it, search again with GET /v1/availability — the intent is that a paused supplier's offers will disappear from search, so the search result will be what tells you what is still sellable.
supplier_required HTTP 409The request needs exactly one supplier and nothing in it decides which: your API key reaches more than one supplier, no X-Supplier-Id was sent, and no id in the request belongs to exactly one of your suppliers (or a settlement id exists at more than one). Returned only to a key that reaches more than one supplier.
Retry: No — name the supplier.
What to do: Send X-Supplier-Id with the supplier_id of the supplier you mean — it is carried on every booking, quote and reference object that supplier returns — or address the request by an id that belongs to that supplier, such as a booking_id or a quote's vehicle_id and station location_id.
version_mismatch HTTP 409X-API-Version disagrees with the version in the path.
Retry: No — align header and path.
What to do: Remove the X-API-Version header, or set it to 1; the version is the /v1 path segment and the header may only agree with it.
webhooks_disabled HTTP 409Webhook delivery is not enabled for this account, so there is nothing to replay.
Retry: No — ask the operator to enable delivery.
What to do: Ask the operator to enable webhook delivery for your account (they arm it per relationship); until then use POST /v1/webhooks/test on the sandbox to prove your receiver, and poll GET /v1/bookings for changes.
rate_expired HTTP 410RESERVED — not returned by any /v1 endpoint today. It is published so the code enum is stable, and it would mean: the priced result this request was built on is too old to honour. There is no `rate_id` in this API to expire — a quote expiring is `quote_expired` (409). Do NOT confuse it with `rate_limited` (429), which is one character away and IS retried.
Retry: No — re-search. (Never returned today.)
What to do: This code is reserved and never returned today; if you ever receive it, search again with GET /v1/availability and quote afresh — a priced result has a lifetime and this one ended.
distribution_channel_not_approved HTTP 422The downstream distribution channel named on the search, the quote or the booking is not approved by the operator for your account at this moment: it is not registered, it is proposed, suspended or deactivated, or its approval is not in effect. Nothing was priced, booked or changed.
Retry: No — name an approved channel, or have this one approved first.
What to do: Read GET /v1/distribution-channels and send the code of a channel whose status is approved and whose effective window covers now; to sell through another channel, ask the operator to register it and give its written approval first.
driver_age_out_of_range HTTP 422The driver's age is outside the vehicle's min/max.
Retry: No.
What to do: Compare the driver's age with the min_driver_age / max_driver_age the vehicle publishes in GET /v1/reference/vehicles and offer a group the driver qualifies for; the operator's age rules are not negotiable per booking.
idempotency_key_reused HTTP 422The same Idempotency-Key was sent with a different body — or, for an account whose credentials reach more than one supplier, it was already used with a different supplier.
Retry: No — use a fresh key for a different request.
What to do: An Idempotency-Key belongs to exactly one logical request to one supplier. Mint a new UUID for a different body or a different supplier; reuse a key only to replay the identical request, to the same supplier, after a timeout.
no_availability HTTP 422The selected group is no longer available for those dates.
Retry: No — re-search.
What to do: The group has no units left for those dates. Search again with GET /v1/availability for the same dates and offer the customer another group, or other dates.
not_sellable HTTP 422The requested group/product cannot be sold for this request (e.g. incoherent or withheld configuration).
Retry: No — choose another option.
What to do: Choose another option from the availability response: the group or product you named cannot be sold for this request as configured by the operator, and no retry changes that.
supplier_mismatch HTTP 422The ids in one request belong to more than one of the suppliers your API key reaches, for example a pickup station of one supplier and a vehicle of another. A request is executed by exactly one supplier. Returned only to a key that reaches more than one supplier.
Retry: No — correct the ids.
What to do: Take every id in the request from the same supplier's reference data (read GET /v1/reference/* with X-Supplier-Id naming that supplier), and send the same X-Supplier-Id on this request so the supplier is explicit.
unknown_supplier HTTP 422The X-Supplier-Id header names a supplier this API key cannot sell. Sent today for any value that is not your own supplier_id.
Retry: No — send your own supplier id, or omit the header.
What to do: Read GET /v1/suppliers, take the supplier_id of the supplier you meant, and send that value — or omit the header entirely, which is always valid because the supplier is implied by the ids in your request.
validation_error HTTP 422Field-level validation failed; details[] names each field and issue.
Retry: No — fix the fields.
What to do: Read details[]: each entry names the field and the issue; correct every field named, then resubmit — sending the same body again returns the same answer.
rate_limited HTTP 429Too many requests for this partner key.
Retry: Yes — honour Retry-After with exponential backoff.
What to do: Slow down: honour Retry-After, read X-RateLimit-Remaining and X-RateLimit-Reset on every response, and spread bulk reads (catalogue refreshes) across the window instead of bursting them.
internal_error HTTP 500An unexpected fault on our side, and we are alerted to it. It is not load: a service too busy to answer, or a database failing over, answers `503 service_unavailable` instead.
Retry: Yes — backoff with jitter.
What to do: Retry with exponential backoff and full jitter — wait a random time up to 0.5 s × 2ⁿ before retry n, capped at 8 s — keeping the same Idempotency-Key on a write so the retry replays rather than re-executes; if it persists, quote the request_id to support.
reference_set_too_large HTTP 500A reference dataset exceeded the servable bound; the operator has been alerted.
Retry: Yes — backoff; contact support if persistent.
What to do: Retry later with backoff — the operator has been alerted and is trimming the dataset; use changed_since on the catalogue endpoints so a refresh reads only what moved.