Adrasis™

Errors

The error envelope shape and the full HTTP status-code reference, with what each code means, how your integration should react, and when to partition stalled SEARCH_TIMEOUT batches.

Every error response carries a stable machine-readable code, and every status code maps to a defined condition.

Error envelope

Every error on this API has the same shape. One handler covers the whole surface, including authentication failures and unrouted paths. The single exception is the OAuth token endpoint, which follows the RFC 6749 error body (error, error_description) as the OAuth standard requires. See Authentication.

{
  "error": {
    "code": "BOOKING_NOT_CANCELLABLE",
    "message": "booking is not cancellable",
    "trace_id": "0f2c9d6e-7b1a-4c3e-9a2f-1d6e7b1a4c3e",
    "retryable": false
  }
}
Field Type Notes
code string A stable, machine-readable UPPER_SNAKE error code. Branch on this, not on message.
message string A human-readable description. Wording may change; do not parse it.
trace_id string Always present. Correlates the response with our logs; quote it in support enquiries.
retryable boolean Whether a retry (after re-prebooking where relevant) can succeed.
field string Optional. Present when the endpoint can unambiguously identify the invalid request member; use it for form feedback, not error classification.

code, message, trace_id, and retryable are always present. When the request omits X-Request-Id, the API generates trace_id. When the request includes X-Request-Id, the API echoes that value as trace_id for correlation.

Status-code reference

Status Meaning How to react
400 Bad Request The request is malformed: bad JSON, an unknown body field or query parameter (field names it), a value outside a published minimum / maximum / pattern, an invalid date, a failed field validator, a forbidden search breadth field, more than 200 bulk properties, a max_offers_per_property outside 1..250 or a string other than "all", a missing selection proof (SELECTION_TOKEN_REQUIRED), an invalid proof (INVALID_SELECTION_TOKEN), a quoted block disagreeing with the quote sealed in an otherwise valid proof (QUOTED_MISMATCH, with field naming the member), a prebook_token that is unreadable, tampered with, or minted for a different caller (INVALID_TOKEN), a missing Idempotency-Key on book / package cancel (IDEMPOTENCY_KEY_REQUIRED), or a request carrying both a partner credential and Adrasis's internal service headers (AMBIGUOUS_AUTH). Place search adds PLACE_TYPE_UNSUPPORTED (an airport or landmark place, or a near place without a point), PLACE_TOO_BROAD (the place or radius exceeds the limits described below) and INVALID_CURSOR (a cursor that is tampered with, issued to another caller, or sent with a changed field). GET /catalog/changes answers INVALID_CURSOR or INVALID_REFERENCE_FILTER (an unsupported resource_type). The webhook endpoints answer INVALID_WEBHOOK_ENDPOINT for a malformed URL, cursor, subscription, property, profile or body. Fix the request. Do not retry unchanged. On INVALID_TOKEN the credential is not the problem, so refreshing it changes nothing: prebook again for a usable token.
401 Unauthorized The credential is missing, malformed, expired, revoked, or signature-invalid. Codes: CREDENTIAL_MISSING, CREDENTIAL_MALFORMED, CREDENTIAL_UNAUTHORIZED. Carries a WWW-Authenticate: Bearer challenge. A rejected prebook_token or selection_token is a body or quote error, not a credential failure: malformed proofs return 400, expired selection proofs 410, and expired prebook tokens 409. Obtain a fresh token (see Authentication) and retry.
403 Forbidden The token is valid but lacks the required scope (INSUFFICIENT_SCOPE), or an explicitly selected profile is inactive (PARTNER_PROFILE_INACTIVE). Use a credential/token with the required scope, or ask the account owner to select or activate the intended profile. Do not retry unchanged.
404 Not Found The addressed resource is outside the authenticated account/profile boundary: an unknown property (PROPERTY_NOT_FOUND), room or rate plan on an availability check (ROOM_NOT_FOUND, RATE_PLAN_NOT_FOUND), booking (BOOKING_NOT_FOUND), trip (TRIP_NOT_FOUND), service zone, vehicle type, tour or flight (CATALOG_RESOURCE_NOT_FOUND), or an unknown country_code on GET /reference/place-types (COUNTRY_NOT_FOUND); a place public ID that is unknown or deleted, or one that exists only as an administrative area (PLACE_NOT_FOUND), a webhook endpoint or delivery that is absent or belongs to another account (WEBHOOK_ENDPOINT_NOT_FOUND), or the path is not an endpoint on this API (NOT_FOUND). An unknown or cross-account profile returns PARTNER_PROFILE_NOT_FOUND with field=profile_public_id when the selector is a query field. Verify the public ID and selected profile, or check the path against this reference. Do not retry blindly.
405 Method Not Allowed The path exists but does not accept that method. Code: METHOD_NOT_ALLOWED. Answered by the router before any handler runs, which is why no single operation lists it. Use the method documented for the endpoint.
409 Conflict The request conflicts with current state: an Idempotency-Key reused with a different body (IDEMPOTENCY_KEY_CONFLICT), an exact partner reference already owned by another booking in the same account namespace (AGENCY_REFERENCE_CONFLICT), an expired or spent prebook token, a selection proof for a property no longer open to the profile (SELECTION_RESELECT_REQUIRED), a booking in a terminal status (BOOKING_NOT_MODIFIABLE), a booking already in an active trip (BOOKING_ALREADY_LINKED), a duplicate or over-limit webhook endpoint (WEBHOOK_ENDPOINT_ALREADY_EXISTS, WEBHOOK_ENDPOINT_LIMIT_REACHED), a webhook delivery that is not retryable (WEBHOOK_NOT_RETRYABLE), a catalogue or place-search walk whose catalogue changed underneath it (CATALOG_VERSION_CHANGED), a place-search cursor whose kept search set has expired (SEARCH_EXPIRED), a test event asked of an endpoint that has not been verified (WEBHOOK_ENDPOINT_NOT_ACTIVE), an amendment of an external stay, which only its supplier can change (EXTERNAL_STAY_NOT_MODIFIABLE), an external stay's supplier refusing a cancellation or its quote (SUPPLIER_REJECTED), a package whose live flight the airline refused at booking, its other items cancelled (LIVE_FLIGHT_REFUSED), a package cancellation that reaches a live flight whose order still holds seats at the airline (LIVE_FLIGHT_ORDER_OPEN), or a flight or tour prebook of a tour operator's offer that it sells inside a package only (ENROLLED_OFFER_PACKAGE_ONLY). Resolve the named conflict: reuse the existing resource, compose a package-only offer into a package, use a new key for a new operation, re-prebook, verify the endpoint before testing it, or stop when the state is terminal. For CATALOG_VERSION_CHANGED and SEARCH_EXPIRED, discard the partial walk and search again from the first page.
410 Gone The selected proof expired (SELECTION_TOKEN_EXPIRED), prebook found the exact slots sold out, or the rate disappeared between prebook and book (SOLD_OUT, RATE_NOT_AVAILABLE). Return to search for a fresh selection; do not retry the same token.
412 Precondition Failed The If-Match ETag for a booking or webhook endpoint is stale. Codes: BOOKING_VERSION_CONFLICT, WEBHOOK_VERSION_CONFLICT. Re-read the resource, re-apply the change, and retry with the new ETag.
422 Unprocessable Entity The request is well-formed but cannot be fulfilled: an unsupported currency pair, a rejected selling-price override, an unsupported payment intent, a child the property does not accept (GUEST_AGE_NOT_ACCEPTED), a single-property search whose best allocation or complete room list exceeds the work budget (SEARCH_WORK_LIMIT_BREACH), a prebook mixing pay-at-property and prepaid rooms (MIXED_SETTLEMENT), a webhook URL that resolves to a forbidden destination (WEBHOOK_DESTINATION_BLOCKED), a booking by a sub-agency whose account the provider suspended (ACCOUNT_SUSPENDED) or whose net is more than its account still has available (CREDIT_LIMIT_EXCEEDED), or a package whose live flight refuses a passenger's details (FLIGHT_PASSENGER_INVALID). Adjust the inputs. Nothing was written and no inventory was held.
428 Precondition Required A conditional booking or webhook endpoint write omitted If-Match. Code: PRECONDITION_REQUIRED. Read the resource, then retry with its current ETag.
415 Unsupported Media Type A request carrying a body declared a Content-Type other than application/json. Code: INVALID_REQUEST. Sending no Content-Type at all is accepted. Send Content-Type: application/json with the body.
429 Too Many Requests The edge throttled general traffic, or the webhook API returned RATE_LIMITED after an account exhausted its published management or manual-delivery budget. Webhook responses use the normal error envelope. Both may carry Retry-After. Back off with jitter and honor Retry-After. Reuse the same idempotency key for a webhook command retry. See Rate limits and usage.
500 Internal Server Error An unexpected server-side error. Retry only when retryable is true. If it persists, capture the trace_id and contact support.
503 Service Unavailable Search capacity is exhausted (SEARCH_OVERLOADED), a search exceeded its server deadline (SEARCH_TIMEOUT), /book did not confirm an outcome inside its 50-second application deadline (BOOKING_TIMEOUT), profile resolution is temporarily unavailable (PARTNER_PROFILE_UNAVAILABLE), the supplier of an external stay cannot be asked now (SUPPLIER_UNAVAILABLE, with Retry-After), or the webhook API cannot count a management request against its budget (SERVICE_UNAVAILABLE). Search failures carry Retry-After; branch on code. Honor Retry-After when present and retry when retryable is true. For BOOKING_TIMEOUT, resend the exact body with the exact same Idempotency-Key; the API timeout is 50 seconds.

Response handling

On GET /properties and GET /bookings, an invalid or obsolete cursor is 400 INVALID_REQUEST with field=cursor; restart the lookup from its first page. On GET /catalog/changes, an invalid cursor is 400 INVALID_CURSOR, while a cursor that can no longer continue is 409 CATALOG_VERSION_CHANGED: rebuild the property snapshot and resume from its terminal changes_cursor.

  • Branch on code, never on message. Codes are stable; messages are not.
  • Retry only the retryable. Retry eligible 429, 500, and 503 responses with bounded backoff and jitter; honor Retry-After on 429 and 503. 401 is retryable after refreshing the token. 400, 403, 404, 409, 410, and 422 indicate a request or state problem and will not be fixed by an unchanged retry. PREBOOK_EXPIRED (409) succeeds after a fresh prebook. Every error carries a retryable boolean stating this directly, so prefer it over inferring from the status code.
  • Retry writes with the same idempotency key. When retrying book, re-send the same Idempotency-Key and identical body so the retry replays rather than duplicates. See Idempotency.
  • Keep the trace_id. Every error response carries one, including the 401 you get before authenticating; log it so a single request can be traced end to end in a support enquiry.

Per-property search failures

POST /properties/availability returns 200 when an individual property fails. The failure surfaces inline on results[i].error_code: PROPERTY_NOT_FOUND, UNSUPPORTED_FX_PAIR, SEARCH_WORK_LIMIT_BREACH, SEARCH_TIMEOUT, SUBSTRATE_DATA_INVARIANT, or INTERNAL_ERROR. Inspect every result. Two request-level failures are exceptions: search capacity exhaustion returns top-level 503 SEARCH_OVERLOADED, and a deadline before any row completes returns top-level 503 SEARCH_TIMEOUT. Both carry Retry-After. SEARCH_TIMEOUT is the only retryable inline code; keep every other row and retry only the affected property IDs with bounded backoff. Preserve their relative order and all other search fields. For an inline timeout response, even if the first request omitted it, set request_time to the first response's request_time on every retry. If a retry does not reduce the timeout set, partition the remaining IDs into smaller ordered batches (for example, halves) before the next attempt. Continue within an overall retry/time budget; a single-property timeout may be retried with backoff, but SEARCH_WORK_LIMIT_BREACH is deterministic and must not be retried unchanged.

On an initial availability request, a client-supplied request_time may be at most 30 days behind or 60 seconds ahead of the server clock; both boundaries are inclusive. A value outside the inclusive [server_time - 30 days, server_time + 60 seconds] window is a top-level 400 INVALID_REQUEST with field=request_time; it never appears as a per-property error_code.

For POST /places/availability, omitting request_time is safe on every page: the signed cursor restores the first page's server-resolved instant. That valid cursor-carried fixed instant remains usable until the cursor expires, even if the server clock crosses the initial 30-day lower boundary during the walk. An explicit value on a continuation must equal the first response. Do not derive it from the HTTP Date header.

A top-level 503 has no property rows. Honor Retry-After and branch on its code. For SEARCH_OVERLOADED, reduce concurrent search pressure and retry the request. For SEARCH_TIMEOUT, no property completed before the deadline, so partition the ordered property IDs into smaller batches before retrying. Preserve the original request_time; if the original request omitted it, choose one new evaluation instant before the first partitioned retry and reuse that value across every partition.

Place search has its own request-level failures, all in the normal error envelope, none of them retryable unchanged:

Code Status Meaning
PLACE_NOT_FOUND 404 place_id, or near.place_id, is not a known Adrasis place id or country code, is deleted, or is an administrative-only area. Take it from GET /countries, GET /places or location.destinations[].
PLACE_TYPE_UNSUPPORTED 400 place_id is an airport or a landmark, which are not searchable as a place; use it as the centre of a near search instead. With near.place_id (field=near.place_id): the place has no point, a country for one.
PLACE_TOO_BROAD 400 A sorted or filtered place search exceeds 10,000 visible properties or 1,000 properties still requiring a price after unavailable ones are ruled out. Search a smaller place or add property-level filters. A near search has the same limits inside its radius and names field=near.radius_km; reduce the radius or add property-level filters. Price, board and refundability filters do not narrow these counts.
INVALID_CURSOR 400 The cursor is tampered with, was issued to another caller, or a repeated field, a near point or radius included, differs from the first request. A continuation may send the cursor alone.
SEARCH_EXPIRED 409 The cursor or its kept search set outlived the quote window. Search again from the first page.
CATALOG_VERSION_CHANGED 409 The catalogue changed during the walk. Search again from the first page.

The single-property detail route uses top-level status codes because it has no batch isolation boundary: 404 PROPERTY_NOT_FOUND, 422 UNSUPPORTED_FX_PAIR or SEARCH_WORK_LIMIT_BREACH, and 500 for a server failure. SEARCH_OVERLOADED and SEARCH_TIMEOUT both use 503; branch on code to distinguish them. Optional projection failures do not invalidate priced allocations; they appear as typed projection_issues inside a successful response. An issue's code is INTERNAL_ERROR when the block failed on the server, UNSUPPORTED_FX_PAIR when a fixed tax amount is kept in a currency with no current rate to your currency_code (not retryable; a currency from GET /reference/currencies may price it), or SUBSTRATE_DATA_INVARIANT when the property's stored tax data is inconsistent.

Guest age refusals

A property's guest age rules surface in three places, and only one of them is an HTTP error:

Where What you receive Meaning
Search: POST /properties/availability, POST /places/availability and the detail route 200 with no allocation and unavailable_reason: GUEST_AGE_NOT_ACCEPTED A requested child is younger than the youngest age the property accepts. It is not an error, and the same party gets the same answer on a repeat.
POST /availability/check 200 with verdict: REJECT, reject_reason GUEST_AGE_NOT_ACCEPTED or CHILD_AGES_REQUIRED, and failing_gate: 0 The age rule failed before any gate ran. CHILD_AGES_REQUIRED means the party has children without child_ages at a property whose minimum guest age is 1 to 17; send the ages to resolve it. An adults-only property answers GUEST_AGE_NOT_ACCEPTED for any child, ages sent or not.
POST /prebook, POST /package/search and POST /package/prebook 422 with error.code GUEST_AGE_NOT_ACCEPTED and retryable: false The property no longer accepts a child in the sealed party. Search again: the property now comes back unavailable for the same reason.

Branch on the code, never on the message. Offer the traveller another property, or a party the property accepts, instead of retrying the same request. A property's min_guest_age is in Property content, so a client can show the rule before it searches.

Mixed room settlement

422 MIXED_SETTLEMENT with field=selection_tokens refuses a selection containing both pay-at-property and prepaid rooms. Choose rooms with the same settlement route. No inventory is held.

Next steps