# Errors

> Every code you can receive, what it means, and whether retrying helps.

Two rules make the rest of this page readable.

**Every error carries a code.** The HTTP status says what kind of problem it is;
the code says which one. Branch on the code — statuses are shared, codes are not.

```json
{
  "error": "QUOTA_EXCEEDED",
  "detail": "this organisation does not have enough credits for that call",
  "context": { "balance": "0", "needed": "1", "limit": "20", "used": "20", "plan": "free" },
  "request_id": "01J…"
}
```

| Field | Meaning |
|---|---|
| `error` | The code. Stable — safe to branch on |
| `detail` | Plain language. **Not** stable; do not match on it |
| `context` | Whatever is useful for this code. Shape varies |
| `request_id` | Quote this when you ask us about a call |

**A refusal is not an error.** An oracle that ran and refused answers
`200` with `"status": "invalid"`. See [Verdicts](https://docs.formalogic.io/verdicts).

## Codes

| Status | Code | Meaning | Retry? |
|---|---|---|---|
| 401 | `KEY_MISSING` | No bearer token was sent | After fixing the header |
| 401 | `KEY_UNKNOWN` | The token is not recognised | No — check for a truncated paste |
| 401 | `KEY_REVOKED` | The key was revoked | Never. Mint a new one |
| 401 | `KEY_EXPIRED` | The key passed its expiry | Never. Mint a new one |
| 403 | `ORG_DISABLED` | The key is fine; its organisation is not active | No. Keep the key and contact support |
| 400 | `SIGNATURE_INVALID` | A figure token is malformed, altered or expired | No — draw from a fresh answer |
| 402 | `QUOTA_EXCEEDED` | The allowance is spent | When the window turns, or on a bigger plan |
| 404 | `NOT_FOUND` | No such oracle, or no organisation for this key | No — read `GET /v1/oracles` |
| 422 | `VALIDATION_FAILED` | The request is malformed, or the format is one this oracle cannot produce | After correcting it. `context.available` lists the formats |
| 422 | `DATA_REQUIRED` | A chart request does not give the values to draw | After putting them in the intent, or with `allow_generated_data` |
| 500 | `INTERNAL` | Something failed that we did not anticipate | Yes, once. Then send us the `request_id` |
| 501 | `UNSUPPORTED` | Nothing draws this figure's target | No |
| 503 | `UPSTREAM_UNAVAILABLE` | A service the call depends on is briefly unavailable | Yes, after a short wait |
| 200 | — | `"status": "error"` — our fault | Yes. Nothing was billed |

That is the whole list. A request that fails validation names the field in
`context.parameter`. Each endpoint's page lists exactly which of these it can
return.

## The distinctions worth honouring

**`402` is not `401`.** Your credential is fine; the allowance is spent. A client
that treats a spent quota as an auth failure re-prompts for a key that was never
the problem — often to a human, in the middle of the night.

**`ORG_DISABLED` is `403`, not `401`.** The key is fine; the organisation behind
it is not active. A client that throws the key away and asks for a new one gets
the same answer with the new one. Keep it, and tell a person.

**`KEY_REVOKED` and `KEY_EXPIRED` are permanent.** No amount of backoff will
help. A client that retries these forever is a client that never tells anyone it
is broken.

**A `200` can still be a failure.** `"status": "error"` is ours, and unbilled.
Branch on `status`, not only on the status line.

See [Handle every verdict](https://docs.formalogic.io/handling-verdicts) for the client code.
