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.

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 counter counts requests, not answers: a keyless call refused with a 400 (a malformed domain, say) still spends one.

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.

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 No monthly cap No per-second limit

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, no cap 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 payment_required, details.reason: export_requires_pro).

Accounts created today are on Starter. A few older accounts are still marked trial; since API 1.23.0 they are served exactly as Starter — the same endpoints, the same monthly quota and per-second limit, no separate daily cap. (Before that a trial key was refused with 402 endpoint_requires_paid_plan on everything but /v1/check and the keyless endpoints, and capped at 1,000 requests a day.)

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, start Starter’s 14-day free trial (card required) at app.igregulator.io/signup for one; with a key, move to a larger plan on Billing or 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:

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=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:

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 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
quota_exceeded geo_check_quota_exceeded POST /v1/geo-checks over your plan’s geo-checks per UTC month (geo-checks). The 1st of next month (no Retry-After)

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": "Start a 14-day free trial of Starter (card required) at https://app.igregulator.io/signup for an API key — 10,000 requests/month, no per-IP cap — and send it as \"Authorization: Bearer <key>\". 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: start Starter’s 14-day free trial (card required) at app.igregulator.io/signup for one. With a key: move to a larger plan on Billing or 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.