Skip to content

Verify many domains in one request (KYB sweep)

POST
/v1/check/batch

Authenticated batch of /v1/check (domains only) — one round trip for a KYB sweep or affiliate-list audit instead of N sequential calls. Each result carries verdict / verdict_detail / match / confidence / match_absence_reason exactly as the single check does, and _meta.register when a jurisdiction matched — but not alternatives, the dual-licence jurisdictions list or as_of; a dual-licensed row’s verdict_detail says how many other links GET /v1/check would show. checked_jurisdictions and _meta (checked_at, stale_jurisdictions) are returned once at the top to stay token-lean. Up to 100 domains per request — paginate beyond. Each entry is normalised as the single check normalises domain (case, a trailing dot, an internationalised name → punycode); query.domain is what was looked up and query.input echoes the entry as sent. An entry that is not a hostname — too long, not a string, a URL — comes back as a per-row error (with error_detail, and no verdict) rather than failing the whole batch; only a body that is not { domains: [1–100 entries] } is a 400. Rows carry related_hosts when the related-host stage answered. Results are in the order sent; duplicates are answered twice. The whole batch counts as one request against your plan quota. POST only: a GET here is a 405 (method_not_allowed, Allow: POST), with or without a key.

Media type application/json
object
domains
required
Array<string>
>= 1 items <= 100 items

Example

{
"domains": [
"bet365.com",
"www.virginbet.com",
"casino.com"
]
}

Per-domain results.

Media type application/json
object
count
required

Rows in results — one per entry sent.

integer
checked_jurisdictions
required

The jurisdiction codes every row was checked against — once for the whole batch. Report a not_found row as “not found in these registers”, never as “unlicensed”.

Array<string>
_meta
required
object
checked_at
required

When this batch was computed.

string format: date-time
stale_jurisdictions
required

Covered registers past their freshness SLA (often []) — once for the whole batch. Each weakens every not_found / generic_term / name_match_only row.

Array<object>

A covered jurisdiction whose latest register read is past its freshness SLA. A domain its regulator listed after last_read_at would not be in our data yet, so each entry weakens a “not found”.

object
code
required

Jurisdiction code — UKGC, MGA, CW (Curaçao), KH (Kahnawake), AN (Anjouan), TGC (Tobique), IOM (Isle of Man), ON (Ontario — the iGaming Ontario operator directory), SE (Sweden — online gaming and betting licences from the Spelinspektionen register, nothing else), ES (Spain — the DGOJ’s state-level online licences; not the state lotteries (SELAE, ONCE) or regional land-based gambling), BE (Belgium — the Gaming Commission’s online licence classes A+, B+ and F1+ only; not land-based licences or the Loterie Nationale), DE (Germany — the GGL whitelist’s four internet game types (virtual slots, online poker, sports betting, online horse betting); not a Land’s online casino permits, lotteries or land casinos), CO (Colombia — Coljuegos’ online gaming concessions only; not land-based gaming (juegos localizados) or the lotteries), UA (Ukraine — PlayCity’s online casino, online poker and bookmaking licences only; not land casinos or slot halls or the state lotteries). GET /v1/jurisdictions lists them with their names.

string
last_read_at
required
string format: date-time
results
required
Array<object>
object
query
required
object
domain

The normalised hostname looked up. Absent on an error row.

string
input
required

The entry exactly as sent (any JSON value on an invalid_input row).

verdict

As on the single check. Absent on an error row.

string
Allowed values: licensed licensed_provisional domain_not_listed licence_not_active related_host_listed name_match_only not_found generic_term
verdict_detail

As on the single check. Absent on an error row.

string
_meta

Present when the row matched a jurisdiction.

object
register

When we last read the matched jurisdiction’s register, and whether that read is inside its freshness SLA — the same rule and numbers as /v1/health/coverage (24 h for UKGC, AN, TGC, IOM, ON, SE, ES, BE, DE, CO, UA; 48 h for MGA, CW, KH). A stale register means a change the regulator published since last_read_at is not in this answer.

object
jurisdiction
required

Jurisdiction code — UKGC, MGA, CW (Curaçao), KH (Kahnawake), AN (Anjouan), TGC (Tobique), IOM (Isle of Man), ON (Ontario — the iGaming Ontario operator directory), SE (Sweden — online gaming and betting licences from the Spelinspektionen register, nothing else), ES (Spain — the DGOJ’s state-level online licences; not the state lotteries (SELAE, ONCE) or regional land-based gambling), BE (Belgium — the Gaming Commission’s online licence classes A+, B+ and F1+ only; not land-based licences or the Loterie Nationale), DE (Germany — the GGL whitelist’s four internet game types (virtual slots, online poker, sports betting, online horse betting); not a Land’s online casino permits, lotteries or land casinos), CO (Colombia — Coljuegos’ online gaming concessions only; not land-based gaming (juegos localizados) or the lotteries), UA (Ukraine — PlayCity’s online casino, online poker and bookmaking licences only; not land casinos or slot halls or the state lotteries). GET /v1/jurisdictions lists them with their names.

string
last_read_at
required

The newest last_verified_at among the jurisdiction’s licences — when a load last confirmed its register. Cached up to 10 minutes.

string | null format: date-time
fresh
required

last_read_at is less than sla_hours old. null if never read.

boolean | null
sla_hours
required
integer

Example

{
"jurisdiction": "UKGC",
"last_read_at": "2026-09-30T03:00:26.364Z",
"fresh": true,
"sla_hours": 24
}
match
required
One of:
object
confidence
required

How sure we are WHICH OPERATOR this is — a statement about the match, not about the licence. high = the domain (or its www-counterpart, or — match_type: related_host — another host on the same registrable domain), or the licence number, is listed under this operator in a register we cover. medium = no register we cover ties this domain to one operator; its root closely matches one of the operator’s trading or legal names, so we can name the operator but not prove the domain is theirs (it may be a lookalike). low = a weak name resemblance; operator is a guess. (A generic gambling label such as bestcasino is not a low match: match is null and match_absence_reason is generic_term.) Whether the site is licensed is status together with domain_status, never confidence.

string
Allowed values: high medium low
match_type
required

domain_exact — a register lists the queried host, or its www-counterpart (www.x.com and x.com are one site; matched_domain says which spelling is stored). related_host — the queried host is NOT listed; another host on its registrable domain is, under this one operator, and the match describes that host (matched_domain; verdict related_host_listed) — a listing covers the host the regulator names, so this is never licensed. license_number — a ?license_number= query. trading_name_fuzzy / name_similarity — a name resembled the operator’s (confidence medium/low).

string
Allowed values: domain_exact related_host license_number trading_name_fuzzy name_similarity
operator
required

The operator’s display_name.

string
operator_slug
required

The key of /v1/operators/{slug}.

string
jurisdiction

Jurisdiction code — UKGC, MGA, CW (Curaçao), KH (Kahnawake), AN (Anjouan), TGC (Tobique), IOM (Isle of Man), ON (Ontario — the iGaming Ontario operator directory), SE (Sweden — online gaming and betting licences from the Spelinspektionen register, nothing else), ES (Spain — the DGOJ’s state-level online licences; not the state lotteries (SELAE, ONCE) or regional land-based gambling), BE (Belgium — the Gaming Commission’s online licence classes A+, B+ and F1+ only; not land-based licences or the Loterie Nationale), DE (Germany — the GGL whitelist’s four internet game types (virtual slots, online poker, sports betting, online horse betting); not a Land’s online casino permits, lotteries or land casinos), CO (Colombia — Coljuegos’ online gaming concessions only; not land-based gaming (juegos localizados) or the lotteries), UA (Ukraine — PlayCity’s online casino, online poker and bookmaking licences only; not land casinos or slot halls or the state lotteries). GET /v1/jurisdictions lists them with their names. null on a name_similarity match (no licence is attached).

string | null
regulator_name

The regulator’s name, as GET /v1/jurisdictions gives it (e.g. Curaçao Gaming Authority, UK Gambling Commission) — no second call needed to say whose register lists it. For ON it is iGaming Ontario, the body that publishes the directory, not the regulator (the AGCO) — quote verdict_detail, which words it correctly. null when jurisdiction is.

string | null
license_id

The licence’s iGregulator UUID — the input to GET /v1/licenses/{license_id} (full record, not_listed_since, last_listed_at) and /v1/licenses/{license_id}/history. null when no licence is attached (name_similarity matches).

string | null format: uuid
license_number

The licence number as the regulator’s register prints it — except for KH (Kahnawake), TGC (Tobique), IOM (Isle of Man), ON (Ontario), SE (Sweden), ES (Spain), BE (Belgium), DE (Germany) and UA (Ukraine), whose registers publish none. For those nine it is an iGregulator reference we assign: KH/IG/<slug> or KH/CSPA/<slug>, TGC/B2C/<slug> or TGC/B2B/<slug>, IOM/OGRA/<company key>, ON/IGO/<iGaming Ontario directory id>, SE/ONLINE/<holder key> or SE/BETTING/<holder key>, ES/APUESTAS/<DGOJ profile slug>, ES/CONCURSOS/<…> or ES/OTROS-JUEGOS/<…> (one per general licence), BE/APLUS/<Gaming Commission dossier id>, BE/BPLUS/<…> or BE/F1PLUS/<…> (one per licence class and dossier), DE/SLOTS/<holder key>, DE/POKER/<…>, DE/SPORTS/<…> or DE/HORSES/<…> (one per GGL game type), UA/CASINO/<company code>/<decision date>/<decision number>, UA/POKER/<…> or UA/BETTING/<…> (one per PlayCity issuing decision). The reference is stable and /v1/check?license_number= accepts it, but no regulator issued it: do not present it to a user as the licence number.

string | null
license_reference_is_ours

true for KH, TGC, IOM, ON, SE, ES, BE, DE and UA: those registers publish no licence number, so license_number is an iGregulator reference — never present it as the regulator’s number. false everywhere else (and when there is no licence).

boolean
status_source_url

Where THIS status was published — provenance is per status, not per licence (the same field as on /v1/licenses/{id}). Often not the register the licence is listed in: a Curaçao or Anjouan revocation comes from the enforcement / revoked-licences page, a Kahnawake termination from its advisory notice. Cite it when you report the status. null for a status set before we tracked provenance.

string | null format: uri
status_observed_at

The latest read of status_source_url that still said this status. When the status CHANGED is in /v1/licenses/{license_id}/history.

string | null format: date-time
status

See the LicenseStatus schema. Approve only on active. not_in_register (the register no longer lists the licence; upstream_status is then null) and surrendered (the operator gave it up) are not revocations, and unknown means we could not read the regulator’s wording — read upstream_status and decide for yourself. We do not guess. This is the LICENCE’s status: for the domain read domain_status too, and on a medium/low match it is the named operator’s licence, not a statement about this domain. null on a domain match (with license_number and license_id) when the regulator lists the domain under an operator that holds no licence that can cover a website — a UKGC licensee with non-remote licences only, an MGA one with no B2C licence; jurisdiction names the regulator that lists it and verdict is licence_not_active.

string | null
Allowed values: active suspended revoked expired pending unknown surrendered not_in_register
expires_at

The licence’s expiry date as the register gives it (expiry_date on the licence endpoints), YYYY-MM-DD. null when the register publishes none.

string | null format: date
domain_association

Populated for domain matches (domain_exact, related_host) only. direct = licensee runs the domain; white_label = licensee authorises a third-party brand on the domain. null for licence-number and fuzzy matches.

string | null
Allowed values: direct white_label
upstream_status

The regulator’s own words for the licence status, verbatim, before our enum mapping (e.g. Surrendered → surrendered, Revoked - Non Payment of Fee → revoked, N/A Indefinite → active). Always informative; essential when status is unknown. null when status is not_in_register — there is no current upstream word, and repeating the last one we read as if it were current is how a page came to say ‘revoked · upstream: valid’. Also null when the register’s word says the licence is in force (valid, Active, Licensed, Assessment in progress…) and status is revoked, suspended, surrendered or expired: that status was read from another publication (an enforcement register, an advisory notice), and the register row it would sit next to had not caught up. The reverse is never suppressed — a negative word next to active is always shown. Always null for ON: iGaming Ontario’s directory prints no status word.

string | null
status_qualifier

Set when status is right but not the whole truth; null otherwise. provisional_under_assessment: a Curaçao licence the CGA register lists as “ Assessment in progress” — a provisional licence past its stated term, which the CGA keeps in force until it communicates a final decision. status is active (the operator may trade), and expires_at may be in the past; a decision is outstanding. Treat as licensed, and re-check: the licence can become final or end. Derived from upstream_status.

string | null
Allowed values: provisional_under_assessment
domain_status

The status of matched_domain under this operator, which is not the status of the licence. delisted = the domain is no longer listed on this licence — this site is not covered by it; the licence’s own status is in status, whatever that is. On a related_host match it is the RELATED host’s status, not the queried host’s (which is not listed at all). Simplest: branch on verdict. Field by field, the site is licensed only when match_type is domain_exact, status is active AND domain_status is active. null for licence-number queries and for fuzzy matches (no register ties the domain to this operator).

string | null
Allowed values: active parked expired redirecting delisted
matched_domain

The hostname the register lists, which this match describes. Usually the query itself; its www-counterpart when only that spelling is stored (www.virginbet.com for virginbet.com — one site, verdict as for the query); or, on a related_host match, another host of the same registrable domain (fi.unibet.com for zz.unibet.com — NOT the queried host, verdict related_host_listed). null for licence-number queries and name matches.

string | null
domain_last_listed_at

When a regulator source last listed matched_domain on this licence — the latest register, certificate or seal read that wrote this link as listed. Present only while domain_status is active. null when the domain is delisted: we do not keep the time it was last listed before the de-listing, nor since when it has been de-listed, and we will not approximate either. Also null for licence-number queries and name matches.

string | null format: date-time
verification_url

The regulator’s OWN page naming this domain, where we hold one — a Curaçao certificate (cert.cga.cw), a Tobique validation seal (validate.thetgc.ca) or an MGA verification page (authorisation.mga.org.mt); verification_page_kind says which kind. Fetch or link it to confirm the verdict against the primary source directly. We re-read these pages to keep domain_status current: every Tobique seal nightly, each Curaçao certificate every few days (a rolling nightly batch, oldest first) — so the page itself can be newer than our domain_status. null where the regulator has no such page (UKGC, KH, AN, IOM, ON, SE, ES, BE, DE, CO, UA — the DGOJ’s operator page, the Gaming Commission’s class lists and the GGL’s whitelist list an operator’s domains and are cited as status_source_url, but none is a page per domain) or for non-domain matches. A legacy cert.gcb.cw link is served on cert.cga.cw, which it redirects to.

string | null format: uri
verification_page_kind

What verification_url is — what you hold as evidence. domain_certificate: a page the regulator issues for this domain alone (a CGA Certificate of Operation, cert.cga.cw/certificate; a Tobique validation seal). licence_certificate: the CGA certificate for the licence (cert.cga.cw/token), which lists this domain among its “Approved domains”. licensee_page: the MGA verification page for the licensee company, which lists this domain among its websites. other: a regulator page we have not classified. null when verification_url is null. Read from the URL alone; what the page currently states is verification_page_status.

string | null
Allowed values: domain_certificate licence_certificate licensee_page other
verification_page_status

Present (possibly null) on domain matches — domain_exact, related_host — and absent on licence-number and name matches. The licence status that page printed at our latest read of it, verbatim (Active, Revoked…). null when we hold no such reading. When it is not in-force wording, the page is not confirmation — say what it reads and when (verification_page_read_at). It moves no status by itself: a revocation comes only from a regulator publication (status_source_url).

string | null
verification_page_read_at

When we read verification_page_status off the page. Present on domain matches only, like it.

string | null format: date-time
confidence
required

Always present here (the single check omits it instead): none when nothing matched, and on an error row.

string
Allowed values: high medium low none
match_absence_reason

As on the single check: present when match is null. Absent on an error row.

string | null
Allowed values: generic_term shared_registrable no_record_found
related_hosts

As on the single check.

Array<object>
<= 10 items

Another stored host on the queried host’s registrable domain (Public Suffix List, private section included: a hosting platform’s customers are not each other’s related hosts). Its listing is about IT, not about the queried host.

object
host
required
string
operator
required
string
operator_slug
required
string
jurisdiction
required

The operator’s licence jurisdiction; null when it holds none.

string | null
domain_status
required

That host’s link status under that operator.

string
Allowed values: active parked expired redirecting delisted
error

invalid_hostname: the entry is a string but not a hostname (too long, a URL, a path, an underscore…). invalid_input: the entry is not a string. match is null and there is no verdict.

string
Allowed values: invalid_hostname invalid_input
error_detail

One sentence saying what is wrong with the entry.

string
X-RateLimit-Limit
integer

Your plan’s monthly request quota (not the per-second limit). Not sent on a plan with no monthly quota (Business, Enterprise).

X-RateLimit-Remaining
integer

What is left of the monthly quota, this request counted. Not sent on a plan with no monthly quota.

X-RateLimit-Reset
integer

Unix epoch seconds of the start of the next UTC month. Not sent on a plan with no monthly quota.

X-RateLimit-Policy
string

tier=unlimited on a plan with no monthly quota; otherwise not sent.

X-Upgrade-URL
string format: uri

Always https://igregulator.io/pricing.

X-Monthly-Quota-Limit
string

The monthly request quota of your plan, or unlimited.

X-Monthly-Quota-Used
integer

Requests counted this UTC month, this one included (a request that ends in 4xx / 5xx is not counted). Not sent when the limit is unlimited.

X-Monthly-Quota-Remaining
integer

What is left of the monthly quota. Not sent when unlimited.

X-Monthly-Quota-Reset
string format: date-time

When the monthly counter starts again: the first of next month, 00:00 UTC, ISO-8601. Not sent when unlimited.

X-Monthly-Quota-Warning
string

80% of monthly limit used — sent from 80% of the quota on, absent before.

The body is not JSON, or not { "domains": [ … ] } with 1–100 entries. A bad entry inside the array is not a 400 — it is a per-row error.

Media type application/json

The body of every non-2xx response.

object
error
required

Human-readable summary. The wording can change; do not branch on it.

string
code
required

The error class. Mostly one per HTTP status, with two exceptions to know: quota_exceeded is a 429 for the monthly request quota and a 403 for a plan cap (watchlist size, webhook endpoints). A 409 carries conflict (it carried invalid_query before 1.24.0). Branch on the HTTP status and details.reason. forbidden is only ever sent to a signed-in MCP connection, never to an API key.

string
Allowed values: invalid_query invalid_slug invalid_license_id invalid_jurisdiction_code invalid_pagination auth_required auth_invalid auth_revoked payment_required forbidden not_found method_not_allowed conflict quota_exceeded rate_limited server_error
details
required

Always present. reason is always set; field and suggestion when they apply; limit errors add the numbers below.

object
reason
required

Machine-readable refinement of the top-level code — branch on this. Values: invalid_input, missing_required_parameter, conflicting_parameters, not_a_valid_hostname, hostname_too_long, invalid_pagination, invalid_cursor, since_exceeds_retention_window, invalid_event_type, invalid_jurisdiction, as_of_not_supported, api_key_missing, malformed_header, api_key_invalid, api_key_revoked, plan_inactive, plan_ended, export_requires_pro, geo_checks_require_business, watchlist_quota_exceeded, webhook_quota_exceeded, operator_not_found, license_not_found, jurisdiction_not_found, webhook_not_found, dataset_not_found, route_not_found, method_not_allowed, watchlist_duplicate, no_active_secret, quota_exceeded, monthly_request_limit_reached, per_second_limit_exceeded, watchlist_events_poll_limit, export_daily_limit_reached, geo_check_quota_exceeded, internal_error. A rejected webhook URL carries the SSRF check’s reason (invalid_url, invalid_scheme, empty_host, blocked_hostname, unresolvable_host, private_ip_blocked). New values can be added.

string
field

Present only when the error maps to a specific request input (query param, path param, body key). Omitted for errors that aren’t field-scoped (e.g. rate_limited, auth_revoked).

string
suggestion

A hint a person or an agent can act on. Present on every error today.

string
limit

On a limit error (429, and the 403 plan caps): the ceiling that was reached.

integer
current_usage

On a keyed limit error: how much of limit is used. Not on the keyless per-IP 429 or the watchlist-poll 429.

integer
reset_at

When the counter starts again — on the keyless per-IP 429, the monthly quota, the watchlist-poll limit and the export daily limit. Not on the per-second limit (retry after one second).

string format: date-time
plan_tier

The key’s plan, on plan-scoped limit errors and on export_requires_pro.

string
required_plan

On export_requires_pro: the lowest plan that includes the feature.

string
window

On watchlist_events_poll_limit: always hour.

string
key
additional properties
any

Example

{
"error": "body must be { domains: [...] } with 1–100 entries",
"code": "invalid_query",
"details": {
"reason": "invalid_input",
"suggestion": "POST a JSON body like {\"domains\":[\"bet365.com\",\"www.virginbet.com\"]} — 1 to 100 entries. An entry that is not a hostname comes back as a per-row error; it does not fail the batch."
}
}

The API key is missing, malformed, unknown or revoked. On an endpoint that works without a key this is only ever sent when a key WAS sent and is not valid — drop the header, or fix the key.

Media type application/json

The body of every non-2xx response.

object
error
required

Human-readable summary. The wording can change; do not branch on it.

string
code
required

The error class. Mostly one per HTTP status, with two exceptions to know: quota_exceeded is a 429 for the monthly request quota and a 403 for a plan cap (watchlist size, webhook endpoints). A 409 carries conflict (it carried invalid_query before 1.24.0). Branch on the HTTP status and details.reason. forbidden is only ever sent to a signed-in MCP connection, never to an API key.

string
Allowed values: invalid_query invalid_slug invalid_license_id invalid_jurisdiction_code invalid_pagination auth_required auth_invalid auth_revoked payment_required forbidden not_found method_not_allowed conflict quota_exceeded rate_limited server_error
details
required

Always present. reason is always set; field and suggestion when they apply; limit errors add the numbers below.

object
reason
required

Machine-readable refinement of the top-level code — branch on this. Values: invalid_input, missing_required_parameter, conflicting_parameters, not_a_valid_hostname, hostname_too_long, invalid_pagination, invalid_cursor, since_exceeds_retention_window, invalid_event_type, invalid_jurisdiction, as_of_not_supported, api_key_missing, malformed_header, api_key_invalid, api_key_revoked, plan_inactive, plan_ended, export_requires_pro, geo_checks_require_business, watchlist_quota_exceeded, webhook_quota_exceeded, operator_not_found, license_not_found, jurisdiction_not_found, webhook_not_found, dataset_not_found, route_not_found, method_not_allowed, watchlist_duplicate, no_active_secret, quota_exceeded, monthly_request_limit_reached, per_second_limit_exceeded, watchlist_events_poll_limit, export_daily_limit_reached, geo_check_quota_exceeded, internal_error. A rejected webhook URL carries the SSRF check’s reason (invalid_url, invalid_scheme, empty_host, blocked_hostname, unresolvable_host, private_ip_blocked). New values can be added.

string
field

Present only when the error maps to a specific request input (query param, path param, body key). Omitted for errors that aren’t field-scoped (e.g. rate_limited, auth_revoked).

string
suggestion

A hint a person or an agent can act on. Present on every error today.

string
limit

On a limit error (429, and the 403 plan caps): the ceiling that was reached.

integer
current_usage

On a keyed limit error: how much of limit is used. Not on the keyless per-IP 429 or the watchlist-poll 429.

integer
reset_at

When the counter starts again — on the keyless per-IP 429, the monthly quota, the watchlist-poll limit and the export daily limit. Not on the per-second limit (retry after one second).

string format: date-time
plan_tier

The key’s plan, on plan-scoped limit errors and on export_requires_pro.

string
required_plan

On export_requires_pro: the lowest plan that includes the feature.

string
window

On watchlist_events_poll_limit: always hour.

string
key
additional properties
any

Examples

{
"error": "Authorization header required",
"code": "auth_required",
"details": {
"reason": "api_key_missing",
"suggestion": "Attach an API key: `Authorization: Bearer <key>`. No key yet? Start a 14-day free trial of Starter (card required) at https://app.igregulator.io/signup; an existing account creates keys at https://app.igregulator.io/api-keys."
}
}

The key’s plan does not cover this request: the account has no plan or a canceled one (plan_inactive), or its plan’s end date has passed (plan_ended — keys, watchlist and webhook endpoints are kept, and no webhook is sent until the plan is renewed). A trial account is served as Starter. Paid plans are not on sale yet: email founder@igregulator.io and we will sort it out.

Media type application/json

The body of every non-2xx response.

object
error
required

Human-readable summary. The wording can change; do not branch on it.

string
code
required

The error class. Mostly one per HTTP status, with two exceptions to know: quota_exceeded is a 429 for the monthly request quota and a 403 for a plan cap (watchlist size, webhook endpoints). A 409 carries conflict (it carried invalid_query before 1.24.0). Branch on the HTTP status and details.reason. forbidden is only ever sent to a signed-in MCP connection, never to an API key.

string
Allowed values: invalid_query invalid_slug invalid_license_id invalid_jurisdiction_code invalid_pagination auth_required auth_invalid auth_revoked payment_required forbidden not_found method_not_allowed conflict quota_exceeded rate_limited server_error
details
required

Always present. reason is always set; field and suggestion when they apply; limit errors add the numbers below.

object
reason
required

Machine-readable refinement of the top-level code — branch on this. Values: invalid_input, missing_required_parameter, conflicting_parameters, not_a_valid_hostname, hostname_too_long, invalid_pagination, invalid_cursor, since_exceeds_retention_window, invalid_event_type, invalid_jurisdiction, as_of_not_supported, api_key_missing, malformed_header, api_key_invalid, api_key_revoked, plan_inactive, plan_ended, export_requires_pro, geo_checks_require_business, watchlist_quota_exceeded, webhook_quota_exceeded, operator_not_found, license_not_found, jurisdiction_not_found, webhook_not_found, dataset_not_found, route_not_found, method_not_allowed, watchlist_duplicate, no_active_secret, quota_exceeded, monthly_request_limit_reached, per_second_limit_exceeded, watchlist_events_poll_limit, export_daily_limit_reached, geo_check_quota_exceeded, internal_error. A rejected webhook URL carries the SSRF check’s reason (invalid_url, invalid_scheme, empty_host, blocked_hostname, unresolvable_host, private_ip_blocked). New values can be added.

string
field

Present only when the error maps to a specific request input (query param, path param, body key). Omitted for errors that aren’t field-scoped (e.g. rate_limited, auth_revoked).

string
suggestion

A hint a person or an agent can act on. Present on every error today.

string
limit

On a limit error (429, and the 403 plan caps): the ceiling that was reached.

integer
current_usage

On a keyed limit error: how much of limit is used. Not on the keyless per-IP 429 or the watchlist-poll 429.

integer
reset_at

When the counter starts again — on the keyless per-IP 429, the monthly quota, the watchlist-poll limit and the export daily limit. Not on the per-second limit (retry after one second).

string format: date-time
plan_tier

The key’s plan, on plan-scoped limit errors and on export_requires_pro.

string
required_plan

On export_requires_pro: the lowest plan that includes the feature.

string
window

On watchlist_events_poll_limit: always hour.

string
key
additional properties
any

Examples

{
"error": "Active subscription required",
"code": "payment_required",
"details": {
"reason": "plan_inactive",
"suggestion": "Current plan_tier: none — this account has no active plan. Renew or choose a plan at https://app.igregulator.io/billing (by card, or by invoice for three months or more; Starter starts with a 14-day free trial, card required), or email founder@igregulator.io."
}
}

A limit of the key was reached. quota_exceeded / monthly_request_limit_reached: the plan’s monthly request quota — wait for reset_at. rate_limited / per_second_limit_exceeded: more requests in one second than the plan allows — retry after Retry-After (1 s). A request refused here is not counted against the monthly quota.

Media type application/json

The body of every non-2xx response.

object
error
required

Human-readable summary. The wording can change; do not branch on it.

string
code
required

The error class. Mostly one per HTTP status, with two exceptions to know: quota_exceeded is a 429 for the monthly request quota and a 403 for a plan cap (watchlist size, webhook endpoints). A 409 carries conflict (it carried invalid_query before 1.24.0). Branch on the HTTP status and details.reason. forbidden is only ever sent to a signed-in MCP connection, never to an API key.

string
Allowed values: invalid_query invalid_slug invalid_license_id invalid_jurisdiction_code invalid_pagination auth_required auth_invalid auth_revoked payment_required forbidden not_found method_not_allowed conflict quota_exceeded rate_limited server_error
details
required

Always present. reason is always set; field and suggestion when they apply; limit errors add the numbers below.

object
reason
required

Machine-readable refinement of the top-level code — branch on this. Values: invalid_input, missing_required_parameter, conflicting_parameters, not_a_valid_hostname, hostname_too_long, invalid_pagination, invalid_cursor, since_exceeds_retention_window, invalid_event_type, invalid_jurisdiction, as_of_not_supported, api_key_missing, malformed_header, api_key_invalid, api_key_revoked, plan_inactive, plan_ended, export_requires_pro, geo_checks_require_business, watchlist_quota_exceeded, webhook_quota_exceeded, operator_not_found, license_not_found, jurisdiction_not_found, webhook_not_found, dataset_not_found, route_not_found, method_not_allowed, watchlist_duplicate, no_active_secret, quota_exceeded, monthly_request_limit_reached, per_second_limit_exceeded, watchlist_events_poll_limit, export_daily_limit_reached, geo_check_quota_exceeded, internal_error. A rejected webhook URL carries the SSRF check’s reason (invalid_url, invalid_scheme, empty_host, blocked_hostname, unresolvable_host, private_ip_blocked). New values can be added.

string
field

Present only when the error maps to a specific request input (query param, path param, body key). Omitted for errors that aren’t field-scoped (e.g. rate_limited, auth_revoked).

string
suggestion

A hint a person or an agent can act on. Present on every error today.

string
limit

On a limit error (429, and the 403 plan caps): the ceiling that was reached.

integer
current_usage

On a keyed limit error: how much of limit is used. Not on the keyless per-IP 429 or the watchlist-poll 429.

integer
reset_at

When the counter starts again — on the keyless per-IP 429, the monthly quota, the watchlist-poll limit and the export daily limit. Not on the per-second limit (retry after one second).

string format: date-time
plan_tier

The key’s plan, on plan-scoped limit errors and on export_requires_pro.

string
required_plan

On export_requires_pro: the lowest plan that includes the feature.

string
window

On watchlist_events_poll_limit: always hour.

string
key
additional properties
any

Examples

{
"error": "Monthly quota exceeded",
"code": "quota_exceeded",
"details": {
"reason": "monthly_request_limit_reached",
"current_usage": 10000,
"limit": 10000,
"reset_at": "2026-11-01T00:00:00.000Z",
"plan_tier": "starter",
"suggestion": "Wait until the monthly reset (reset_at). Higher limits: Pro and Business, by card or by invoice (three months or more) — https://app.igregulator.io/billing, or email founder@igregulator.io."
}
}
Retry-After
integer

On the per-second limit (1). Not on the monthly quota — read details.reset_at.

X-RateLimit-Limit
integer

On the per-second limit only: the requests per second your plan allows.

X-RateLimit-Remaining
integer

On the per-second limit only: 0.

X-RateLimit-Reset
integer

On the per-second limit only: Unix epoch seconds of the next second.

X-Monthly-Quota-Limit
string

On the monthly quota only: the quota of your plan.

X-Monthly-Quota-Used
integer

On the monthly quota only: requests counted this month.

X-Monthly-Quota-Remaining
integer

On the monthly quota only: 0.

X-Monthly-Quota-Reset
string format: date-time

On the monthly quota only: when the counter starts again (ISO-8601).

Unexpected server error. Safe to retry.

Media type application/json

The body of every non-2xx response.

object
error
required

Human-readable summary. The wording can change; do not branch on it.

string
code
required

The error class. Mostly one per HTTP status, with two exceptions to know: quota_exceeded is a 429 for the monthly request quota and a 403 for a plan cap (watchlist size, webhook endpoints). A 409 carries conflict (it carried invalid_query before 1.24.0). Branch on the HTTP status and details.reason. forbidden is only ever sent to a signed-in MCP connection, never to an API key.

string
Allowed values: invalid_query invalid_slug invalid_license_id invalid_jurisdiction_code invalid_pagination auth_required auth_invalid auth_revoked payment_required forbidden not_found method_not_allowed conflict quota_exceeded rate_limited server_error
details
required

Always present. reason is always set; field and suggestion when they apply; limit errors add the numbers below.

object
reason
required

Machine-readable refinement of the top-level code — branch on this. Values: invalid_input, missing_required_parameter, conflicting_parameters, not_a_valid_hostname, hostname_too_long, invalid_pagination, invalid_cursor, since_exceeds_retention_window, invalid_event_type, invalid_jurisdiction, as_of_not_supported, api_key_missing, malformed_header, api_key_invalid, api_key_revoked, plan_inactive, plan_ended, export_requires_pro, geo_checks_require_business, watchlist_quota_exceeded, webhook_quota_exceeded, operator_not_found, license_not_found, jurisdiction_not_found, webhook_not_found, dataset_not_found, route_not_found, method_not_allowed, watchlist_duplicate, no_active_secret, quota_exceeded, monthly_request_limit_reached, per_second_limit_exceeded, watchlist_events_poll_limit, export_daily_limit_reached, geo_check_quota_exceeded, internal_error. A rejected webhook URL carries the SSRF check’s reason (invalid_url, invalid_scheme, empty_host, blocked_hostname, unresolvable_host, private_ip_blocked). New values can be added.

string
field

Present only when the error maps to a specific request input (query param, path param, body key). Omitted for errors that aren’t field-scoped (e.g. rate_limited, auth_revoked).

string
suggestion

A hint a person or an agent can act on. Present on every error today.

string
limit

On a limit error (429, and the 403 plan caps): the ceiling that was reached.

integer
current_usage

On a keyed limit error: how much of limit is used. Not on the keyless per-IP 429 or the watchlist-poll 429.

integer
reset_at

When the counter starts again — on the keyless per-IP 429, the monthly quota, the watchlist-poll limit and the export daily limit. Not on the per-second limit (retry after one second).

string format: date-time
plan_tier

The key’s plan, on plan-scoped limit errors and on export_requires_pro.

string
required_plan

On export_requires_pro: the lowest plan that includes the feature.

string
window

On watchlist_events_poll_limit: always hour.

string
key
additional properties
any

Example

{
"error": "Internal server error",
"code": "server_error",
"details": {
"reason": "internal_error",
"suggestion": "Retry with exponential backoff (1s / 2s / 4s). If the error persists past 4 seconds, surface failure state to the user instead of looping."
}
}