Rate limits and quota
Two figures, one of which refuses
A monthly volume is what your plan includes, and a burst limit caps requests per second so one client cannot crowd out others.
Only the burst limit refuses a request. Going past the included volume does not cut you off: your integration keeps working, and traffic that settles well above what you pay for is a conversation about moving up a plan at your next renewal rather than a 429 at three in the morning. Write your client against the burst limit; treat the monthly figure as the size of the plan you bought.
Both are set by your plan. Current usage is on the
keys page and from GET /v1/api/me; plan figures are on the
business page.
Headers
Every response carries your current standing, so a well-behaved client can slow down before it is refused rather than after.
RateLimit-Limit: 100000
RateLimit-Remaining: 58796
RateLimit-Reset: 1725148800
RateLimit-Policy: 100000;w=2592000, 20;w=1| Header | Meaning |
|---|---|
RateLimit-Limit | Requests permitted in the current window. |
RateLimit-Remaining | Requests left. Watch this rather than counting your own calls. |
RateLimit-Reset | Unix timestamp when the window rolls over. |
Retry-After | Sent only with a 429. Seconds to wait, and authoritative: prefer it over any backoff you would calculate. |
Backing off correctly
async function call(request, attempts = 5) {
for (let attempt = 0; attempt < attempts; attempt++) {
const response = await fetch(request);
if (response.status !== 429 && response.status < 500) {
return response;
}
// The server knows when it will let you back in. Believe it.
const retryAfter = Number(response.headers.get("Retry-After") ?? 0);
const backoff = retryAfter > 0
? retryAfter * 1000
: Math.min(2 ** attempt * 250, 30_000);
// Without jitter a fleet of workers retries in lockstep and
// recreates the spike that caused the 429 in the first place.
await sleep(backoff + Math.random() * 500);
}
throw new Error("Exhausted retry budget");
}Spending less quota
- Use webhooks. A delivery costs no quota. Polling a market every five minutes to catch a change that happens twice a day is the single most common source of wasted requests.
- Ask only for what changed.
updated_sinceturns a full re-read into a handful of records. - Send If-None-Match. A 304 does not count against your quota.
- Page larger. One request for 200 records costs one request; four for 50 cost four.
- Expand instead of following.
expand=imagesavoids one extra request per property. - Test against test keys. An
sk_test_key does not draw on your live quota, so a noisy CI suite costs nothing.
If you need more
Tell us what you are building rather than working around the limit. Sustained high volume is usually better served by an export than by a faster crawl, and we would rather raise a quota than have you rediscover the burst limit in production.