# Rate limits

> 60 requests per minute per key, RateLimit headers on every response, and Retry-After on 429.

Each API key may make **60 requests per minute**.

## Headers

Every response carries:

| Header | Meaning |
|---|---|
| `RateLimit-Limit` | Requests allowed per minute for this key. |
| `RateLimit-Remaining` | Requests left in the current window. |
| `RateLimit-Reset` | Seconds until the window resets. |

A `429` also carries `Retry-After`: seconds to wait before retrying.

```http
HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
RateLimit-Limit: 60
RateLimit-Remaining: 0
RateLimit-Reset: 17
Retry-After: 17
```

```json
{
  "type": "https://developers.gocubic.io/errors/rate-limited",
  "title": "Rate limit exceeded",
  "status": 429,
  "code": "rate_limited",
  "detail": "This key has made more than 60 requests in the last minute.",
  "hint": "Wait the number of seconds in the 'Retry-After' header, then retry the same request."
}
```

## Handling 429

```ts
async function getWithBackoff(url: string, headers: Record<string, string>) {
  for (let attempt = 0; ; attempt++) {
    const response = await fetch(url, { headers });
    const retryable = response.status === 429 || response.status >= 500;
    if (!retryable || attempt === 5) return response;

    const retryAfter = Number(response.headers.get('Retry-After'));
    const seconds = retryAfter > 0 ? retryAfter : 2 ** attempt;
    await new Promise((resolve) => setTimeout(resolve, seconds * 1000));
  }
}
```

```python
import time

import requests


def get_with_backoff(url, headers, params=None):
    for attempt in range(6):
        response = requests.get(url, headers=headers, params=params, timeout=30)
        retryable = response.status_code == 429 or response.status_code >= 500
        if not retryable or attempt == 5:
            return response
        retry_after = response.headers.get("Retry-After")
        time.sleep(int(retry_after) if retry_after else 2**attempt)
```

## Staying under the limit

- Use `limit=100` when reading many shipments.
- List items are full Shipment objects; do not fetch each one again.
- Poll with `updatedSince` instead of re-reading everything.
- Run requests one after another, not in parallel.
