# Rate limits

> Per-key request limits, the X-RateLimit headers and handling 429 answers.

Source: https://www.ordstage.com/docs/rate-limits/

Each API key may make a set number of requests per minute (a sliding one-minute window). The limit depends on
the plan; it is always in the `X-RateLimit-Limit` header, so read it instead of hard-coding a number.

Every authenticated answer carries:

| Header | Meaning |
|---|---|
| `X-RateLimit-Limit` | Requests this key may make per minute. |
| `X-RateLimit-Remaining` | Requests left in the current window. |
| `X-RateLimit-Reset` | Unix time (seconds) when a request is next freed. |

Over the limit, the API answers `429` with code `rate_limited` and a `Retry-After` header (seconds). Refused
requests don't count against the limit.

```json
{
  "error": {
    "type": "rate_limit",
    "code": "rate_limited",
    "message": "Too many requests for this key. Wait and try again.",
    "param": null,
    "request_id": "req_4f2a9c0d1e7b3a5c6d8e9f01"
  }
}
```

## Handling 429

```js [JavaScript]
async function request(url, options, attempt = 0) {
  const response = await fetch(url, options);
  if ((response.status === 429 || response.status === 503) && attempt < 5) {
    const wait = Number(response.headers.get("Retry-After") ?? 2 ** attempt);
    await new Promise((resolve) => setTimeout(resolve, (wait + Math.random()) * 1000));
    return request(url, options, attempt + 1);
  }
  return response;
}
```

```python [Python]
import random
import time
import requests

def request(method, url, attempts=5, **kwargs):
    for attempt in range(attempts):
        response = requests.request(method, url, timeout=30, **kwargs)
        if response.status_code not in (429, 503):
            return response
        wait = float(response.headers.get("Retry-After", 2 ** attempt))
        time.sleep(wait + random.random())
    return response
```

## Other limits

- **Invalid keys:** more than 20 failed authentications in a minute from one address block that address for 15
  minutes (`429 too_many_invalid_keys`, with `Retry-After`).
- **Limiter unavailable:** if Ordstage briefly can't check limits, the API answers `503
  rate_limiter_unavailable` with `Retry-After: 5` rather than run unlimited. Retry after the wait.
- **Request size:** request bodies are limited to 10 MB (`413`).

To stay well inside the limits, use [webhooks](https://www.ordstage.com/docs/webhooks/) instead of polling, and spread bulk jobs over time.
