Skip to main content
The REST API uses RFC 9457 Problem Details for every error. Every error response has Content-Type: application/problem+json and a consistent JSON shape.

Error response format

Status code reference

4xx — client errors

5xx — server errors

Common errors

Validation (422)

Required fields missing or wrong types.

Unique constraint (409)

Duplicate value on a unique column. The errors[].field tells you which column.
For handling this without the read-then-write race condition, use a GraphQL upsert.

Foreign key (422)

Referencing a related record that doesn’t exist.

Not found (404)

Rate limit exceeded (429)

The response includes rate-limit headers — back off using Retry-After:

Query timeout (504)

Tighten the filter, paginate, add an index, or move to a view that pre-computes the result.

Field names in errors

Database column names are converted to camelCase in error messages — a constraint on created_at reports the field as createdAt. This matches what you send and receive in JSON bodies.

Handling errors in code

Switch on status first, then on type for ambiguous statuses:

FAQ

400 means the request itself was malformed (bad JSON, wrong content type). 422 means the request was syntactically fine but semantically invalid — required fields missing, types wrong, constraints violated. The distinction tells your code whether to retry the same payload or fix the request shape first.
401 means “we don’t know who you are” — the auth token is missing, expired, or invalid. 403 means “we know who you are, but you can’t do this” — the role doesn’t have the necessary permission. Refresh tokens for 401; surface a permission error for 403.
Yes, with exponential backoff. Most 5xx errors are transient. Cap retries at 3–5 and alert if they keep failing.
The errors array gives field-level reasons. For complex cases, the instance URL points to the request that failed — useful for support tickets.
2xx and 4xx responses are cached and returned on retry with the same key. 5xx responses are not cached, so retries actually re-run the operation. See Idempotency.