Core concepts
Errors.
Every error has the same shape:
JSON
{ "error": { "code": "check_not_found", "message": "Human-readable detail." } }
code is machine-stable forever — branch on it. message may improve over time — display it, never parse it.
Error codes.
| Code | HTTP | When |
|---|---|---|
| missing_key | 401 | No Authorization: Bearer … header. |
| invalid_key | 401 | Unknown or revoked key. |
| insufficient_scope | 403 | Key lacks the read/write scope the route requires. |
| validation_error | 400 | Request failed validation — message lists every failing field. |
| rate_limited | 429 | Over the rate limit — back off and retry (standard RateLimit-* headers are sent). |
| not_found | 404 | No such endpoint. |
| check_not_found | 404 | Check id doesn’t resolve for your restaurant. |
| cursor_not_found | 404 | starting_after doesn’t reference a known check. |
| table_not_found | 404 | Ingestion: table.id/table.number doesn’t match a table in your restaurant. |
| conflict_with_paid_items | 409 | Ingestion would remove/price-decrease claimed items — response includes conflict_item_external_ids. |
| invalid_status_transition | 409 | Attempt to reopen or re-close a closed/voided check (same-status replays are no-op successes). |
| invalid_webhook_url | 400 | Webhook URL isn’t HTTPS, doesn’t resolve, or points at a private address. |
| endpoint_not_found | 404 | Webhook endpoint id doesn’t belong to your restaurant. |
| internal_error | 500 | Something went wrong on our side — quote your X-Request-Id to support. |