Skip to content

Error Handling

Every error response — across menu, orders, hotels, and webhook registration — is a JSON object with a machine-readable code and a human-readable message:

{
"code": "item_not_found",
"message": "One or more menu items were not found: sku-123, sku-456."
}
Field Type Description
code string Machine-readable error code — build your error handling against this, not against message text.
message string Human-readable description, safe to log or show in an internal tool. For multi-field validation errors it joins each field’s errors with " | ".
Status Meaning
200 OK Successful GET, or a repeat POST to an idempotent create (order/reservation already exists — see below).
201 Created First successful POST to an idempotent create.
400 Bad Request Request body/query params failed validation, or referenced an id that doesn’t resolve to a real resource (e.g. an unknown menu item).
401 Unauthorized Missing, unknown, or inactive X-API-Key, or a wrong X-API-Secret.
403 Forbidden Credential is valid but the tenant hasn’t enabled the module you’re calling — see Feature gating.
404 Not Found The path itself references a resource that doesn’t exist (unknown external_order_id, external_reservation_id, or room_type_id).
409 Conflict The request is well-formed but conflicts with the resource’s current state (no availability, invalid status transition).
429 Too Many Requests You’ve exceeded the rate limit — see Rate Limits.
5xx Server Error Not covered by this error envelope. Safe to retry with backoff.

These are every code value the backend currently raises for the menu/orders/hotels/webhook endpoints, confirmed from source:

code Status Where Meaning
invalid_credentials 401 Any endpoint Missing/unknown/inactive X-API-Key, or wrong X-API-Secret.
invalid_request 400 Any endpoint with a request body Generic DRF field validation failure — message lists the field(s).
item_not_found 400 POST /orders/ One or more item_id values in the order don’t match a real menu item’s sku.
variant_not_found 400 POST /orders/ A variant_id doesn’t exist for the given item.
addon_not_found 400 POST /orders/ One or more addon_id values don’t exist.
room_type_not_found 404 (on POST /hotels/reservations/, GET /hotels/availability/) Hotel endpoints The room_type_id doesn’t exist for this tenant.
room_not_available 409 POST /hotels/reservations/ No availability for the requested room type/dates.
order_not_found 404 GET/POST /orders/{external_order_id}/... No order matches this credential’s external_order_id.
reservation_not_found 404 GET/POST /hotels/reservations/{external_reservation_id}/... No reservation matches this credential’s external_reservation_id.
invalid_status_transition 409 POST /orders/{id}/cancel/, POST /hotels/reservations/{id}/cancel/ Can’t cancel from the resource’s current status (e.g. already cancelled or completed). message includes the current status.
rate_limited 429 Any endpoint See Rate Limits.
error varies Fallback Anything not covered above (e.g. a generic DRF exception) — message carries whatever DRF’s own detail text was. Don’t rely on this message text being stable.

403 (feature-gate) responses use code: "error" with the generic DRF permission-denied message — see Feature gating.

Generic field validation (code: "invalid_request") joins each invalid field’s errors, one field per field: message segment, separated by " | ":

{
"code": "invalid_request",
"message": "quantity: Ensure this value is greater than or equal to 1."
}

Errors about a referenced id not existing (an unknown menu item, variant, add-on, or room type) use their own specific codes instead — see the table above, not the generic invalid_request.

POST /v1/third-party/orders/ and POST /v1/third-party/hotels/reservations/ are idempotent on external_order_id / external_reservation_id. Retrying a create with an id you’ve already used returns the existing resource with 200 OK — it is not an error, and does not create a duplicate. Only the first call for a given external id returns 201 Created.

A 403 from /v1/third-party/orders/* or /v1/third-party/hotels/* means the tenant hasn’t enabled that module for their account:

{
"code": "error",
"message": "You do not have permission to perform this action."
}

The underlying permission check is independent per module (a hotel-only tenant’s credential is only ever checked against the hotel feature flag, never the restaurant one, and vice versa), so this is never a sign of a broken or invalid credential. See Feature gating.

  • Whether the exact message wording shown above is guaranteed stable across backend versions — don’t pattern-match on message text in production code, only on code and the HTTP status.
  • Whether code values beyond the table above exist for situations not yet exercised (for example, a very malformed request body).