# Rate limits

Each API key has a request budget, **600 requests per minute with bursts up to 60** by default. It works
like a bucket of 60 tokens that refills at the per-minute rate: every request takes a token, and an empty
bucket means a `429`. Every authenticated response tells you where you stand:

| Header | Meaning |
|---|---|
| `RateLimit-Limit` | Requests per minute allowed for this key. |
| `RateLimit-Remaining` | Tokens left in the bucket: how many requests you can send right now, at most the burst size. |
| `RateLimit-Reset` | Seconds until the bucket is full again. |

With an empty bucket, you get `429` [`rate_limited`](/docs/errors#rate_limited) with a `Retry-After`
header. Separately, a workspace can only have a limited number of lookups in flight at once, and the whole
service has a ceiling too. Past either one you get `429` [`concurrency_limited`](/docs/errors#concurrency_limited).
Neither is charged.

Need a higher limit? Write to us. Limits can be raised per key.

## Retrying well

Retry only when `error.retryable` is `true`, wait at least `Retry-After`, and back off exponentially:

```javascript tab="JavaScript"
async function withRetry(send, tries = 5) {
  for (let i = 0; ; i++) {
    const res = await send();
    if (res.ok) return res.json();
    const body = await res.json();
    if (!body.error?.retryable || i === tries - 1) throw Object.assign(new Error(body.error.message), body);
    const wait = Number(res.headers.get("retry-after") ?? 2 ** i);
    await new Promise((r) => setTimeout(r, wait * 1000));
  }
}
```

```python tab="Python"
import time

def with_retry(send, tries=5):
    for i in range(tries):
        res = send()
        if res.ok:
            return res.json()
        err = res.json()["error"]
        if not err["retryable"] or i == tries - 1:
            raise RuntimeError(f'{err["code"]}: {err["message"]}')
        time.sleep(float(res.headers.get("Retry-After", 2 ** i)))
```

Pair retries with an [`Idempotency-Key`](/docs/idempotency) so a retry is never charged twice.
