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

# Rate limits

> Per-minute and daily quotas, usage monitoring, and throttling behavior.

Rate limits protect API availability and enforce fair usage per plan. Limits apply **per API key** on the hosted API.

See [Pricing & plans](/pricing) for tier details.

## Monitor usage

```bash theme={null}
curl -s -H "X-API-Key: $POLYORDERBOOKS_API_KEY" \
  "https://api.polyorderbooks.com/v1/usage" | jq
```

### Response fields

```json theme={null}
{
  "plan": "starter",
  "organization": "acme-research",
  "limits": {
    "requests_per_minute": 60,
    "requests_remaining": 55,
    "granularity_allowed": "60s",
    "max_history_days": 7,
    "data_points": {
      "metrics": true,
      "prices": true,
      "books": true
    }
  },
  "reset_at": 1754140860
}
```

| Field                        | Description                                                |
| ---------------------------- | ---------------------------------------------------------- |
| `plan`                       | Account tier (`starter`, `pro`, `scale`, `enterprise`)     |
| `organization`               | Display name on your account                               |
| `limits.requests_per_minute` | Maximum requests in the current rolling minute window      |
| `limits.requests_remaining`  | Requests left before per-minute throttling                 |
| `limits.granularity_allowed` | Finest `resolution` permitted on your plan                 |
| `limits.max_history_days`    | Maximum lookback for historical queries                    |
| `limits.data_points.*`       | Feature flags (always `true` on hosted plans today)        |
| `reset_at`                   | Unix timestamp (seconds) when the per-minute window resets |

The [dashboard](https://polyorderbooks.com/dashboard) also shows daily usage and plan details.

## Throttled responses

When you exceed per-minute or daily quotas, the API returns HTTP **`429`**:

```json theme={null}
{
  "detail": "Rate limit exceeded. Please try again later."
}
```

or, for daily caps:

```json theme={null}
{
  "detail": "Daily request limit exceeded. Limit resets at midnight UTC."
}
```

The response includes a **`Retry-After`** header (seconds). Honor it before retrying.

| Limit type | Reset                    |
| ---------- | ------------------------ |
| Per minute | Rolling 60-second window |
| Per day    | Midnight UTC             |

## Plan limits on historical queries

Historical endpoints also enforce resolution and lookback. Violations return HTTP **`403`**:

```json theme={null}
{
  "error": "forbidden",
  "message": "Resolution '1s' is not available on your starter plan. Finest allowed: 60s."
}
```

```json theme={null}
{
  "error": "forbidden",
  "message": "History window exceeds your starter plan (max 7 days). Earliest allowed start_ts is …"
}
```

## Recommended client behavior

<AccordionGroup>
  <Accordion title="Respect Retry-After">
    On `429`, read the `Retry-After` header and sleep at least that many seconds before retrying.
  </Accordion>

  <Accordion title="Exponential backoff">
    If `Retry-After` is absent, use increasing delays (e.g. 1s → 2s → 4s) with a maximum retry cap.
  </Accordion>

  <Accordion title="Connection reuse">
    Reuse HTTP connections with `requests.Session` (Python) or `httpx.Client` to reduce TLS overhead.
  </Accordion>

  <Accordion title="Bulk access patterns">
    Paginate with cursors instead of unbounded parallel fan-out. Pace sequential history calls (\~200ms) when backfilling many markets.
  </Accordion>

  <Accordion title="Resolution selection">
    Use the coarsest `resolution` that meets your analysis needs — fewer buckets, fewer pages, lower quota usage.
  </Accordion>
</AccordionGroup>

## Retry example

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

def get_with_retry(url, *, headers, params, max_retries=5):
    delay = 1.0
    for attempt in range(max_retries):
        response = requests.get(url, headers=headers, params=params, timeout=60)
        if response.status_code == 429:
            retry_after = int(response.headers.get("Retry-After", delay))
            time.sleep(retry_after)
            delay = min(delay * 2, 30.0)
            continue
        response.raise_for_status()
        return response.json()
    response.raise_for_status()
```

Need higher throughput? See [Enterprise on the pricing page](https://polyorderbooks.com/pricing) or email [contact@polyorderbooks.com](mailto:contact@polyorderbooks.com).
