Errors
Every error — 4xx or 5xx, on every product — uses one envelope:
{ "error": { "code": "order.invalid_transition", "message": "Order ord_9d31f2 is cancelled; it cannot move to preparing." }}Branch on error.code, never on error.message. Codes are stable and
dot-namespaced (<domain>.<problem>); messages are for humans and may change
wording at any time.
HTTP status usage
Section titled “HTTP status usage”| Status | Meaning | Retry? |
|---|---|---|
400 | The request is malformed: validation failure, unknown field, bad enum value | No — fix the request. Retrying the same payload returns the same error |
401 | Missing, expired or invalid credential | Refresh the token, retry once. If it fails again, stop — see Authentication |
403 | Authenticated but not allowed: revoked consent, wrong environment, disallowed operation | No — this does not resolve on its own |
404 | The resource does not exist, or no active integration covers it | No — check the id and the integration state |
409 | Conflict with current state, e.g. a duplicate create | No — read the current state and reconcile |
429 | Rate limited | Yes — after the Retry-After delay. See Rate limits |
500 / 502 / 503 | Server-side failure | Yes — exponential backoff with jitter, cap the attempts |
504 | Gateway timeout — the request may have been applied | Yes, but read the resource state first before repeating a write |
Stable codes
Section titled “Stable codes”| Code | Status | Cause |
|---|---|---|
request.validation | 400 | A body or query field failed validation; message names the field |
request.unknown_field | 400 | The body contains a field the endpoint does not accept (including app_id — the token carries it) |
auth.invalid_client | 401 | Unknown client_id, wrong secret, or revoked credential |
auth.token_expired | 401 | Access token past its lifetime |
auth.invalid_token | 401 | Malformed, revoked or wrong-environment token |
auth.invalid_grant | 400 | Authorization code expired, reused, or bound to a different redirect_uri |
auth.consent_revoked | 403 | Merchant uninstalled or revoked your app |
store.not_found | 404 | Unknown store id, or the store is outside your token’s reach |
store.not_integrated | 404 | The store exists but has no active integration for your app |
menu.push_not_allowed | 403 | The integration’s menu_sync_direction does not permit POS-to-Quiqqy pushes |
menu.job_not_found | 404 | Unknown async menu job id |
order.not_found | 404 | Unknown order id for your integration |
order.invalid_transition | 409 | The requested status is not reachable from the order’s current status |
rate.limited | 429 | Too many requests — the response carries Retry-After |
internal.error | 500 | Unexpected server failure; safe to retry with backoff |
The list grows over time; treat any unrecognised code as non-retryable unless the status row above says otherwise.
Order-denial reason codes
Section titled “Order-denial reason codes”Denying (cancelling) an order is not an error — it is a structured outcome.
When your POS cannot fulfil an order, send
POST /pos/orders/:orderId/cancel
with a free-text reason (shown to the customer) and a structured
reason_code from this enum, so Quiqqy can inform the customer accurately
and remediate automatically:
reason_code | Use when | What Quiqqy does |
|---|---|---|
ITEM_OUT_OF_STOCK | One or more lines cannot be made | Refunds; prompts an availability resync so the items stop selling |
STORE_CLOSED | The store is closed despite showing open | Refunds; flags the store’s hours for review |
POS_OFFLINE | The POS cannot receive or process orders right now | Refunds; can pause the store until you recover |
CAPACITY | The kitchen is too busy to take the order | Refunds; capacity signals feed prep-time estimates |
PRICE_MISMATCH | The order’s prices disagree with the POS catalogue | Refunds; flags the menu for re-sync |
OTHER | Anything else — include a message explaining it | Refunds; the message is surfaced to support |
{ "reason": "Burrata is finished for the day", "reason_code": "ITEM_OUT_OF_STOCK"}reason is free text — it is what the customer sees. The enum lives in
reason_code; an optional message can carry additional detail for support.