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

# Rate Limits

> Per-key rate limiting tiers, headers, and best practices for staying within limits.

# Rate Limits

Rate limits are enforced **per API key** using sliding-window counters with both per-second (RPS) and per-minute (RPM) buckets.

***

## Tiers

| Tier         | Requests/sec | Requests/min | Max WS Connections | Max Batch Size |
| ------------ | :----------: | :----------: | :----------------: | :------------: |
| `free`       |       2      |      120     |          1         |        1       |
| `pro`        |      10      |      600     |          3         |        5       |
| `pro_plus`   |      30      |     1,800    |         10         |       10       |
| `enterprise` |      100     |     6,000    |         50         |       25       |

<Note>
  The `free` tier allows short bursts (up to 2 requests in any one
  second) but is also capped at 120 requests in any rolling one-minute
  window, so the per-minute bucket is the one you'll hit first under
  sustained load. Use `POST /v1/prices/batch` and the WebSocket feeds
  (which don't count against the REST limit) to stay well inside it.
</Note>

<Tip>
  The authoritative source for tier limits is `GET /v1/keys/tiers`. If a doc
  page ever disagrees with that endpoint, the endpoint wins.
</Tip>

***

## Rate Limit Response

When you exceed your limit, the API returns **HTTP 429** with the
Polymarket-shape single-field `error` envelope and a stable
`X-Polysim-Code` response header:

```http theme={null}
HTTP/1.1 429 Too Many Requests
Retry-After: 1
X-Polysim-Code: RATE_LIMIT_EXCEEDED
X-Request-Id: a1b2c3d4-...
Content-Type: application/json
```

```json theme={null}
{"error": "Rate limit exceeded. Retry after 1s."}
```

Branch on `X-Polysim-Code === "RATE_LIMIT_EXCEEDED"` rather than the
body prose, and read the `Retry-After` header for the exact wait time
in seconds.

***

## Rate Limit Headers (on every response)

Every authenticated response carries `x-ratelimit-*` headers so bots
can **pre-throttle** instead of waiting for an actual 429:

| Header                             | Type   | Description                                                                  |
| ---------------------------------- | ------ | ---------------------------------------------------------------------------- |
| `x-ratelimit-tier`                 | string | Tier of the key making the call — `free` / `pro` / `pro_plus` / `enterprise` |
| `x-ratelimit-limit`                | int    | Per-minute cap for the tier                                                  |
| `x-ratelimit-limit-per-second`     | int    | Per-second cap for the tier                                                  |
| `x-ratelimit-remaining`            | int    | Requests remaining in the current rolling-minute window                      |
| `x-ratelimit-remaining-per-second` | int    | Requests remaining in the current rolling-second window                      |
| `x-ratelimit-reset`                | int    | Unix epoch seconds when the per-minute window rolls over                     |

<Note>
  Every header above is **also** emitted under the PolySim-namespaced
  `x-polysim-ratelimit-*` prefix with identical values (e.g.
  `x-polysim-ratelimit-remaining`). Read whichever your SDK or proxy
  keys off — the unprefixed `x-ratelimit-*` form is canonical.
</Note>

```python theme={null}
import time
import requests

# Pre-throttle off the response headers — don't wait for 429.
resp = requests.get(url, headers={"X-API-Key": KEY})
remaining = int(resp.headers.get("x-ratelimit-remaining", "1"))
remaining_sec = int(resp.headers.get("x-ratelimit-remaining-per-second", "1"))
reset_at = int(resp.headers.get("x-ratelimit-reset", "0"))

# If the per-second bucket is almost drained, sleep a beat
if remaining_sec <= 1:
    time.sleep(1.0)
# If the rolling-minute bucket is almost drained, sleep until reset
elif remaining <= 5:
    sleep_for = max(0.0, reset_at - time.time())
    time.sleep(min(sleep_for, 60.0))
```

***

## Handling Rate Limits

When the limiter does fire, exponential backoff keyed off `Retry-After`:

```python theme={null}
import time
import requests

def api_request(url, headers, json_data=None, max_retries=3):
    for attempt in range(max_retries):
        resp = requests.post(url, headers=headers, json=json_data)

        # 429 → respect Retry-After (always populated in seconds)
        if resp.status_code == 429:
            retry_after = int(resp.headers.get("Retry-After", 1))
            time.sleep(retry_after)
            continue

        if resp.status_code >= 500:
            time.sleep(2 ** attempt)  # Exponential backoff
            continue

        return resp

    raise Exception(f"Failed after {max_retries} retries")
```

***

## Best Practices

<CardGroup cols={2}>
  <Card title="Use Batch Endpoints" icon="layer-group">
    `POST /v1/orders/batch` and `POST /v1/prices/batch` combine multiple
    operations into one request — and a batch call counts as **one** tick
    against your RPS/RPM. It's bounded by your tier's **Max Batch Size**
    (see the Tiers table above): `free=1` means no batching benefit on
    free, so this pays off most on `pro` (5) / `pro_plus` (10) /
    `enterprise` (25).
  </Card>

  <Card title="Use WebSocket Feeds" icon="bolt">
    Subscribe to `WS /v1/ws/prices` instead of polling `GET /v1/markets`.
    WebSocket connections don't count against your REST rate limit.
  </Card>

  <Card title="Cache Market Metadata" icon="database">
    Market metadata (slug, question, outcomes) changes infrequently.
    Cache it locally and only refresh periodically.
  </Card>

  <Card title="Idempotency Keys" icon="key">
    On order placement (`POST /v1/orders`, `POST /v1/order`,
    `POST /v1/clob/order`), send an `Idempotency-Key` header — a
    Stripe-style alias for the body's `client_order_id` — so a retried
    request can't double-fill. Reusing a key with a **different** payload
    returns `409 IDEMPOTENCY_KEY_REUSE`; reuse it only for the exact same
    order you're retrying.
  </Card>
</CardGroup>

***

## Next Steps

* [String Numerics](/concepts/string-numerics) — Why all numbers are strings
* [Batch Orders](/trading/batch-orders) — Reduce request count with batching
