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

# Rate Limits

> How the Management API rate-limits per API key, the response headers it returns, and how to back off correctly on a 429.

# Rate Limits

Requests to the KnoxCall Management API (`/v1`) are rate-limited **per API key**. The limit is
enforced consistently across every API worker, so a burst spread across parallel connections
counts against the same budget — there is no per-process loophole.

<Note>
  This page covers the **Management API** (`api.knoxcall.com/v1`) — the control plane you use
  to configure routes, secrets, clients, and so on. Rate limits on your **proxy traffic**
  (the routes you expose to your own callers) are configured separately, per route / per
  client / per tenant — see [Rate Limiting overview](/essentials/rate-limiting/overview).
</Note>

## Rate-limit headers

When a limit is configured for a request, the response includes these headers — on **every**
response, not only on a `429`, so you can watch your remaining budget as you go:

| Header                  | Type    | Description                                                                               |
| ----------------------- | ------- | ----------------------------------------------------------------------------------------- |
| `X-RateLimit-Limit`     | integer | Maximum requests allowed in the current window                                            |
| `X-RateLimit-Remaining` | integer | Requests remaining in the current window                                                  |
| `X-RateLimit-Reset`     | integer | Epoch seconds (UTC) at which the current window resets and `Remaining` returns to `Limit` |
| `Retry-After`           | integer | Seconds to wait before retrying — sent **only** on a `429` response                       |

A normal, well-under-budget response looks like this:

```http theme={"dark"}
HTTP/1.1 200 OK
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 597
X-RateLimit-Reset: 1754390400
X-Request-Id: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json
```

<Tip>
  `X-RateLimit-Reset` is an **epoch-seconds** timestamp. Convert it to a delay with
  `reset - now()` (in seconds) if you want to sleep until the window rolls over rather than
  retrying blindly.
</Tip>

## The 429 response

When you exceed the limit, the request is rejected with HTTP `429` and the canonical
[error envelope](/api-reference/errors) using the `rate_limit_exceeded` type. The response
also carries a `Retry-After` header telling you exactly how long to wait:

```http theme={"dark"}
HTTP/1.1 429 Too Many Requests
Retry-After: 4
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1754390400
X-Request-Id: 7b0f2c9a-1d3e-4a5b-9c8d-2e1f0a3b4c5d
Content-Type: application/json

{
  "error": {
    "type": "rate_limit_exceeded",
    "message": "Rate limit exceeded. Retry after 4 seconds.",
    "request_id": "7b0f2c9a-1d3e-4a5b-9c8d-2e1f0a3b4c5d"
  }
}
```

<Warning>
  Continuing to send requests while rate-limited does **not** reset the window — it just
  burns effort. Wait for `Retry-After` seconds (or until `X-RateLimit-Reset`) before your
  next attempt.
</Warning>

## Backoff guidance

1. **Respect `Retry-After` first.** On a `429` it is the authoritative wait — sleep that many
   seconds before retrying.
2. **Watch `X-RateLimit-Remaining` proactively.** If it is trending toward `0`, slow down
   before you get a `429` rather than after.
3. **Add jitter.** When several workers hit the limit together, a fixed `Retry-After` makes
   them all retry in lockstep. Add a small random offset (e.g. `Retry-After + random(0, 1s)`)
   to spread the retries.
4. **Cap your retries.** Use exponential backoff with a ceiling and a maximum attempt count;
   surface a clear error to the caller rather than retrying forever.

<CodeGroup>
  ```typescript Node.js theme={"dark"}
  async function withRetry(fn, maxAttempts = 5) {
    for (let attempt = 1; ; attempt++) {
      const resp = await fn();
      if (resp.status !== 429) return resp;
      if (attempt >= maxAttempts) return resp; // give up, let the caller handle it

      const retryAfter = Number(resp.headers.get("Retry-After") ?? 1);
      const jitter = Math.random(); // 0–1s
      await new Promise((r) => setTimeout(r, (retryAfter + jitter) * 1000));
    }
  }
  ```

  ```python Python theme={"dark"}
  import random, time

  def with_retry(request, max_attempts=5):
      for attempt in range(1, max_attempts + 1):
          resp = request()
          if resp.status_code != 429:
              return resp
          if attempt == max_attempts:
              return resp  # give up, let the caller handle it

          retry_after = int(resp.headers.get("Retry-After", "1"))
          time.sleep(retry_after + random.random())  # + jitter
  ```
</CodeGroup>

<Tip>
  The first-party [SDKs](/sdks/overview) implement `Retry-After`-aware backoff with jitter for
  you and raise a typed `RateLimitError` once retries are exhausted — you rarely need to hand-
  roll the loop above.
</Tip>

## What's Next?

<CardGroup cols={2}>
  <Card title="Errors" icon="triangle-alert" href="/api-reference/errors">
    The canonical error envelope and the full type → status table.
  </Card>

  <Card title="Idempotency" icon="repeat" href="/api-reference/idempotency">
    Retry mutating requests safely without creating duplicates.
  </Card>

  <Card title="Proxy rate limiting" icon="gauge" href="/essentials/rate-limiting/overview">
    Per-route / per-client / per-tenant limits on your own proxy traffic.
  </Card>

  <Card title="API Overview" icon="book" href="/api-reference/overview">
    Base URLs, response envelope, pagination, and quick examples.
  </Card>
</CardGroup>
