Errors and rate limits
Every error the API can answer with, what it means and what to do, plus the rate-limit headers and how to back off.
Last updated September 17, 2026
On this page
Every failure answers with the matching HTTP status and a JSON body:
{ "error": "unknown_field", "message": "Order has no field 'totl'. Fields: total, status, ..." }The error value is stable and safe to branch on; the message is for a person.
401 and 403
| Code | When | Fix |
|---|---|---|
unauthorized | No key, an unknown or revoked key, or a key from a different app. One answer for all of it. | Send the key from the API tab in X-Api-Key. Check it is whole, not revoked, and belongs to the app you are calling. |
secret_key_in_browser | A secret key was presented from a browser. | Move the call to your server, or use a publishable key in the page. |
read_only_key | A publishable or read-only key tried to write, or a publishable key asked for deleted records. | Use a secret key with full access from your server. |
key_too_narrow | A key limited to named tables called a function. | Use a key without a table limit. |
field_not_readable | A publishable key filtered, sorted or selected created_by_id. | Drop that field; only secret keys see it. |
404
| Code | When | Fix |
|---|---|---|
no_such_entity | No table by that name, or one this key may not see. | Check spelling and capitalisation against /api/openapi.json. |
no_such_record | No record with that id, or it was deleted. | Check the id. A deleted record can be restored. |
no_such_function | No function by that name, or it is off or internal. | The callable functions are in the OpenAPI document. |
not_published | The app has never been published. | Publish it. Keys made beforehand keep working. |
not_found | Wrong hostname or path. | Check both. |
409 and 412
| Code | When | Fix |
|---|---|---|
ambiguous_match | An upsert matched more than one record. | Match on a unique field or clean up duplicates. |
idempotency_in_flight | The same Idempotency-Key is still being processed. | Wait for Retry-After and repeat. |
precondition_failed | If-Match did not match: the record changed since you read it. | Read it again and retry with the new ETag. |
413 and 422
| Code | When |
|---|---|
body_too_large | The body is over the size ceiling. Send fewer records per call. |
response_too_large | The page asked for is too big. Use a smaller limit or fewer fields. |
bad_body | Missing or malformed JSON, or an object where an array belongs (or the reverse). |
bad_filter | The q filter is not usable; the message names the part. |
bad_sort, bad_limit, bad_skip, bad_cursor, bad_fields, bad_search, bad_deleted | A parameter is out of shape; the message says how. |
unknown_field | A filter, sort, field list, upsert match or body named a field the table does not have. |
reserved_field | A body tried to set id, created_date, updated_date or created_by_id. |
invalid_record | A field failed the table's schema: wrong type, value outside the enum, required field empty. |
missing_id | A bulk update item has no id. |
filter_required | Delete-many or update-many arrived without a filter. |
too_many | More records than one call may take; the message names the maximum. Split the work. |
idempotency_mismatch | An Idempotency-Key was reused for a different request. |
bad_idempotency_key | The key is longer than 255 characters. |
429: rate limited
Too many calls from a key or app, or too many refused credentials from one address, answers rate_limited with a Retry-After header. Wait and retry; if you are backfilling, use the bulk endpoints instead of one call per record. Every successful answer carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset so you can stay under the allowance.
500, 503 and 504
| Code | When | Fix |
|---|---|---|
function_failed | A function ran and threw, timed out or was stopped. | Read the run on the Automations tab. Retrying reruns it, so only retry if that is safe. |
function_unavailable | The function could not be started. | Wait for Retry-After and repeat. |
internal_error | Something unexpected on our side. | Retry once. Nothing about the request needs to change. |
upstream_unavailable | A temporary problem on our side. | Retry with a short backoff. |
upstream_timeout | The call took too long, or a webhook handler ran too long. | Retry. A webhook handler should acknowledge fast and do slow work afterwards. |
A note on strangers
A caller without a valid key learns nothing about the shape of your API: it sees unauthorized whatever it typed, and no verb or endpoint names in the message.