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

# Rate Limits

> How many requests you can make, and how to back off cleanly.

You get **100 requests per minute** on a sliding window. The limit is **per user**, shared across every key and every endpoint.

Batch read endpoints (e.g. `POST /api/v1/traders/batch`, `POST /api/v1/markets/intel/batch`) get a **separate** budget in *item units*, also 100 per minute. A 25-item batch reserves 25 item units before it runs.

`GET /api/v1/usage` does not spend primary quota or log into daily usage. It has its own 100 reads/minute per-user guard so checking quota can't itself blow up usage-count load.

| Setting                     | Value                                                        |
| --------------------------- | ------------------------------------------------------------ |
| Requests per minute         | 100                                                          |
| Usage reads per minute      | 100                                                          |
| Batch item units per minute | 100                                                          |
| Window type                 | Sliding (not bucket-of-60-seconds)                           |
| Scope                       | Per user (sum of all keys)                                   |
| Header on success           | `X-RateLimit-*` (+ `X-Batch-RateLimit-*` on batch endpoints) |
| Header on `429`             | `Retry-After`                                                |

## Headers you should read

Every authenticated response carries:

```
X-RateLimit-Limit:     100
X-RateLimit-Remaining: 87
X-RateLimit-Reset:     1774450631
X-Request-Id:          req_550e8400
```

| Header                  | Meaning                                                       |
| ----------------------- | ------------------------------------------------------------- |
| `X-RateLimit-Limit`     | Max requests per minute.                                      |
| `X-RateLimit-Remaining` | Remaining slots in the current window.                        |
| `X-RateLimit-Reset`     | Unix timestamp (seconds) when the window resets.              |
| `X-Request-Id`          | Request identifier to include when reporting a support issue. |

Batch responses additionally carry:

| Header                        | Meaning                                                   |
| ----------------------------- | --------------------------------------------------------- |
| `X-Request-Cost`              | Item units this single call charged (one per batch item). |
| `X-Batch-RateLimit-Limit`     | Max item units per minute.                                |
| `X-Batch-RateLimit-Remaining` | Item units remaining in the window.                       |
| `X-Batch-RateLimit-Reset`     | Unix timestamp when the batch window resets.              |

Browser clients can read these headers on public REST `/api/v1/*` responses
because the public API exposes them through CORS. The public API also exposes
`Retry-After`, `ETag`, `X-Request-Id`, `X-Request-Cost`, `Mcp-Session-Id`, and
`X-Mcp-Error-Code`. Remote MCP at `/api/v1/mcp` still validates `Origin`
against the 0xinsider/localhost allowlist. Do not use credentialed CORS for
public API calls; send only the Bearer token header.

If you go over either budget, you get a `429` plus a `Retry-After` header in seconds:

```
HTTP/1.1 429 Too Many Requests
Retry-After: 12
```

```json theme={null}
{
  "object": "error",
  "error": {
    "code": "rate_limited",
    "message": "Rate limit exceeded. Retry after 12s.",
    "doc_url": "https://docs.0xinsider.com/rate-limits"
  }
}
```

## A client that respects the retry

<CodeGroup>
  ```javascript JavaScript theme={null}
  async function fetchWithBackoff(url, opts = {}, maxRetries = 5) {
    for (let attempt = 0; attempt < maxRetries; attempt++) {
      const res = await fetch(url, opts);
      if (res.status !== 429) return res;

      const wait = Number(res.headers.get("retry-after") ?? 1) * 1000;
      await new Promise(r => setTimeout(r, wait));
    }
    throw new Error("Rate limit retries exhausted");
  }

  const res = await fetchWithBackoff(
    "https://api.0xinsider.com/api/v1/whale-trades?limit=50",
    { headers: { Authorization: `Bearer ${process.env.OXINSIDER_API_KEY}` } }
  );
  const body = await res.json();
  ```

  ```python Python theme={null}
  import os, time, requests

  def get_with_backoff(url, params=None, max_retries=5):
      headers = {"Authorization": f"Bearer {os.environ['OXINSIDER_API_KEY']}"}
      for _ in range(max_retries):
          r = requests.get(url, params=params, headers=headers)
          if r.status_code != 429:
              return r
          time.sleep(int(r.headers.get("Retry-After", "1")))
      raise RuntimeError("Rate limit retries exhausted")

  body = get_with_backoff(
      "https://api.0xinsider.com/api/v1/whale-trades",
      params={"limit": 50},
  ).json()
  ```
</CodeGroup>

## Staying well under the limit

* **Cache.** Most of this data updates every 30-120 seconds. Re-fetching every second wastes budget.
* **Paginate, don't re-list.** Use cursors instead of refetching the full leaderboard each loop.
* **Ask for more per request.** Bump `limit` to 100 instead of making 10 calls of `limit=10`.
* **Batch when you know your IDs.** One batch call for 25 traders costs 25 batch-item units but only one regular request.
* **Watch `X-RateLimit-Remaining`.** Below 10, pause polling until the next reset.
* **Use `/usage` for budget checks.** `GET /api/v1/usage` reads the current primary limiter window without incrementing it. Respect its own 100 reads/minute cap; don't probe quota with another endpoint.
* **One process owns the polling.** Don't have every container in a fleet poll the same endpoint with the same key.

## Conditional GETs

Deterministic read endpoints return an `ETag` header. Send it back as `If-None-Match` to get `304 Not Modified` with an empty body when nothing changed.

This covers trader, positions, whale-trades, whale-trades/history, leaderboard, markets/explore, market intel, market snapshot, insider-radar, position-timeline, and health responses. Report snapshots aren't conditional yet because their body includes a request-time `generated_at`.
