Rate limits
60 requests per minute and 10 concurrent requests per API key, what a 429 looks like, and how to size a job.
Each API key may send 60 requests per minute and have 10 requests in flight at once. Going over either limit returns a 429, and a rejected request costs nothing. Beyond those two ceilings, what bounds your volume is your credit balance.
The limits at a glance
| Limit | Applies to | Value | When exceeded |
|---|---|---|---|
| Request rate | Per API key | 60 requests / sliding 60 seconds | 429 RATE_LIMITED with Retry-After |
| Concurrency | Per API key | 10 requests in flight | 429 CONCURRENCY_LIMIT with Retry-After: 1 |
| Credits | Per account | Your balance | 402 INSUFFICIENT_CREDITS |
| API keys | Per account | 25 keys | Key creation is refused. See Authentication |
The dashboard Explorer has its own allowance with the same numbers, so trying endpoints there does not eat into your keys' limits.
Request rate: 60 per minute per key
Every data request on a key counts toward a sliding 60-second window. That includes requests that end in a 402, 404 or 405, and owned re-reads that cost nothing. A max_pages walk is one request and takes one slot. None of these count: GET /v1/credits, GET /v1/endpoints, the bare GET /v1, and any request rejected for a missing or bad key, since authentication runs first.
{
"success": false,
"error": {
"type": "RATE_LIMITED",
"message": "Too many requests on this key. Honour Retry-After, then back off with jitter."
},
"request_id": "req_1a2b3c4d5e6f",
"credits_used": 0,
"credits_remaining": null
}Retry-After is the number of seconds until the oldest request in the window drops out, at least 1.
Concurrency: 10 in flight per key
Separately, a key may have at most 10 open requests. This is not a time window: a slot frees the moment one of your requests returns, so an 11th request sent while 10 are open is rejected.
{
"success": false,
"error": {
"type": "CONCURRENCY_LIMIT",
"message": "Too many calls in flight on this key. Retry shortly."
},
"request_id": "req_1a2b3c4d5e6f",
"credits_used": 0,
"credits_remaining": null
}Retry-After is 1 on this one.
Only Retry-After
The API sends no headers describing your remaining headroom. The only timing header is Retry-After. It comes with every 429, and also with 409 IDEMPOTENCY_IN_PROGRESS and some 503 responses. Pace yourself from your own counts, as described below.
Credits are the volume limit
There is no daily or monthly request quota. What limits sustained work is your balance: the free plan includes 500 credits a month, Pro 10,000, and packs add more. Prices differ per endpoint and some are metered, so price every endpoint your job uses on Endpoint pricing before you size it. See Credits for how charging works.
Handling a 429
Read error.type
RATE_LIMITED means you are sending too fast: slow the request rate. CONCURRENCY_LIMIT means too many requests are open at once: shrink the worker pool. They need different fixes.
Wait for Retry-After
Wait at least that many seconds before retrying.
Back off with jitter
If the retry also gets a 429, double the wait and add random jitter so your workers do not all retry in the same instant.
Cap the attempts
After a handful of attempts, give up and surface the error.
async function callWithBackoff(url: string, init: RequestInit, maxRetries = 5): Promise<Response> {
for (let attempt = 0; ; attempt++) {
const res = await fetch(url, init);
// Only 429s are handled here. A 402 or another 4xx will not
// change on retry, so return it straight away.
if (res.status !== 429 || attempt >= maxRetries) return res;
const retryAfter = Number(res.headers.get("Retry-After")) || 1;
const backoff = retryAfter * 2 ** attempt;
const jittered = backoff * (0.5 + Math.random() * 0.5);
await new Promise((r) => setTimeout(r, jittered * 1000));
}
}The Errors page has complete retry loops in cURL, Python and Node.js that also cover 503 and 500.
Sizing a job
- Run at most 10 workers per key.
- Keep the total under 60 requests a minute, which is one request a second on average.
- Budget credits per page, not per job, and remember that metered calls hold the top of their price range while they run.
- Walk paginated endpoints until
pagination.has_moreisfalse. See Pagination. - Send an
Idempotency-Keyon each call so retries never pay twice.