# Errors & Limits

All errors use a consistent JSON envelope with a machine-readable `code` and `request_id` for support.

## Error envelope

```json
{
  "error": {
    "code": "insufficient_credits",
    "message": "Insufficient credits",
    "request_id": "uuid"
  }
}
```

## Error codes

| Code | Status | Meaning |
|------|--------|---------|
| unauthorized | 401 | Missing Authorization header |
| invalid_key | 401 | Key does not start with oo_live_ |
| key_not_found | 401 | Revoked or unknown API key |
| org_inactive | 403 | Organization suspended or closed |
| scope_denied | 403 | Key lacks the required scope |
| model_not_allowed | 403 | Model outside your plan or key allowlist |
| insufficient_credits | 402 | Not enough credits for the request |
| rate_limit_exceeded | 429 | Per-key per-minute limit hit — retry after 60s |
| validation_error | 400 | Malformed body or unsupported model for endpoint |
| content_policy | 422 | Prompt blocked by content policy |
| idempotency_conflict | 422 | Idempotency-Key reused with a different payload |
| idempotency_in_progress | 409 | A request with this Idempotency-Key is still running |

Send `Idempotency-Key` (8–256 chars) on POSTs. Replays return the stored response. 402s include `checkout_url`.

Responses include `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`. 429s include `Retry-After`.

## Rate limits

| Tier | Limit |
|------|-------|
| standard | 60 requests / minute / key |
| elevated | 300 requests / minute / key |

Exceeded limits return `429` with `rate_limit_exceeded`.
