Skip to content

Error reference

Every problem+json type we emit — and the four failures that are not problem+json.

How to read an error

Every failure the API decides on comes back as application/problem+json (RFC 9457) with a type URI that links to the section below, a code, a title and a detail. Branch on code, never on the wording.

We never put an identifier in the body. A 404 is deliberately indistinguishable from “not yours” — telling you which would let a key enumerate other customers' decks.

Unauthorized

401 · unauthorized · do not retry

The credential is missing, malformed, expired, or was revoked. For MCP this includes an access token that died mid-conversation — tokens last 15 minutes and are re-checked on every call.

What to do: Mint a new key in Settings → API & MCP, or sign in again in your MCP client. Never retry the same credential in a loop: a 401 is a verdict, not a hiccup.

Forbidden

403 · forbidden · do not retry

The credential is valid but this account is not on the access list for the public API, or the key belongs to an organisation that has switched org-key spend off.

What to do: Ask us to add your account. Access is an explicit allow-list while the surface is in preview — nothing about your key is wrong.

Not found

404 · not_found · do not retry

There is no such resource, or it is not visible to this credential. The two are deliberately indistinguishable: a 403 would confirm that an id you guessed belongs to somebody else.

What to do: Check the taskId. If you are using an organisation key, confirm the deck was created by that organisation.

Insufficient credits

402 · insufficient_credits · do not retry

The wallet funding this call does not hold the credits the request reserves.

What to do: The body carries `options` — an ordered list of what you can do next (top up, subscribe, unlock this deck). Present them; do not guess a price.

Price changed since you quoted

409 · price_drift · retryable

You sent `expected_credits` and the live price no longer matches — typically an admin retune between your quote and your submit.

What to do: Re-quote and resubmit with the new number. Never strip `expected_credits` to make the 409 go away; it is the only thing standing between a retune and an unexpected charge.

Conflict

409 · conflict · do not retry

The request collides with the current state — most often an idempotency key you already used with a different body.

What to do: Use a fresh idempotency key for a genuinely new request, and the same one only for a true retry of the identical call.

Validation failed

400 · validation_failed · do not retry

A field is missing or outside its allowed set.

What to do: Read `detail`. Note that FastAPI's own 422 body is NOT problem+json — see “Four failures that are not problem+json” below.

Payload too large

413 · payload_too_large · do not retry

The request body exceeded the cap for that path.

What to do: Upload materials through the materials endpoint, which carries the large-body cap; other paths do not.

Too many requests

429 · rate_limited · retryable

You crossed either the per-IP edge bucket or your per-credential bucket.

What to do: Honour `Retry-After` and the `X-RateLimit-*` headers. They describe the per-credential bucket, which is the tighter of the two.

Public API disabled

503 · surface_disabled · retryable

The public surface kill switch is off. This affects reads too.

What to do: Retry with backoff. This is an operator action, not a problem with your request; it is announced on the changelog feed when planned.

Temporarily unavailable

503 · service_unavailable · retryable

A dependency we need — the database, or the credential verifier — did not answer.

What to do: Retry with backoff. A 503 is never a verdict on your credential; do not treat it as a sign-in failure.

Internal error

500 · internal_error · retryable

We broke. It is on our side.

What to do: Retry once, then tell us. Include the time and the endpoint — never your key.

FailureWhat arrives instead
422 from request validation`{"detail": [{"loc": …, "msg": …}]}` — FastAPI's own shape.
503 when the database is unreachableA plain JSON body from the shared handler, not problem+json.
429 raised before authenticationThe edge bucket answers before any `/v1` code runs; treat any 429 as rate limiting regardless of body.
413 from the body-size middlewarePure-ASGI and outside the sub-app, so it cannot be reshaped by a handler.