Rate limits
Two independent systems: keyless calls are capped per IP, and keyed calls are metered per account against your plan. A valid key on a public endpoint takes that call off the IP cap and onto your plan.
Public endpoints (no key)
Section titled “Public endpoints (no key)”| Endpoint | Limit |
|---|---|
GET /v1/check | 10 req / IP / hour |
GET /v1/jurisdictions | 10 req / IP / hour |
GET /v1/operators/search | 10 req / IP / hour (+ 3-row cap on limit) |
GET /v1/health/coverage | 10 req / IP / hour |
GET /v1/health and GET /v1/stats (the headline counts the homepage
shows) are keyless and on no per-IP cap. /v1/stats answers everyone with one
payload, recounted at most every 10 minutes, so calling it costs the database
nothing and a homepage visit never spends your lookups. A key sent with it is
ignored and not charged.
The counters are per route, per IP: ten /v1/check calls don’t use up
your /v1/operators/search allowance. A route’s counter counts every keyless
call to it from your IP, whatever made the call — including the
MCP server, which forwards your IP: a keyless
check_domain counts against /v1/check exactly like a direct request,
search_operators against /v1/operators/search, list_jurisdictions against
/v1/jurisdictions and check_coverage against /v1/health/coverage. A
domain check and a license_number check share the one /v1/check counter.
The window is clock-aligned: every counter resets at the top of each UTC hour. A caller that burns 10 requests at 14:58 waits two minutes, not sixty.
Authenticated endpoints
Section titled “Authenticated endpoints”| Plan | Monthly quota | Per-second limit |
|---|---|---|
| Starter | 10,000 calls | 5 req/sec |
| Pro | 100,000 calls | 20 req/sec |
| Business | Fair use — no monthly cap enforced | 100 req/sec |
| Enterprise | Custom | Negotiated |
Both limits are enforced today, on every keyed request, before the request reaches the endpoint. Both are per account: every key on the account shares one monthly counter and one per-second counter. The monthly counter resets at 00:00 UTC on the 1st; the per-second counter is a fixed one-second window.
Only answers count against the monthly quota. A request we refuse (any 4xx:
an invalid parameter, an unknown slug) or fail (5xx) is not charged, and its
X-Monthly-Quota-Used / X-RateLimit-Remaining headers say so. It still counts
toward the per-second limit.
Exports
Section titled “Exports”CSV/JSON export (GET /v1/export/:dataset, Pro and above) has
its own daily cap on top of these: 10 exports per UTC day on Pro, 100 on
Business, none on Enterprise — per account, shared with the dashboard’s Export
button. Each export counts as one request against the monthly quota,
whatever its size. Starter has no exports (402 export_requires_pro).
Legacy trial keys
Section titled “Legacy trial keys”Accounts created today are on Starter. A few older accounts are still on
the trial tier, and their keys carry two extra restrictions on top of the
monthly quota and per-second limit above:
GET /v1/checkand the endpoints that work without a key (GET /v1/jurisdictions,GET /v1/operators/search,GET /v1/health/coverage). Any other endpoint answers402 payment_requiredwithdetails.reason: "endpoint_requires_paid_plan"(/v1/exportsays what it needs instead:export_requires_pro). (Until API 1.14.1 the public endpoints refused a trial key too, so a key left its holder worse off than no key.)- 1,000 requests per UTC day, per key, reset at
00:00:00Z.
Over the daily cap → 429 rate_limited:
{ "error": "Pre-launch rate limit exceeded", "code": "rate_limited", "details": { "reason": "prelaunch_daily_cap", "current_usage": 1001, "limit": 1000, "reset_at": "2026-09-30T00:00:00.000Z", "suggestion": "Pre-launch keys are capped at 1000 requests/day; the counter resets at reset_at. …" }}The response carries Retry-After (seconds until midnight UTC) and
X-Prelaunch-Daily-Limit, -Used, -Reset.
Response headers
Section titled “Response headers”Keyless calls
Section titled “Keyless calls”Every response to a call made without a key on the four public endpoints includes:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Ceiling for this IP on this endpoint in the current window: 10. |
X-RateLimit-Remaining | Requests left. Never negative. |
X-RateLimit-Reset | Unix epoch seconds when the window rolls over (the top of the next UTC hour). |
X-RateLimit-Policy | Human-friendly policy string: tier=public;limit=10;window=hour. Hand-readable, easy to awk. |
RateLimit-Policy | IETF draft format (draft-ietf-httpapi-ratelimit-headers): "default";q=10;w=3600. Modern HTTP clients (Cloudflare SDK, Kong, etc.) auto-parse this. |
X-Upgrade-URL | https://igregulator.io/pricing — the header’s value today. To keep going past a limit: without a key, get a free one at app.igregulator.io/signup; with a key, email founder@igregulator.io. |
The two policy headers in this form are sent only on keyless calls.
Keyed calls
Section titled “Keyed calls”A keyed call reports your monthly quota — the number you budget against — not the per-second limit:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Your plan’s monthly quota (e.g. 10000). |
X-RateLimit-Remaining | Monthly calls left. |
X-RateLimit-Reset | Unix epoch seconds of the monthly reset (00:00 UTC on the 1st). |
X-Monthly-Quota-Limit / -Used / -Remaining / -Reset / -Warning | The same monthly view, with an ISO-8601 reset and an 80 % warning — see authentication. |
X-RateLimit-Policy | Sent in two cases only: tier=unlimited on plans with no monthly cap (Business, Enterprise — which get no X-RateLimit-Limit / -Remaining / -Reset), and tier=authenticated when the key is used on one of the four public endpoints. |
X-Upgrade-URL | As above. |
There is no RateLimit-Policy on keyed calls, and the per-second limit is
not advertised on a successful response. Only a per-second 429 describes
it: there X-RateLimit-Limit is the per-second limit and
X-RateLimit-Reset is the next second.
Parsing the policy headers (keyless calls)
Section titled “Parsing the policy headers (keyless calls)”// X-RateLimit-Policy — custom: tier=public;limit=10;window=hourconst policyCustom = Object.fromEntries( res.headers.get('X-RateLimit-Policy').split(';').map((kv) => kv.split('=')),);// { tier: 'public', limit: '10', window: 'hour' }
// RateLimit-Policy — IETF: "default";q=10;w=3600const policyIetf = res.headers.get('RateLimit-Policy');const q = policyIetf.match(/q=(\d+)/)?.[1]; // quotaconst w = policyIetf.match(/w=(\d+)/)?.[1]; // window in secondsHandling 429
Section titled “Handling 429”Six different limits answer 429. code alone does not tell them apart —
branch on code and details.reason:
code | details.reason | Cause | Wait until |
|---|---|---|---|
rate_limited | quota_exceeded | Keyless 10 / hour / IP cap. Here quota_exceeded means the hourly IP allowance, not your monthly quota. | details.reset_at / X-RateLimit-Reset — the top of the next UTC hour (no Retry-After) |
rate_limited | per_second_limit_exceeded | Your plan’s per-second limit. | Retry-After: 1 |
quota_exceeded | monthly_request_limit_reached | Your plan’s monthly quota is used up. | details.reset_at / X-Monthly-Quota-Reset — the 1st of next month (no Retry-After) |
rate_limited | prelaunch_daily_cap | Legacy trial key over 1,000 requests/day. | Retry-After |
rate_limited | watchlist_events_poll_limit | GET /v1/watchlist/events over your plan’s polls per hour (watchlist). | details.reset_at |
rate_limited | export_daily_limit_reached | GET /v1/export/:dataset over your plan’s exports per UTC day (export). | Retry-After — 00:00 UTC |
The keyless 429, in full:
HTTP/2 429X-RateLimit-Limit: 10X-RateLimit-Remaining: 0X-RateLimit-Reset: 1790694000X-RateLimit-Policy: tier=public;limit=10;window=hourRateLimit-Policy: "default";q=10;w=3600X-Upgrade-URL: https://igregulator.io/pricing
{ "error": "Public rate limit reached (10/hour/IP).", "code": "rate_limited", "details": { "reason": "quota_exceeded", "suggestion": "Get a free API key (no card, 10,000 requests/month) at https://app.igregulator.io/signup and send it as \"Authorization: Bearer <key>\" — keyed calls skip this per-IP cap. Or wait until reset_at, the top of the next hour (UTC; also the X-RateLimit-Reset header).", "limit": 10, "reset_at": "2026-09-29T15:00:00.000Z" }}details.reset_at and X-RateLimit-Reset name the same instant — the top of the
next UTC hour, when this hour’s counter stops counting — one as ISO-8601, the
other as Unix epoch seconds (1790694000 above).
Recommended client behaviour:
- Check
X-RateLimit-Remainingbefore every call. - On 429, wait until the time in the table above, then retry once.
- If the same caller keeps hitting 429, that’s a signal to change something — not to back-off-and-retry indefinitely. Without a key: get a free one at app.igregulator.io/signup. With a key: email founder@igregulator.io.
- Don’t scrape the public endpoint. Paginate the authenticated
/v1/jurisdictions/:code/operatorslist once a day and cache — every register is read once a day, so polling more often buys nothing. - Front-ends that surface check results to end-users — apply the 10/hour IP limit on your server and call the API with a single authenticated key; don’t let every browser session hit us directly or the shared IP will burn out your quota.
- Bulk re-verification (weekly AML sweep of 2,000 domains) — use
POST /v1/check/batch: 100 domains per request, each request one call against your quota. Use the operator / licence endpoints for the detail behind a verdict.