# Errors & limits

> The error envelope, rate limits, and credits.


## Error envelope

OpenAI-exact, on every error:

```json
{"error": {"message": "…", "type": "…", "param": null, "code": "…"}}
```

| Status | When | `type` | `code` |
|---|---|---|---|
| 400 | malformed body, `n>1`, `tools`, unsupported content parts | `invalid_request_error` | — |
| 401 | missing / bad / revoked key | `invalid_request_error` | `invalid_api_key` |
| 403 | key lacks the scope for a visible resource; plan without API access | `permission_error` | — |
| 404 | unknown model, or resource outside your key's scope (never distinguished) | `invalid_request_error` | `model_not_found` / — |
| 409 | `Idempotency-Key` reused with a different body; session mid-turn | `invalid_request_error` | `idempotency_key_reused` / `session_busy` |
| 429 | **credits exhausted** | `insufficient_quota` | `insufficient_quota` |
| 429 | request rate / concurrency | `rate_limit_error` | `rate_limit_exceeded` |
| 500 | agent turn failed; schema-invalid structured output | `server_error` | — |
| 503 | temporary capacity (spawn timeout) | `server_error` | — |

Official OpenAI SDKs map by HTTP status. The two 429s are distinguished by `code`: `insufficient_quota` means **buy credits or raise your plan**, not retry.

## Rate limits

Per-**account** request limits by plan (concurrency = simultaneous in-flight turns). All your keys share one budget — creating more keys does not raise it.

| Plan | RPM | Concurrent turns |
|---|---|---|
| Talmid (free) | no API access | — |
| Chaver | 20 | 2 |
| Scholar | 60 | 4 |
| Max | 300 | 8 |

Every response carries `x-ratelimit-limit-requests`, `x-ratelimit-remaining-requests`, `x-ratelimit-reset-requests` (duration format, e.g. `6m0s`), and `retry-after` (seconds) on a 429. Token-based limits are not metered in v1 — credits are the real budget. File endpoints are limited separately from completions.

## Credits

Every turn charges RavChat credits from your account's pools (plan allowance + purchased packs). `usage.cost` on the response is the charge for that turn — non-streaming responses also carry it in the `x-ravchat-credits` header; streams deliver it in the final chunk. When a spending bucket is exhausted you get 429 `insufficient_quota` **before** the turn runs — never a partial charge.
