# Errors

Every error, from any endpoint, uses one envelope with a stable, enumerable code:

```json
{
  "error": {
    "code": "insufficient_balance",
    "message": "Top up your wallet to run this call.",
    "request_id": "req_1f2e...",
    "details": {"optional": "structured context"}
  }
}
```

Branch on `code`, show `message` to humans, and include `request_id` when writing to [hello@jell.run](mailto:hello@jell.run).

## Codes

| Code | HTTP | Meaning | Retry? |
| --- | --- | --- | --- |
| `invalid_request` | 400 | malformed body, unknown field or bad value | fix the request |
| `invalid_api_key` | 401 | missing, malformed or revoked key | no |
| `invalid_credentials` | 401 | bad dashboard sign-in | no |
| `forbidden` | 403 | the key cannot do this (for example: sandbox-only routes on production) | no |
| `capability_not_found` | 404 | unknown capability slug | no; [discover](/docs/api/discover) first |
| `endpoint_not_found` | 404 | unknown [raw endpoint](/docs/api/endpoints) for that provider | no; list `/v1/catalog/providers/{provider}/endpoints` |
| `no_match` | 404 | providers answered authoritatively: no result | not with the same input |
| `insufficient_balance` | 402 | reservation exceeds available balance | after a [top-up](/docs/api/wallet) |
| `cost_limit_exceeded` | 402 | the run's hold exceeds the workspace's per-run ceiling; the run is `blocked` | lower the call or raise the ceiling at `/v1/spend-guard` |
| `budget_exceeded` | 402 | the daily spend guard would be breached; the run is `blocked` | after the UTC reset, or raise the cap |
| `duplicate_request` | 409 | conflicting concurrent request | no |
| `not_found` | 404 | no such route | no |
| `payload_too_large` | 413 | request body above 1 MB | with a smaller body |
| `provider_not_available` | 409 | no live provider for this capability yet, or a provider not exposed raw | later |
| `rate_limited` | 429 | too many requests from your client (counted per key, with a wider cap shared by every key on one IP; per IP for calls without a key, and tighter on sign-in, code requests and OAuth registration); `Retry-After` says when | after `Retry-After` |
| `provider_rate_limited` | 429 | the upstream provider throttled us | with backoff |
| `provider_error` | 502 | the upstream provider failed; nothing charged | yes, with an `Idempotency-Key` |
| `provider_timeout` | 504 | the upstream provider timed out; nothing charged | yes, with an `Idempotency-Key` |
| `internal_error` | 500 | our fault; nothing charged without settlement | yes, with an `Idempotency-Key` |

## Retrying safely

Retry `provider_error`, `provider_timeout`, `internal_error` and both rate limits with exponential backoff, always resending the same `Idempotency-Key`: a retry that races a completed run gets the original result back (`Idempotent-Replay: true`) instead of a second charge.

On [auto routing](/docs/api/run), most provider failures never reach you: a rate-limited or 5xx provider is retried once after a short wait, and the waterfall tried the next-best provider before returning an error. When `max_cost` kept a fallback from running, the error message names it and the cap that admits it: raise the cap once rather than resubmitting the same call.
