# Errors

> The typed error envelope and the closed code registry — what to retry, what not to


Every error response, on every `/v1` endpoint:

```json
{"error": {"type": "billing_error", "code": "insufficient_credits",
           "message": "Not enough credits for a standard run ($10).",
           "param": null, "retriable": false,
           "doc_url": "/developers/errors#insufficient_credits",
           "request_id": "req_a1b2c3…"}}
```

`retriable` answers the only question that matters mid-loop: **can an identical
retry succeed?** `request_id` is on every response (header `X-Request-Id` too) —
include it in support requests.

## Code registry (closed)

| Code | HTTP | Retriable | Meaning / correct response |
|---|---|---|---|
| `invalid_request` | 400 | no | malformed input — fix the request |
| `validation_failed` | 422 | no | body failed validation; `error.errors` lists fields |
| `authentication_required` / `invalid_api_key` | 401 | no | missing/bad key |
| `key_revoked` / `key_expired` | 401 | no | a key we recognise that no longer works — mint or rotate a new one at `/settings/api-keys` |
| `key_suspended` / `account_suspended` | 403 | no | the abuse guard paused this key / account (a runaway loop, off-purpose prompts). **Stop and alert a human; do not mint a new key** — a fresh key of a suspended user is refused too. Re-enable from `/settings/api-keys` or your support contact |
| `insufficient_role` / `insufficient_scope` | 403 | no | key lacks the scope — mint one that has it |
| `workspace_forbidden` | 403 | no | key can't act in that workspace |
| `not_found` | 404 | no | unknown resource/entity |
| `conflict` | 409 | no | state conflict (e.g. rotating a revoked key) |
| `no_active_blueprint` | 409 | no | the workspace holds no Blueprint yet — design one at `/onboarding` or with `POST /v1/blueprints/drafts`. Returned by every endpoint that reads the workspace's schema: export, reports, report columns, and run listing |
| `idempotency_key_in_flight` | 409 | yes | original still running — wait `Retry-After` |
| `idempotency_key_reused` | 422 | no | same key, different payload — new key or same body |
| `rate_limited` | 429 | yes | back off per `Retry-After` |
| `llm_daily_limit_exceeded` | 429 | yes | the per-day cap on the free model surfaces (parsers, onboarding, chat) — a rate limit, **not** a billing wall: topping up does not lift it; retry after the window or ask support to raise it |
| `run_concurrency_exceeded` | 429 | yes | your org's own run caps (set by your admin) — queue and retry |
| `insufficient_credits` | 402 | no | balance too low — a human must top up |
| `plan_upgrade_required` | 402 | no | the plan doesn't include this capability — **topping up won't help**; a human must upgrade |
| `budget_exceeded` | 402 | no | this key's budget ceiling — a human must raise it |
| `payment_required` | 402 | no | subscription required for this surface |
| `authorization_pending` | 400 | yes | CLI sign-in not approved yet — keep polling at `interval` |
| `slow_down` | 400 | yes | polling faster than `interval` — increase it, then continue |
| `expired_token` | 400 | no | the device code expired or was already used — run `deepsieve login` again |
| `access_denied` | 403 | no | the human refused the CLI sign-in |
| `internal_error` | 500 | yes | our fault; retry with backoff |
| `service_unavailable` | 503 | yes | transient; retry with backoff |

The four device codes belong to the CLI sign-in flow ([CLI](/developers/cli)).
`authorization_pending` and `slow_down` are **not failures** — they are the
normal path while a human approves in their browser, which is why both are
`retriable: true`.

Additions to this registry are announced in the changelog; codes are never
removed or renamed within v1.
