Errors
Every failed request returns a non-2xx status and a body in one shape:
{ "error": { "code": "store_closed", "message": "This store is currently closed.", "request_id": "req_9f2c1a7b3d4e", "details": { } }}codeis stable and meant for yourswitchstatements. Never match onmessage— the wording can improve.messageis safe to log and, for business-rule errors, to show to a customer.request_idis also in theX-Request-Idresponse header. Quote it when you contact support.detailsappears when there is more to say, for exampleerrors[]for validation failures.
Status codes
Section titled “Status codes”| HTTP | Meaning | Retry? |
|---|---|---|
200 / 201 / 202 | Success / created / accepted for later processing | — |
400 | The request is malformed or fails validation | No — fix the request |
401 | Missing or invalid key | No |
403 | Key lacks a scope, or endpoint not available to this key | No |
404 | Resource does not exist or is not yours | No |
409 | Conflict with the current state | No — read the order first |
413 | Body larger than 256 KB | No |
422 | Valid request, but a business rule refuses it | No — tell the customer |
429 | Rate limited | Yes — after Retry-After |
500 / 502 | Something failed on our side | Yes — with back-off |
Error codes
Section titled “Error codes”Authentication
Section titled “Authentication”| Code | HTTP | When |
|---|---|---|
missing_api_key | 401 | No Authorization header |
invalid_api_key | 401 | Unknown, expired or revoked key, or suspended account |
insufficient_scope | 403 | The key lacks the required scope (details.required_scope) |
sandbox_only | 403 | Sandbox endpoint called with a live key |
Request
Section titled “Request”| Code | HTTP | When |
|---|---|---|
validation_error | 400 | A parameter or the body is invalid. details.errors lists each field and message |
invalid_json | 400 | The body is not valid JSON |
payload_too_large | 413 | Body over 256 KB |
not_found | 404 | Unknown id or route — or a resource that belongs to another account |
rate_limited | 429 | Over your per-minute limit |
Orders (business rules, 422 unless stated)
Section titled “Orders (business rules, 422 unless stated)”| Code | When | What to show |
|---|---|---|
store_closed | The store is closed, paused or busy | “This restaurant isn’t taking orders right now” |
out_of_zone | The address is outside the delivery area | “We don’t deliver to this address” |
below_minimum_order | Basket is below the store’s minimum | The minimum from delivery.min_order |
item_unavailable | An item is sold out, hidden or not sold by this store | Refresh the menu |
address_incomplete | floor, apartment or area missing (details.missing_fields) | Ask for them — the rider needs them |
fulfilment_not_supported | The store does not offer delivery / pickup | Hide that option |
cash_not_allowed | Cash is not enabled for your account | Offer card |
idempotency_conflict (409) | Same key, different body | A bug in your key generation |
order_not_cancellable (409) | The kitchen has started or the order is finished | Contact the restaurant |
invalid_payment_method (409) | Payment link requested for a non-card order | — |
already_paid (409) | Order is already paid | — |
payment_not_possible | The order cannot be paid (for example cancelled) | — |
order_rejected | Other order-level refusal; read message | Show the message |
Webhooks and sandbox
Section titled “Webhooks and sandbox”| Code | HTTP | When |
|---|---|---|
invalid_url | 422 | Webhook URL is not https, not public, or does not resolve |
webhook_not_configured | 422 | Test or rotate requested before a URL is set |
order_not_advanceable | 409 | Sandbox order is already in a final state |
Server
Section titled “Server”| Code | HTTP | When |
|---|---|---|
upstream_error | 502 | A downstream system (restaurant network, payments) failed — retry with the same idempotency key |
internal_error | 500 | Unexpected failure on our side — retry; if it persists, send us the request_id |
Handling errors well
Section titled “Handling errors well”const body = await res.json();if (!res.ok) { switch (body.error.code) { case 'store_closed': case 'out_of_zone': case 'below_minimum_order': case 'item_unavailable': return showToCustomer(body.error.message); // expected, user-facing case 'rate_limited': case 'upstream_error': case 'internal_error': return retryLater(res.headers.get('Retry-After')); // transient default: log.error({ code: body.error.code, requestId: body.error.request_id }); throw new Error('Unexpected API error'); // a bug on our side or yours }}