Errors
The consolidated catalog of stable error codes your client switches on, mapped to HTTP statuses with the trigger and the fix for each.
Every non-2xx response carries the same small JSON body: a stable, machine-readable error
code and a human-readable message. This page is the catalog of the error codes worth
branching on, and the HTTP statuses they ride on.
{
"error": "invalid_scope",
"message": "Invalid scope values: ['admin']. Allowed: ['read', 'write']"
}Switch on the HTTP status and the error code — never on the message text. The
message is for humans and may be reworded at any time; the status and error code are the
contract. See Response structure.
HTTP statuses
Every endpoint draws from this table. Individual reference pages call out any status that carries a special meaning for that route.
| Status | Meaning | Triggered by | What to do |
|---|---|---|---|
400 | Bad request | The body or query failed validation, or a read needs a connected account that is not yet connected. | Read error / message, fix the request, then retry. Do not blindly retry — it fails identically. |
401 | Unauthorized | Missing, malformed, or revoked bearer token. | Check the Authorization header; mint a new key if it was revoked. |
403 | Forbidden | Valid token, but it lacks the scope for this action. | Use a key with the required scope (for example a write key for a mutation). |
404 | Not found | The resource does not exist or is owned by another account. | Confirm the id. Another account's resource is indistinguishable from one that never existed — by design. |
409 | Conflict | The request collides with current state (uniqueness or a state-machine rule). | Re-read current state, reconcile, then retry. |
413 | Payload too large | An uploaded file or request body exceeds the size cap. | Shrink or compress the payload below the limit and retry. |
422 | Unprocessable entity | The body parsed but a field is semantically invalid (wrong type, out of range, failed a rule). | Fix the offending field named in message, then retry. |
429 | Too many requests | You exceeded a rate limit. | Back off and retry — see Rate limits. |
503 | Service unavailable | A dependency is briefly unavailable or warming up. | Transient — retry with backoff. If it persists, check status before assuming a client bug. |
404 hides ownership
A resource that belongs to a different account returns 404, exactly like one that never
existed. This is deliberate tenant isolation: you can never probe whether another account's
id is real.
Error code catalog
These are stable error codes a client is likely to branch on. The list is not exhaustive —
new codes can appear — so always keep a default branch that falls back to the HTTP status.
error code | Status | Trigger | Fix |
|---|---|---|---|
invalid_scope | 400 | A request asked for a scope value that is not allowed. | Request only read / write scopes when minting a key. |
api_key_not_found | 404 | An operation referenced an API key id that does not exist or is not yours. | Confirm the key id from the keys list; it may already be revoked. |
domain_taken | 409 | You tried to claim a custom domain that is already in use. | Choose a different domain, or release the existing claim first. |
segment_has_buttons | 409 | You tried to delete a segment that still has buttons attached to it. | Remove or reassign the segment's buttons, then delete the segment. |
rate_limited | 429 | You exceeded the per-account rate limit. | Back off with exponential delay and jitter, then retry the same request. |
Treat this catalog as a starting set, not a closed enum. When you encounter an error code
you do not recognize, fall through to handling by HTTP status — that always works.
Branch on status and code, not message
A correct client checks the status first, parses the consistent { error, message } body,
switches on the stable error code, and keeps a default that handles by status.
const res = await fetch(url, { headers });
if (!res.ok) {
const { error, message } = await res.json();
switch (error) {
case 'rate_limited':
return backoffAndRetry();
case 'invalid_scope':
throw new Error('use a key with the required scope');
case 'domain_taken':
return promptForDifferentDomain();
case 'segment_has_buttons':
throw new Error('remove the segment buttons before deleting it');
default:
// unknown code — fall back to the HTTP status
if (res.status >= 500) return backoffAndRetry();
throw new Error(`${res.status} ${error}: ${message}`);
}
}
const data = await res.json();res = requests.get(url, headers=headers)
if not res.ok:
body = res.json()
error, message = body["error"], body["message"]
if error == "rate_limited":
return backoff_and_retry()
if error == "invalid_scope":
raise RuntimeError("use a key with the required scope")
if error == "domain_taken":
return prompt_for_different_domain()
if error == "segment_has_buttons":
raise RuntimeError("remove the segment buttons before deleting it")
# unknown code — fall back to the HTTP status
if res.status_code >= 500:
return backoff_and_retry()
raise RuntimeError(f"{res.status_code} {error}: {message}")
data = res.json()Next steps
- Response structure — the error shape and status table.
- Rate limits — how to back off correctly on
429. - SDKs & tooling — a wrapper that raises a typed error carrying the
errorcode. - FAQ — quick answers, including why some reads return
400before you connect.
Credits & usage
What one credit is, which actions cost a credit and which are free, how monthly plan allowances and rollover work, and where to watch your usage in real time.
SDKs & tooling
Thin typed wrappers for Node and Python that centralize the base URL, bearer auth, retry/backoff, and pagination — plus the published OpenAPI spec for Postman, Insomnia, and code generators.