Get started / Rate limits & quotas

Rate limits & quotas

What each plan allows, how throttling behaves, and how to stay under it.

Two independent limits apply to every key: a per-minute rate and a monthly request quota. Both come from your plan.

Plan limits#

PlanPriceRequests / monthRequests / minute
Free$050010
Starter$49/mo50,00060
Growth$199/mo500,000300
Scale$499/mo5,000,0001,000

Every plan gets the full dataset and every filter; plans differ only in volume. Annual billing is 10× the monthly price — two months free — but isn't self-serve yet; email us to set it up. Above Scale, talk to us too — custom limits and flat-file delivery are available.

Plan limits sync from the portal to the API about every five minutes, so the limits you bought are the limits that are enforced — an upgrade is live within minutes, no redeploy or new key needed.

Limits are per account, not per key. Ten keys on a Growth plan share one 500,000-request monthly pool. Keys exist to separate environments and to be revoked independently, not to multiply quota.

Your current usage is on the dashboard, which shows month-to-date consumption against your quota and a 30-day daily breakdown per key.

What counts as a request#

Every authenticated HTTP request that reaches the API, including ones that return zero results.

/v1/health is free — it's unauthenticated, isn't rate-limited, and doesn't touch your quota. Use it for liveness probes rather than a limit=1 job query. (/openapi.json is likewise keyless and free.)

Rate-limit headers#

Every authenticated response carries three headers:

HeaderValue
X-RateLimit-LimitYour per-minute limit.
X-RateLimit-RemainingRequests left in the current window.
X-RateLimit-ResetSeconds until the window frees up.

They describe the per-minute limit only — monthly quota consumption lives on the dashboard. 401 responses carry none of these; the headers appear once a request has authenticated.

Exceeding the per-minute rate#

JSON
{ "error": "rate limit exceeded", "rate_per_min": 300, "retry_after_s": 12 }

The window is a sliding 60 seconds, not a fixed calendar minute: the limit counts requests in the last 60 seconds from now. So a burst of 300 at 12:00:00 on a Growth plan clears at 12:01:00, not at the top of the next minute.

retry_after_s — also sent as a standard Retry-After header — is how long until the window frees. Wait that long and retry; if you're consistently hitting the limit, pace the client rather than retry-looping.

Exceeding the monthly quota#

JSON
{
  "error": "monthly quota exceeded",
  "monthly_quota": 50000,
  "resets": "first of next month (UTC)"
}

Also a 429, but with no Retry-After header — retrying won't help. Requests are rejected until the quota resets at the start of the next calendar month (UTC), or until you upgrade. Upgrading takes effect within minutes — the new quota applies to the current month, and there's no proration penalty for mid-month upgrades. Both 429 shapes are in errors.

Staying under the limit#

The cheapest optimisation by far is asking for fewer, bigger pages. limit goes to 200 on /v1/jobs and 1,000 on /v1/jobs/feed, and both cost exactly one request:

50 pages × limit=20  = 50 requests
5 pages  × limit=200 = 5 requests   ← same data, 10× cheaper

The second cheapest is not fetching descriptions you won't read. description_format defaults to omitting them; leave it off until you need them. It doesn't affect your request count, but it's the difference between a 20KB and a 2MB response.

For a client that needs to sustain throughput, pace from the headers rather than retrying on 429 — every response tells you exactly how much headroom is left:

JS
// Read the budget off each response. When the window is nearly spent, wait out
// X-RateLimit-Reset. Pacing at the source costs nothing; discovering the limit
// via 429s costs a round-trip each time.
async function paced(url, key) {
  const res = await fetch(url, { headers: { Authorization: `Bearer ${key}` } });
 
  const remaining = Number(res.headers.get("X-RateLimit-Remaining"));
  const reset = Number(res.headers.get("X-RateLimit-Reset"));
  if (remaining <= 1) {
    await new Promise((r) => setTimeout(r, reset * 1_000));
  }
  return res.json();
}
 
for (const url of urls) {
  await paced(url, key);
}

The window slides, so headroom refills continuously — no need to hardcode your plan's rate; X-RateLimit-Limit tells you. If you run multiple workers, they share one account-wide limit, so either give each worker a safety margin or have them share the header state.

Syncing efficiently#

Re-crawling /v1/jobs to find what changed is the most expensive possible way to stay current, and on a Free or Starter plan it will exhaust the monthly quota before it finishes. Use /v1/jobs/feed — one request returns everything posted since your last cutoff. See syncing the feed.

Was this page helpful?