Error Handling
Error envelope
Section titled “Error envelope”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 " | ". |
HTTP status codes
Section titled “HTTP status codes”| 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. |
Known error codes
Section titled “Known error codes”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.
Validation errors
Section titled “Validation errors”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.
Idempotent creates are not errors
Section titled “Idempotent creates are not errors”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.
Feature-gate errors (403)
Section titled “Feature-gate errors (403)”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.
What’s still not confirmed
Section titled “What’s still not confirmed”- Whether the exact
messagewording shown above is guaranteed stable across backend versions — don’t pattern-match onmessagetext in production code, only oncodeand the HTTP status. - Whether
codevalues beyond the table above exist for situations not yet exercised (for example, a very malformed request body).
