Getting started
Rate limits
Requests are rate limited per API key. When you exceed the limit, the API returns 429 instead of queueing — your request is never silently delayed.
- 429Returned, never queued
- 10–600 RPMPublished plan ceilings
- GETRetries are always safe
The 429 response
Rate-limited requests use the standard error envelope described in Errors:
{ "success": false, "data": null, "pagination": null, "meta": { "error": { "statusCode": 429, "type": "rate_limited", "detail": "Rate limit exceeded. Retry with backoff." } }}Per-plan ceilings
The ceiling is a plan property: every plan grants a requests-per-minute figure for its key, and a request over that figure returns 429 rather than being queued. The register below reads off the same record the pricing pagepublishes, where each plan's price and monthly credit grant sit beside it.
- Free
- 10 RPMExplore the API and evaluate core endpoints.
- Launch
- 60 RPMFor prototypes, internal tools, and early-stage products.
- Growth
- 120 RPMFor production apps and recurring enrichment workflows.
- Scale
- 300 RPMFor high-volume SaaS products, CRMs, and data pipelines.
- Volume
- 600 RPMFor large-scale recurring enrichment workloads.
- Enterprise
- Custom rate limitsCustom volume, SLA, DPA, procurement, and reserved capacity.
If steady traffic keeps returning 429after backoff, the client is not misbehaving — its sustained rate is simply above the plan's ceiling. Slow the worker, spread the run over a longer window, or move to the plan whose ceiling matches the throughput you need.
Handling 429s
- Retry with bounded exponential backoff — start at 1 second and double on each attempt. Do not retry aggressively.
- Add jitter when several workers share one key, so a burst of rate-limited workers spreads out instead of retrying in step and hitting the ceiling together again.
- All endpoints are GET requests, so retries are safe — there are no side effects to worry about.
- Cache responses where you can, and slow the client instead of looping on failures.
- Reuse discovery results: filter and location ids from the search discovery endpoints are stable enough to fetch once and reuse.
async function fetchWithBackoff(url: URL, attempts = 3) { for (let attempt = 0; attempt < attempts; attempt += 1) { const res = await fetch(url, { headers: { "X-API-Key": process.env.ENVO_API_KEY ?? "" }, }); if (res.status !== 429) { return res.json(); } // 1s, 2s, 4s — bounded, no aggressive retry loops. await new Promise((resolve) => setTimeout(resolve, 1000 * 2 ** attempt), ); } throw new Error("Rate limited after bounded retries.");}The same pattern in Python, with jitter for a shared key:
import osimport randomimport timeimport requestsdef fetch_with_backoff(url: str, attempts: int = 3): for attempt in range(attempts): res = requests.get( url, headers={"X-API-Key": os.environ["ENVO_API_KEY"]}, timeout=30, ) if res.status_code != 429: return res.json() # 1s, 2s, 4s, plus jitter so parallel workers spread out # instead of retrying in step. time.sleep(2 ** attempt + random.random()) raise RuntimeError("Rate limited after bounded retries.")