# Rate limits

> Per-key request limits for live and test keys, the rate-limit headers, and how to handle 429 responses.

Each API key has its own limit per minute:

| Key | Requests per minute |
| --- | --- |
| Live (`mm_live_…`) | 600 |
| Test (`mm_test_…`) | 120 |

The limit counts requests in fixed one-minute windows per key. Two keys of the same workspace have separate limits.

## Headers

Every authenticated response carries the current state of the key's limit:

| Header | Content |
| --- | --- |
| `X-RateLimit-Limit` | Requests allowed per minute for this key. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | Unix time in seconds when the current window resets. |

Above the limit, the API answers `429 rate_limited` with a `Retry-After` header in seconds.

## Handle 429 responses

Wait at least `Retry-After` seconds before you send the next request with that key.

```ts Node.js
async function requestWithRateLimit(url: string, init: RequestInit): Promise<Response> {
  for (;;) {
    const res = await fetch(url, init);
    if (res.status !== 429) return res;
    const waitSeconds = Number(res.headers.get('Retry-After') ?? '1');
    await new Promise((resolve) => setTimeout(resolve, waitSeconds * 1000));
  }
}
```

```python Python
import time
import requests

def request_with_rate_limit(method: str, url: str, **kwargs) -> requests.Response:
    while True:
        res = requests.request(method, url, **kwargs)
        if res.status_code != 429:
            return res
        time.sleep(int(res.headers.get("Retry-After", "1")))
```

To stay below the limit, prefer [webhooks](https://maxmove.com/en/developers/docs/webhooks/events) over polling the tracking endpoint.

---

Source: https://maxmove.com/en/developers/docs/concepts/rate-limits
