Skip to content

Verify a domain or licence number

GET
/v1/check

Pass ?domain= or ?license_number= — exactly one.

No key needed. Without one: 10 requests / IP / hour, and a request we refuse (400) still spends one. With a valid key: your plan’s limits, counted against your monthly quota (a refused request is then not counted). A key that is sent is always checked — an unknown or revoked one is a 401 here too.

Response: query, verdict, verdict_detail, match, alternatives and _meta are always present; confidence, match_absence_reason, checked_jurisdictions, related_hosts, jurisdictions, superseded_number, as_of and domain_registration only when they apply (each says when). Read verdict first: one of licensed, licensed_provisional, domain_not_listed, licence_not_active, related_host_listed, name_match_only, not_found, generic_term, with verdict_detail as quotable text. A host and its www-counterpart are one site; another host on the same registrable domain is reported as such (related_host_listed, related_hosts[]), never as the queried host. match carries the regulator name, the licence UUID and the status’s own provenance (status_source_url, status_observed_at); _meta.register says how fresh the matched register is, and _meta.stale_jurisdictions which registers are past their SLA on a miss. See the confidence-scoring guide at https://igregulator.io/docs/confidence/ for match-type semantics.

A domain or number we do not find is a 200 with match: null and verdict: not_found — never a 404 — and it means “in none of the registers we cover”, not “unlicensed”.

domain
string
<= 253 characters

A bare hostname. Case is ignored, one trailing dot is dropped, and an internationalised name is converted to punycode (query.domain is what we looked up; query.input echoes what you sent when it differs). A scheme, path, port or underscore is a 400 (not_a_valid_hostname); more than 253 characters is a 400 (hostname_too_long).

Example

bet365.com
license_number
string
<= 64 characters

A licence number as the register prints it — case, spaces and separators are ignored (055149-r-331499-004 finds 055149-R-331499-004). An earlier version of a UKGC number (055148-R-331498-001) resolves to the version the register lists now, said in superseded_number and first in verdict_detail. For KH, TGC, IOM, ON, SE, ES, BE, DE and UA, whose registers publish none, pass the iGregulator reference returned as license_number (e.g. IOM/OGRA/bm-solutions-iom-limited). match.match_type is license_number.

Example

039028-R-319297-014
as_of
string
<= 40 characters

Retrospective lookup. Reconstructs the matched licence’s status as it stood at that moment from transition history, within our observation window only — e.g. “was this operator licensed at the time of a transaction three months ago”. Exactly two formats: YYYY-MM-DD, a real calendar date (the end of that day, UTC; today’s date means now), or an ISO-8601 datetime with a UTC offset (2026-03-01T12:00:00Z, 2026-03-01T14:00+02:00). Anything else — 2026/03/01, 2026-02-30, a datetime without an offset — and any future instant is a 400 with details.field: "as_of". match.status and verdict stay current; the historical answer is the top-level as_of object (which names the licence it is about), and verdict_detail leads with it.

Example

2026-03-01

The answer — also when nothing was found (match: null, verdict: not_found).

Media type application/json
object
query
required

What was looked up: domain (+ input) or license_number, never both.

object
domain

The hostname looked up: lower-cased, one trailing dot dropped, an internationalised name in punycode (bücher.de → xn--bcher-kva.de).

string
input

What you sent, when it differs from domain.

string
license_number

The licence number as sent (surrounding spaces trimmed).

string
verdict
required

The answer in one field, derived from match by the rules below — read it before the individual fields. It describes match (the link the regulator lists now); a dual-licensed domain’s other links are in jurisdictions[]. It is about the licence as it stands NOW — with ?as_of=, verdict_detail leads with the answer for that date and as_of carries it.

The first that applies, in this order:

  • generic_term — no match, and the label is a generic gambling word we will not map to one operator. The exact domain is on no licence we hold (it would have matched domain_exact otherwise).
  • related_host_listed (no match) — the queried host is not stored, and hosts of its registrable domain are listed under SEVERAL operators: match is null, match_absence_reason is shared_registrable, the hosts are in related_hosts[].
  • not_found — no match otherwise: in none of the registers we cover (checked_jurisdictions). Not a finding that it is unlicensed.
  • name_match_only — a match with confidence medium/low: a name resembled the operator’s; no licence we hold lists this domain. Decided BEFORE the licence status is looked at — match.status belongs to that operator, not to this domain, whatever it says.
  • licence_not_active — the matched licence status is not active (surrendered, expired, revoked, suspended, pending, not_in_register, unknown — verdict_detail names it), whether or not the regulator still lists the domain on it; when it no longer does, verdict_detail says that too. Also a license_number query whose licence is not active, and a domain the regulator lists under an operator that holds no licence that can cover a website (match.status and match.license_number null — a UKGC licensee with non-remote licences only, an MGA one with no B2C licence). Only revoked/suspended are enforcement decisions.
  • domain_not_listed — the licence is active, but the regulator no longer lists this domain on it (domain_status: delisted): it does not cover this site.
  • related_host_listed (one operator) — the queried host is NOT listed; another host on the same registrable domain is, under one operator, whose licence is active and lists that host: match describes that host (match_type: related_host, matched_domain) and the hosts are in related_hosts[]. A listing covers the host the regulator names — a made-up subdomain of a licensed brand is not licensed. (A host’s www-counterpart is the same site, not a related host: www.x.com is answered from x.com as licensed.)
  • licensed_provisional — as licensed, with a status_qualifier (today provisional_under_assessment: a Curaçao licence the CGA keeps in force pending its final assessment).
  • licensed — match.status is active, the regulator lists this domain (or its www-counterpart) on that licence (domain_status: active), no status_qualifier. For a license_number query: the licence is active.

Approve automatically only on licensed (and licensed_provisional if your policy accepts provisional licences); route everything else to a human.

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

Plain English stating the verdict, safe to quote as it stands: it names the regulator, operator and licence (never our KH/TGC/IOM/ON/SE/ES/BE/DE/UA reference as a licence number), says when we read the listing and — separately — whether that register’s last read is past its SLA, scopes every miss to the registers we cover, and never calls anything “unlicensed”. With ?as_of= it LEADS with the answer for that date (“On 2026-03-01 we were not yet tracking … so its status then is unknown. Today: …”); for a superseded licence number it leads with that note. The wording may improve over time — branch on verdict, quote verdict_detail.

string
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
alternatives
required

On a name match (medium/low) only: up to three other operators whose names resembled the label, best first. [] on every other answer.

Array<object>
<= 3 items
object
operator
required
string
operator_slug
required
string
matched_name
required
string
similarity
required
number format: float
<= 1
confidence

Mirror of match.confidence — about the match, not the licence. Also low with match: null when the label is generic (match_absence_reason: generic_term). Omitted when nothing matched (no_record_found, shared_registrable): this endpoint never sends "none" — only /v1/check/batch rows do.

string
Allowed values: high medium low
match_absence_reason

Present only when match is null. generic_term: the label is an ultra-generic gambling word (e.g. bestcasino.example) we cannot map to one operator. shared_registrable: the queried host is not stored, and the hosts of its registrable domain are linked to more than one operator — related_hosts[] lists them, none is the answer (verdict related_host_listed when one of them is listed now, else not_found). no_record_found: a specific query we checked against every covered register and did not find. Lets a caller phrase the answer precisely instead of treating every miss as “unlicensed”.

string | null
Allowed values: generic_term shared_registrable no_record_found
checked_jurisdictions

Present when the queried host is on no licence we hold — match is null, a name match, or related_host_listed — the jurisdiction codes actually checked. Report a miss as “not found in the registers of these N jurisdictions”, never as “unlicensed”: we cover these regulators, not every regulator. Example: [“AN”,“BE”,“CO”,“CW”,“DE”,“ES”,“IOM”,“KH”,“MGA”,“ON”,“SE”,“TGC”,“UKGC”]. SE means the commercial online gaming and betting licences only: a Swedish lottery or land-based licence is not in our data, so a miss says nothing about one. ES means the DGOJ’s state-level online licences only: the state lotteries (SELAE, ONCE) hold none and land-based or regional gambling is licensed by the autonomous communities, so a miss says nothing about such a site. BE means the Gaming Commission’s online licence classes A+, B+ and F1+ only: land-based licences and the Loterie Nationale are not in our data. DE means the GGL’s four internet game types only (virtual slots, online poker, sports betting, online horse betting): online casino table games licensed by a Land (Schleswig-Holstein, Bayern), lotteries and land casinos are not in our data — a miss says nothing about such a site. CO means Coljuegos’ online gaming concessions only: land-based gaming (juegos localizados) and the lotteries are not in our data, so a miss says nothing about such a site. UA means PlayCity’s online casino, online poker and bookmaking licences only: land casinos, slot halls and the state lotteries are not in our data, so a miss says nothing about such a site.

Array<string>
related_hosts

Present when the related-host stage answered (verdict related_host_listed, or not_found with shared_registrable): the other stored hosts on the queried host’s registrable domain, listed first, at most 10. Not evidence about the queried host.

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
superseded_number

Present only when a license_number query was answered through an earlier version of a UKGC number.

object
requested
required

The number as sent.

string
current
required

match.license_number.

string
note
required

The sentence verdict_detail leads with.

string
jurisdictions

Present only when a matched domain is licensed by MORE THAN ONE (operator, jurisdiction) pair — dual-licensed brands; the links of a host and of its www-counterpart count together, one entry per operator. Best-first: jurisdictions[0] is the pair match reports — a link with a licence, then one the regulator lists now, then an active licence, then the link we have known longest (stable from day to day, whichever register was read last). Read this before phrasing a single-jurisdiction verdict about a multi-licensed brand; a domain can be active under one register and delisted under another at the same time, and each entry carries its own register freshness.

Array<object>

One (operator, jurisdiction) pair that licenses the matched domain. A brand can be licensed by different legal entities at once — in different jurisdictions, or two group companies in one; status/domain_status are per-link — active in one register and delisted in another are both true simultaneously.

object
operator
required
string
operator_slug
required
string
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 | null
license_number
required

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
required

Same as match.license_reference_is_ours, for this entity’s licence: true (KH, TGC, IOM, ON, SE, ES, BE, DE, UA) means license_number is an iGregulator reference — never present it as the regulator’s number.

boolean
matched_domain
required

The stored spelling this link is on — the query or its www-counterpart (one site; each operator appears once, with its better link).

string
register
required
One of:

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
}
status
required

This entity’s licence status (see LicenseStatus).

string | null
domain_status
required

The domain’s status under THIS entity — same meaning as match.domain_status. Licensed through this entity only when status and domain_status are both active.

string
Allowed values: active parked expired redirecting delisted
domain_association
required
string
Allowed values: direct white_label
verification_url
required
string | null format: uri
verification_page_kind
required

Same as match.verification_page_kind, for this link.

string | null
Allowed values: domain_certificate licence_certificate licensee_page other
verification_page_status

Same as match.verification_page_status, for this link.

string | null
verification_page_read_at
string | null format: date-time
status_qualifier
required

Same as match.status_qualifier, for this entity’s licence.

string | null
Allowed values: provisional_under_assessment
as_of

Present only when ?as_of= was sent.

object
as_of
required

The resolved instant (UTC). A bare YYYY-MM-DD resolves to the end of that day; today’s date resolves to now.

string format: date-time
knowledge
required

observed: the date is within our window, status is known. corrected: the event in effect on that date is one we later WITHDREW (see correction) — status is null. We do not report the withdrawn status, and we do not fall back to the one before it either: neither is something we observed for that date. before_tracking: the date predates when we started watching — status is null, NOT a guess. no_such_license: we have no history for this licence. no_license_resolved: a fuzzy /v1/check match with no specific licence to time-travel.

string
Allowed values: observed corrected before_tracking no_such_license no_license_resolved
status_as_of
required

Licence status as of the date, or null when not observed.

string | null
established_by
required

The history transition in effect at the date — when this status was last confirmed relative to the query.

object
changed_at
string format: date-time
new_status
string
change_type
string
source_url
string
correction

Present only when knowledge is corrected. The event that was in effect at the date and that we later withdrew. withdrawn_status is what we USED to say — never quote it as the status.

object
changed_at
string format: date-time
withdrawn_status
string
corrected_at
string format: date-time
note
string | null
tracking_since
required

Lower bound of our knowledge — when we first observed this licence.

string | null format: date-time
scope
required

What was time-travelled: a licence status, never a domain link.

string
Allowed values: licence
license_id
required

match.license_id; null when no licence was resolved.

string | null format: uuid
license_number
required

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
operator
required

The operator holding that licence (match.operator).

string | null
link_note
required

On a domain query that resolved a licence: “We do not record when this domain was linked to this licence; this is the licence’s status on that date.” Null otherwise.

string | null
domain_registration

Present only on a domain query whose registry record we have read: the registration of the queried domain (its registered name — www. and other subdomains share it). Absent when we have none — the TLD publishes no RDAP (e.g. .de, .es, .se, .be), the registry holds no record, or the domain has not been read yet. Never part of the verdict.

object
domain
required

The registered name the facts are about: www.bet365.co.uk → bet365.co.uk.

string
registered_at
required

When the domain was registered, as the registry states it.

string | null format: date-time
expires_at
required

When the current registration period ends. Most domains renew automatically; a past date is a reason to look, not a fact about the site.

string | null format: date-time
last_changed_at
required

When the registry record last changed (a renewal, a transfer, new nameservers…).

string | null format: date-time
registrar
required

The sponsoring registrar, as the registry names it.

string | null
status
required

EPP status codes as the registry words them, e.g. active, client transfer prohibited; client hold / server hold mean the domain is not published in DNS.

Array<string>
source_url
required

The RDAP record we read.

string | null format: uri
read_at
required

When we read it.

string format: date-time
_meta
required

/v1/check provenance: the single-resource envelope (scraped_at, source_modified_at, source_url, confidence_hint) plus when the answer was computed and how fresh the registers behind it are.

object
scraped_at
required

Kept for compatibility. On a match from a register row (domain_exact, related_host, license_number) it is the matched licence’s last_verified_at. On a name match or a miss there is no record behind the answer, and it is just the request time — the same value as checked_at, not a register read. For register freshness read register / stale_jurisdictions.

string format: date-time
source_modified_at
required

Reserved; always null — we store no upstream modification time. Use register.last_read_at and match.status_observed_at for freshness.

string | null format: date-time
source_url
required

On a match from a register row: the page that published the matched licence’s current status (match.status_source_url, else the register it is listed in). null on a name match or a miss — no record is behind the answer.

string | null format: uri
confidence_hint
required

How the answer was obtained, not how sure the match is (that is confidence): authoritative — a UKGC licence, read from the Commission’s own register export; scraped — a licence read from another regulator’s pages; derived — a name match or a miss, computed with no source row behind it.

string
Allowed values: authoritative scraped derived
checked_at
required

When this answer was computed (the request time). Always present.

string format: date-time
register

Present when match.jurisdiction is set: that register’s last read and whether it is inside its SLA.

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
}
stale_jurisdictions

Present when no licence we hold lists the QUERIED host — match is null, a medium/low name match, or a related host (related_hosts is set): the covered registers currently past their SLA (often []). Each one weakens a “not found”; say so when you report the miss.

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

Example

{
"query": {
"domain": "spinlu.com"
},
"verdict": "domain_not_listed",
"verdict_detail": "The Anjouan Gaming Authority no longer lists spinlu.com on the licence held by 3-102-947207 SRL (licence ALSI-202602010-FI1), which is itself active — that licence does not cover this site.",
"match": {
"confidence": "high",
"match_type": "domain_exact",
"operator": "3-102-947207 SRL",
"operator_slug": "3-102-947207-srl",
"jurisdiction": "AN",
"regulator_name": "Anjouan Gaming Authority",
"license_id": "d9832e56-10bb-458b-82af-ac7762f7a502",
"license_number": "ALSI-202602010-FI1",
"license_reference_is_ours": false,
"status": "active",
"status_source_url": "https://anjouangaming.com/license-register/",
"status_observed_at": "2026-10-07T04:00:11.714Z",
"expires_at": "2027-02-15",
"domain_association": "direct",
"domain_status": "delisted",
"matched_domain": "spinlu.com",
"domain_last_listed_at": null,
"upstream_status": "valid",
"status_qualifier": null,
"verification_url": null,
"verification_page_status": null,
"verification_page_read_at": null
},
"alternatives": [],
"confidence": "high",
"_meta": {
"scraped_at": "2026-10-07T04:00:11.714Z",
"source_modified_at": null,
"source_url": "https://anjouangaming.com/license-register/",
"confidence_hint": "scraped",
"checked_at": "2026-10-08T00:56:20.481Z",
"register": {
"jurisdiction": "AN",
"last_read_at": "2026-10-07T04:00:17.625Z",
"fresh": true,
"sla_hours": 24
}
}
}
X-RateLimit-Limit
integer

Without a key: 10 — requests per IP per clock hour on THIS endpoint (each keyless endpoint has its own counter). With a valid key these three X-RateLimit-* headers describe your monthly quota instead, and the X-Monthly-Quota-* headers are sent too — see any keyed endpoint.

X-RateLimit-Remaining
integer

Requests left this hour, this one counted. A refused request (400) counts.

X-RateLimit-Reset
integer

Unix epoch seconds of the top of the next UTC hour, when the counter starts again.

X-RateLimit-Policy
string

tier=public;limit=10;window=hour without a key; tier=authenticated with one.

RateLimit-Policy
string

The same limit in the IETF draft form, "default";q=10;w=3600. Not sent on a keyed call.

X-Upgrade-URL
string format: uri

Always https://igregulator.io/pricing.

Neither or both of domain / license_number, a domain that is not a hostname (not_a_valid_hostname, hostname_too_long), or an as_of we cannot read or that lies in the future.

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": "domain is not a valid hostname",
"code": "invalid_query",
"details": {
"reason": "not_a_valid_hostname",
"field": "domain",
"suggestion": "Pass a bare hostname — no scheme, no path, no port, no underscores; at most 253 characters. A trailing dot is dropped and an internationalised name is converted to punycode. Example: 'paddypower.com' or 'www.bet365.com'."
}
}

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."
}
}

Only when a key was sent and its account has no plan or a canceled one (plan_inactive), or its plan’s end date has passed (plan_ended). The same request without the Authorization header is answered (within the per-IP limit).

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: canceled — 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."
}
}

Without a key: the per-IP limit of this endpoint, 10 requests per hour (rate_limited / quota_exceeded) — every keyless endpoint has its own counter, a refused request (400) counts, and an API key lifts it — Starter (10,000 requests / month) starts with a 14-day free trial, card required, at https://app.igregulator.io/signup. With a key: the limits of the key — see the key_* examples.

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": "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-10-08T01:00:00.000Z"
}
}
X-RateLimit-Limit
integer

Without a key: 10 — requests per IP per clock hour on THIS endpoint (each keyless endpoint has its own counter). With a valid key these three X-RateLimit-* headers describe your monthly quota instead, and the X-Monthly-Quota-* headers are sent too — see any keyed endpoint.

X-RateLimit-Remaining
integer

Requests left this hour, this one counted. A refused request (400) counts.

X-RateLimit-Reset
integer

Unix epoch seconds of the top of the next UTC hour, when the counter starts again.

X-RateLimit-Policy
string

tier=public;limit=10;window=hour without a key; tier=authenticated with one.

RateLimit-Policy
string

The same limit in the IETF draft form, "default";q=10;w=3600. Not sent on a keyed call.

X-Upgrade-URL
string format: uri

Always https://igregulator.io/pricing.

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."
}
}