# 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@routergrowth.com](mailto:hello@routergrowth.com).

## 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 |
| `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 | no offer fits under `routing.max_cost` | raise the cap or pick another strategy |
| `duplicate_request` | 409 | conflicting concurrent request | no |
| `provider_not_available` | 409 | no live provider for this capability yet | later |
| `rate_limited` | 429 | too many requests from your key | with backoff |
| `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: the waterfall already tried the next-best provider before returning an error.
