Concepts
Errors
Errors come back as JSON with a stable code. Build your handling on error.code; the message is for people and may change.
Error shape
{
"error": {
"code": "insufficient_scope",
"message": "Key is missing required scope: transactions.read",
"request_id": "…"
}
}error.code: the contract. Branch on it.error.message: a sentence for logs and people. Don't parse it.error.request_id: the same value as thex-request-idheader. Quote it when you ask for help.error.details: on a422, what failed. For an order, one entry per failing item with itsitem_indexand a code such asitem_unavailable,invalid_variant,invalid_modifiersorquantity_invalid.
Error bodies never contain database text. If something breaks on our side you get internal_error and a request_id; the detail stays in our logs.
Error codes
| Code | HTTP | When | Do |
|---|---|---|---|
invalid_key | 401 | The key is missing, malformed or revoked. | Fix the request |
payment_required | 402 | The business is not on Sansflow Premium. The key itself is fine; the owner upgrades the plan. | Tell the person |
insufficient_scope | 403 | The key lacks the scope this endpoint needs, or it is pinned to another outlet. The message says which. | Fix the request |
validation_failed | 400 | A query parameter did not parse (the message names it), or from/to span more than 400 days. | Fix the request |
validation_failed | 422 | On a POST: the body failed validation. error.details lists what failed. | Fix the request |
invalid_request | 400 | The id in the path is not a UUID, or a required query parameter is missing. | Fix the request |
invalid_json | 400 | The POST body is not valid JSON. | Fix the request |
idempotency_key_required | 400 | A POST without an Idempotency-Key header (1 to 255 characters). | Fix the request |
not_found | 404 | Unknown path under an endpoint. | Fix the request |
transaction_not_found | 404 | No such transaction for this key (another business, deleted, or across the test/live split). | Fix the request |
customer_not_found | 404 | No such customer for this business. | Fix the request |
order_not_found | 404 | The order was not created through the API, belongs elsewhere, or is across the test/live split. | Fix the request |
booking_not_found | 404 | No such booking request for this business, or it is at an outlet the key is not pinned to. | Fix the request |
table_not_found | 404 | table_id is not an active table at the booking's outlet. | Fix the request |
location_not_found | 404 | location_id is unknown or inactive for this business. | Fix the request |
method_not_allowed | 405 | Read endpoints accept GET only; write endpoints POST only. | Fix the request |
idempotency_key_conflict | 409 | The Idempotency-Key was already used with a different request. Use a new key. | Fix the request |
capacity_full | 409 | The slot has no free capacity. The booking stays pending; retry later or ask the venue. | Retry later |
table_taken | 409 | The named table already has an overlapping reservation. | Retry later |
proof_required | 409 | The booking has a pre-order and no transfer proof yet. Confirm again once the guest uploads it. | Retry later |
booking_not_confirmable | 409 | The booking is rejected or expired and cannot be confirmed. | Tell the person |
booking_not_rejectable | 409 | The booking is already confirmed or expired and cannot be rejected. | Tell the person |
rate_limited | 429 | Too many requests this minute. Wait the seconds in Retry-After. | Retry later |
internal_error | 500 | Something failed on our side. Retry with backoff and quote the request_id if it persists. | Retry with backoff |
Retrying
- 4xx (except
429and the slot-related409s): the same request will fail the same way. Fix it first. - 429: wait the number of seconds in the
Retry-Afterheader, then retry. See Rate limits. - 5xx and timeouts: retry with exponential backoff. For a
POST, reuse the sameIdempotency-Keyso a request that did succeed is replayed, not repeated. See Idempotency.
Stuck? Quote the request_id from the response and write toSansflow support.Checked against API v1 on .