Developers
Errors
Failures use the same envelope as successes. Branch on error.code, show or log error.message, and keep the requestId.
Updated 2026-10-20
What does an error look like?
json
{
"ok": false,
"error": {
"code": "RATE_LIMITED",
"message": "Rate limit exceeded — retry in 12s.",
"details": { "retryAfterSeconds": 12 }
},
"requestId": "req_1b6e82"
}details is optional and varies by code (for example field on a validation error, or reason on UNAVAILABLE). The requestId matches the X-Request-Id header; quote it when you contact us.
Which error codes are there?
| Code | HTTP | Meaning | What to do |
|---|---|---|---|
VALIDATION | 400 (413 if the body is over 64 KB) | The request is malformed or breaks a rule: missing field, bad date or time, range over 92 days (details.maxDays: 92), missing or invalid Idempotency-Key, or a cart the checkout rules reject. details.field points at the problem. | Fix the request. Do not retry unchanged. |
AUTHENTICATION | 401 | Missing or malformed Authorization header, unknown key, wrong secret, or revoked key. | Check the Bearer value. Ask for a new key if it was revoked. |
FORBIDDEN | 403 | Your account or API access is disabled, the IP is not on your allow-list, or the experience is not enabled for you. Also: the certification endpoint called with a live key (details.reason: live_key). | Check the experience slug and your server IP, or contact the operator. |
NOT_FOUND | 404 | Unknown experience or extra, or a booking that is not yours (or is in the other key mode). | Check the id. Do not retry. |
UNAVAILABLE | 409 | The request is valid but cannot be done right now. details.reason says why (below). | Refresh availability and offer another time, or follow the reason. |
DUPLICATE | 409 | Two requests with the same Idempotency-Key ran at once (or the key is already in use elsewhere). | Wait a moment and retry with the same key to read the booking. |
CREDIT_LIMIT | 409 | On-account accounts, live keys only: this booking would take what you owe on account over your credit limit. details.reason is credit_limit, with details.limitCents, details.openCents and details.requestedCents (integer cents). Nothing is created. | Do not retry automatically. Retry with the same Idempotency-Key once an invoice is paid or the operator raises your limit. Before booking, check that openCents plus the quote total is at most creditLimitCents (GET /me). |
RATE_LIMITED | 429 | You went over a rate limit: your key's, or a per-IP limit (checked before your key is read). Every 429 uses this envelope, with details.retryAfterSeconds equal to the Retry-After header. | Wait Retry-After (or details.retryAfterSeconds) seconds, then retry. Back off and cache. |
SANDBOX_ONLY | 403 | A live key was used before live access was switched on. | Use your pk_test_ key, or ask the operator to enable live access. |
INTERNAL | 500 | Something went wrong on our side. | Retry with backoff (reuse the same Idempotency-Key when booking). If it persists, send us the requestId. |
Why did I get UNAVAILABLE?
details.reason tells you the first thing that failed. Slot reasons also include details.experience, the slug whose slot failed.
| reason | Meaning |
|---|---|
closed | The experience does not run that day. |
not_a_bookable_time | That time is not one of the experience’s slots. |
blocked | The slot is blocked by the operator. |
past_cutoff | Too late to book: inside the lead time. |
sold_out | No capacity left at that time. |
capacity | Each slot is open, but the whole cart does not fit. |
extra_slot_blocked | An extra in the cart is not available at that time. |
started | Cancel only: a paid or on-account live booking that has already started cannot be cancelled through the API. Contact us. |
pay_link_not_available | Your account is set to pay-link settlement with net-rate pricing, which is not supported. |
payment_in_progress | Cancel only: the customer is paying at this moment. Retry shortly. |
too_many_open_holds | You have 25 unpaid pay-link bookings holding seats (details.limit), or they hold 100 driving seats (details.seatLimit). When the seat limit is the one reached, details.heldSeats is the driving seats your unpaid bookings already hold and details.requestedSeats is the driving seats in this booking. Wait for customers to pay, or cancel unpaid bookings, then retry. |
On a test key, an error caused by a sandbox tag also carries details.trigger (for example soldout).
Which errors are safe to retry?
RATE_LIMITED,INTERNALand network timeouts: yes, with backoff. When booking, always reuse the sameIdempotency-Key, so a retry can never create a second booking.DUPLICATE: retry the same call once to read the booking that won.VALIDATION,AUTHENTICATION,FORBIDDEN,NOT_FOUND,SANDBOX_ONLY: no. Change the request or the account first.CREDIT_LIMIT: no, not automatically. Retry with the sameIdempotency-Keyonly after an invoice is paid or your limit is raised.UNAVAILABLE: not the same request. Re-check availability first.