> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ninjachat.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Rate limits

> Limits per endpoint, response headers, and retry patterns.

## Limits

Limits apply over rolling 60-second windows, both **per key** and **per account**. All keys share the account limit.

| Layer                                         | Per key                                      | Per account | Applies to                                                                                                                                       |
| --------------------------------------------- | -------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| Requests per minute — chat, responses, search | 60                                           | 120         | `POST /responses`, `POST /chat/completions`, `POST /search`. `POST /batch` and `POST /compare` draw on the same pool, one unit per job or model. |
| Requests per minute — images                  | 30                                           | 60          | `POST /images/generations`                                                                                                                       |
| Requests per minute — video                   | 2                                            | 4           | `POST /videos`                                                                                                                                   |
| Tokens per minute                             | 1,500,000                                    | 3,000,000   | Chat is weighed by its input estimate plus output ceiling; each image counts 100,000, each video 1,000,000, each search 20,000                   |
| Concurrent in-flight requests                 | 20                                           | 40          | Every billed route. A slot is held for the whole request, including a streaming body                                                             |
| Server-side fallback attempts                 | 120 per minute                               | —           | Silent model and provider fallbacks inside one request                                                                                           |
| Pre-auth flood cutoff                         | 600 per minute per key **and** per client IP | —           | Checked before authentication; only sheds obvious floods                                                                                         |

RPM windows are kept per workload group (chat, images, video, search), so image traffic never consumes chat's slice. Per-key token and concurrency budgets are shared across every endpoint.

Not admission-limited: `GET /models`, `GET /pricing`, `GET /videos/{id}` (poll as often as you like), `GET /balance`, `GET /usage`, `GET /health`. `POST /estimate` has its own limit of 30 per minute per IP.

## Response headers

Included on every successful response from a billed route, and on admission `429`s. The `401` (missing/invalid key) and zero-balance `402` checks both run *before* the rate limiter, so those two error responses don't carry them.

| Header                  | What it tells you                                                                                 |
| ----------------------- | ------------------------------------------------------------------------------------------------- |
| `X-RateLimit-Limit`     | Your per-key requests-per-minute ceiling for this endpoint group                                  |
| `X-RateLimit-Remaining` | How many you have left in the current window                                                      |
| `X-RateLimit-Reset`     | Seconds until the window resets                                                                   |
| `Retry-After`           | Seconds to wait — on `429`, and also on `409 request_in_flight` and `503 idempotency_unavailable` |
| `X-Idempotent-Replay`   | `true` when a stored response was replayed for a reused `Idempotency-Key`                         |

## When you hit a limit

You get a `429` whose body names the layer you hit:

```json theme={null}
{
  "error": {
    "message": "Rate limit exceeded. Try again in 12 seconds.",
    "type": "rate_limit_error",
    "code": "rate_limit_exceeded",
    "param": null
  },
  "message": "Rate limit exceeded. Try again in 12 seconds.",
  "code": "rate_limit_exceeded",
  "request_id": "req_abc123",
  "retry_after": 12,
  "limit": "key_rpm",
  "scope": "key"
}
```

`limit` is one of `key_rpm`, `account_rpm`, `key_tpm`, `account_tpm`, `key_concurrency`, or `account_concurrency`; `scope` is `key` or `account`. Concurrency denials suggest a 2-second `retry_after`; window denials report the time until the window rolls over. Batch and compare add `units` (the fan-out size that was counted).

The pre-auth flood cutoff answers earlier and more tersely — `"Too many requests. Try again in N seconds."` with `retry_after` but no `request_id`, `limit`, or `scope` — because it runs before your key is looked up.

## Retry code

The SDK is the shortest safe implementation: it honors `Retry-After`, uses exponential backoff, retries `429` and `5xx` up to `maxRetries` (default `2`), and only retries billed requests when an idempotency key makes replay safe.

<CodeGroup>
  ```typescript TypeScript SDK theme={null}
  const client = new NinjaChat({
    apiKey: process.env.NINJACHAT_API_KEY!,
    maxRetries: 3,
    timeoutMs: 120_000,
  });

  const response = await client.responses.create({
    model: "gpt-5.6-luna",
    input: "Classify this support ticket.",
  });
  ```

  ```python Python SDK theme={null}
  client = NinjaChat(
      api_key=os.environ["NINJACHAT_API_KEY"],
      max_retries=3,
      timeout=120,
  )

  response = client.responses.create(
      model="gpt-5.6-luna",
      input="Classify this support ticket.",
  )
  ```
</CodeGroup>

## Tips

* **Bound concurrency** — use a worker pool of at most 20 per key instead of unbounded `Promise.all`
* **Video polling doesn't count** — poll as often as you want (every 10s recommended); video *submissions* are limited to 2 per minute per key
* **Honor `Retry-After`** — it is more accurate than a fixed sleep
* **Reuse idempotency keys** — retries of billed operations must not create duplicate work
