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

# Rate limits

> Quotas, headers, and how to back off cleanly.

The Casebyte API enforces a per-key rate limit to keep the service responsive
for everyone.

## The limit

| Limit | Window | Scope |
| - | - | - |
| **10 requests** | **60 seconds** | Per API key |

Your first request after a 60-second gap starts a fresh window, and you have
10 requests to spend before it resets.

If you have multiple API keys, each one gets its own independent budget.
If multiple processes share a single key (e.g. a worker pool), they share the
budget.

## Response headers

Every successful response (`200`) and rate-limit response (`429`) includes:

| Header | Meaning |
| - | - |
| `X-RateLimit-Limit` | Your per-window quota (always `10` today). |
| `X-RateLimit-Remaining` | Requests remaining in the current window. |

You don't need to track windows yourself — just read `X-RateLimit-Remaining`
and pace your requests when it gets low.

> [!NOTE]
>
> We deliberately don't return `Retry-After` or a reset timestamp on `429`
> responses. Use exponential backoff with jitter instead — it's friendlier to
> the service and to your own latency budget than retrying at the exact
> moment the window rolls.

## When you exceed the limit

Over-quota requests get `429 Too Many Requests` with this body:

```json theme={null}
{
  "error": {
    "code": "rate_limited",
    "message": "Rate limit exceeded."
  }
}
```

## A well-behaved client

Back off on `429` with exponential delay and jitter:

```typescript theme={null}
async function fetchWithBackoff(url: string, init: RequestInit): Promise<Response> {
  for (let attempt = 0; attempt < 6; attempt++) {
    const res = await fetch(url, init);
    if (res.status !== 429) return res;
    const baseMs = Math.min(60_000, 1_000 * 2 ** attempt);
    const jitterMs = Math.random() * baseMs;
    await new Promise((r) => setTimeout(r, baseMs + jitterMs));
  }
  throw new Error("rate-limited after 6 attempts");
}
```

For batch workloads, use the `X-RateLimit-Remaining` header proactively
instead of waiting to be 429'd:

```typescript theme={null}
async function callAndPace(url: string, init: RequestInit) {
  const res = await fetch(url, init);
  const remaining = parseInt(res.headers.get("X-RateLimit-Remaining") ?? "10", 10);

  if (remaining === 0) {
    // Window is at most 60s long. Wait it out (plus a little jitter) before
    // the next call to avoid a guaranteed 429.
    await new Promise((r) => setTimeout(r, 60_000 + Math.random() * 1_000));
  }
  return res;
}
```

## Tips for staying under the limit

* **Prefer larger pages.** A single search with `limit=100` costs the same
  as one with `limit=10`.
* **Cache aggressively.** Case content (the response from `GET /v1/cases/{id}`)
  is effectively immutable once a judgment is published. Cache it locally for
  hours or days.
* **Parallelise across keys, not across requests.** If you need genuine
  parallelism, issue one API key per worker.
* **Don't poll `/cited-by`.** It changes only when new judgments are
  published. Refresh once a day at most.

## Need a higher limit?

If 10 req/min is too tight for your workload, [get in touch](mailto:support@casebyte.ai).
We can raise the per-key limit, issue multiple keys, or set up a bulk export
for one-off backfills.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.