Skip to content

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.

EndpointLimit
GET /v1/check10 req / IP / hour
GET /v1/jurisdictions10 req / IP / hour
GET /v1/operators/search10 req / IP / hour (+ 3-row cap on limit)
GET /v1/health/coverage10 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.

PlanMonthly quotaPer-second limit
Starter10,000 calls5 req/sec
Pro100,000 calls20 req/sec
BusinessFair use — no monthly cap enforced100 req/sec
EnterpriseCustomNegotiated

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.

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).

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/check and the endpoints that work without a key (GET /v1/jurisdictions, GET /v1/operators/search, GET /v1/health/coverage). Any other endpoint answers 402 payment_required with details.reason: "endpoint_requires_paid_plan" (/v1/export says 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.

Every response to a call made without a key on the four public endpoints includes:

HeaderMeaning
X-RateLimit-LimitCeiling for this IP on this endpoint in the current window: 10.
X-RateLimit-RemainingRequests left. Never negative.
X-RateLimit-ResetUnix epoch seconds when the window rolls over (the top of the next UTC hour).
X-RateLimit-PolicyHuman-friendly policy string: tier=public;limit=10;window=hour. Hand-readable, easy to awk.
RateLimit-PolicyIETF draft format (draft-ietf-httpapi-ratelimit-headers): "default";q=10;w=3600. Modern HTTP clients (Cloudflare SDK, Kong, etc.) auto-parse this.
X-Upgrade-URLhttps://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.

A keyed call reports your monthly quota — the number you budget against — not the per-second limit:

HeaderMeaning
X-RateLimit-LimitYour plan’s monthly quota (e.g. 10000).
X-RateLimit-RemainingMonthly calls left.
X-RateLimit-ResetUnix epoch seconds of the monthly reset (00:00 UTC on the 1st).
X-Monthly-Quota-Limit / -Used / -Remaining / -Reset / -WarningThe same monthly view, with an ISO-8601 reset and an 80 % warning — see authentication.
X-RateLimit-PolicySent 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-URLAs 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=hour
const 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=3600
const policyIetf = res.headers.get('RateLimit-Policy');
const q = policyIetf.match(/q=(\d+)/)?.[1]; // quota
const w = policyIetf.match(/w=(\d+)/)?.[1]; // window in seconds

Six different limits answer 429. code alone does not tell them apart — branch on code and details.reason:

codedetails.reasonCauseWait until
rate_limitedquota_exceededKeyless 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_limitedper_second_limit_exceededYour plan’s per-second limit.Retry-After: 1
quota_exceededmonthly_request_limit_reachedYour plan’s monthly quota is used up.details.reset_at / X-Monthly-Quota-Reset — the 1st of next month (no Retry-After)
rate_limitedprelaunch_daily_capLegacy trial key over 1,000 requests/day.Retry-After
rate_limitedwatchlist_events_poll_limitGET /v1/watchlist/events over your plan’s polls per hour (watchlist).details.reset_at
rate_limitedexport_daily_limit_reachedGET /v1/export/:dataset over your plan’s exports per UTC day (export).Retry-After — 00:00 UTC

The keyless 429, in full:

HTTP/2 429
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1790694000
X-RateLimit-Policy: tier=public;limit=10;window=hour
RateLimit-Policy: "default";q=10;w=3600
X-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:

  1. Check X-RateLimit-Remaining before every call.
  2. On 429, wait until the time in the table above, then retry once.
  3. 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/operators list 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.