Errors
What each status means, what the body carries, and which ones to retry.
All error responses are JSON objects with at least an error string. Machine-readable fields (code, capability, resource, retry_after_seconds) are included when applicable.
Status catalog
| HTTP | Meaning | Typical code | Retry? |
|---|---|---|---|
400 | Client validation failure | — | No (fix request) |
401 | Missing, malformed, unknown, or revoked token | — | No (fix credentials) |
403 | Valid token but org lacks api_access | upgrade_required | No until plan upgrade |
403 | Valid token without the scope this route needs | insufficient_scope | No (mint a token with the scope) |
404 | No such record in this workspace | not_found | No |
409 | Token's minting admin is no longer an admin | api_actor_unavailable | No (mint a new token) |
409 | Idempotency-Key reused for a different request | idempotency_key_reused | No (use a new key) |
409 | Deliverable is signed off, so nothing on it can change | deliverable_signed_off | No |
405 | Wrong HTTP method | — | No |
413 | Inline image over the 10 MB decoded cap | payload_too_large | No (send a smaller image) |
422 | Business rule / plan limit on create | upgrade_required | No until upgrade or free capacity |
422 | Project's included revision rounds are used up | revision_over_budget | No until an override or upgrade |
422 | Deliverable or client is onboarding sample data | sample_data_never_emailed | No |
422 | Client has no email address on file | client_email_missing | No (add an address) |
429 | Rate limit exceeded | rate_limit_exceeded | Yes after window |
500 | Unexpected server error | — | Cautious retry |
502 | Upstream storage failure on image upload | storage_unavailable | Retry with backoff, same key |
502 | Review email could not be sent | email_send_failed | Retry with the same key |
Canonical bodies
401 Unauthorized
{ "error": "Invalid or missing API token" }
403 Forbidden (capability)
{
"error": "API access is a paid feature",
"code": "upgrade_required",
"capability": "api_access"
}
400 Bad Request (examples)
{ "error": "title is required (1-200 chars)" }
{ "error": "client_id is required" }
{ "error": "client_id not found in this workspace" }
{ "error": "Provide embed_url or image_base64" }
{ "error": "embed_url must be an https URL" }
422 Unprocessable Entity (deliverable cap)
{
"error": "Deliverable limit reached for your plan. Upgrade to create more.",
"code": "upgrade_required",
"resource": "deliverables"
}
(Exact error prose may vary; rely on code + resource for branching.)
429 Too Many Requests
{
"error": "Rate limit exceeded. Please wait a few minutes and try again.",
"code": "rate_limit_exceeded",
"retry_after_seconds": 60
}
405 Method Not Allowed
{ "error": "Method not allowed" }
Client handling guidance
- Parse JSON even on error responses.
- Branch on
codebefore parsing human-readableerrortext. - For
upgrade_required+capability: api_access, send users to Settings → Billing / Pricing. Every paid tier carriesapi_accesssince migration20261112000600, so this now means "you are on a free or cancelled plan", not "you need Agency". Tiers differ by rate ceiling, not by access. - For
insufficient_scope, the token is valid but was minted without the scope inrequired_scope. Scopes cannot be added to an existing token — mint a new one. - For
api_actor_unavailable(409), the admin who created the token is no longer an admin of the workspace, so writes cannot be attributed. Mint a new token. A credential must not outlive the authority it was granted under. - For
upgrade_required+resource: deliverables, send users to upgrade or archive unused deliverables. - For
idempotency_key_reused(409), the key was already spent on a different request. The first result is deliberately not returned, because answering with it would report success for work the caller never asked for. Generate a fresh key per logical operation. - For
deliverable_signed_off(409), stop offering edits. A signed-off deliverable refuses every modification, not just a state change — create a new deliverable if the work has genuinely moved on. - For
revision_over_budget(422), the project's included revision rounds are spent. This is a commercial conversation, not a retry: surface it as "this revision is outside the agreed rounds" and let the user record an override in the app or bill for it. - For
payload_too_large(413), re-export smaller — a lower scale factor or JPEG instead of PNG. The cap is on the decoded bytes, so base64 inflation is not the problem. - For
storage_unavailable(502), nothing was created and any partial object was removed. Retry with the sameIdempotency-Key. - For
email_send_failed(502), nothing was sent and the idempotency claim was released. Retry with the same key; the API will not have double-emailed the client. - For
sample_data_never_emailed/client_email_missing(422), these are data problems with a clear user action — they are never fixed by retrying. - For
429, wait at leastretry_after_secondswith jitter before retrying. Read the value; it is measured, not a fixed 60. - Never retry
401/403/400blindly in a tight loop.
A 200 that still needs reading
POST /api-v1/deliverables/{id}/send can return ok: true with a non-null
status_warning. That means the client was emailed but the deliverable's status
could not be advanced. Do not treat it as a failure and do not re-send: surface the
warning and move on.
RPC errors (Settings UI)
When minting tokens or registering webhooks via Supabase RPC:
| Exception message | Hint | Meaning |
|---|---|---|
API access is a paid feature | upgrade_required:api_access | Plan lacks capability |
Only organization admins can create API tokens | — | Caller is not admin |
Only organization admins can add webhooks | — | Caller is not admin |
These map to the frontend helper isCapabilityUpgradeError(..., 'api_access').
More reference
- Authentication — How tokens are minted, stored, presented and revoked.
- Endpoints — Every request and response shape, with worked examples.
- Outbound webhooks — Signed HTTPS POSTs when work is signed off, a change order moves, or an invoice is paid — plus how to verify one and how to survive a duplicate.
- Rate limits — Per organization, per minute, and published rather than discovered as a random failure.
- Compatibility and versioning — What may change without notice, what may not, and how you are told.
- API changelog — Every change, dated, including the ones that were corrections.
OpenAPI specification · API access is on every paid plan · Start your 14-day trial