Verify a domain or licence number
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”.
Parameters
Section titled “ Parameters ”Query Parameters
Section titled “ Query Parameters ”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.comA 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-014Retrospective 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-01Responses
Section titled “ Responses ”The answer — also when nothing was found (match: null, verdict: not_found).
object
What was looked up: domain (+ input) or license_number, never both.
object
The hostname looked up: lower-cased, one trailing dot dropped, an internationalised name in punycode (bücher.de → xn--bcher-kva.de).
What you sent, when it differs from domain.
The licence number as sent (surrounding spaces trimmed).
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 matcheddomain_exactotherwise).related_host_listed(no match) — the queried host is not stored, and hosts of its registrable domain are listed under SEVERAL operators:matchis null,match_absence_reasonisshared_registrable, the hosts are inrelated_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 withconfidencemedium/low: a name resembled the operator’s; no licence we hold lists this domain. Decided BEFORE the licence status is looked at —match.statusbelongs to that operator, not to this domain, whatever it says.licence_not_active— the matched licence status is notactive(surrendered,expired,revoked,suspended,pending,not_in_register,unknown—verdict_detailnames it), whether or not the regulator still lists the domain on it; when it no longer does,verdict_detailsays that too. Also alicense_numberquery whose licence is notactive, and a domain the regulator lists under an operator that holds no licence that can cover a website (match.statusandmatch.license_numbernull — a UKGC licensee with non-remote licences only, an MGA one with no B2C licence). Onlyrevoked/suspendedare enforcement decisions.domain_not_listed— the licence isactive, 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 isactiveand lists that host:matchdescribes that host (match_type: related_host,matched_domain) and the hosts are inrelated_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.comis answered fromx.comaslicensed.)licensed_provisional— aslicensed, with astatus_qualifier(todayprovisional_under_assessment: a Curaçao licence the CGA keeps in force pending its final assessment).licensed—match.statusisactive, the regulator lists this domain (or its www-counterpart) on that licence (domain_status: active), nostatus_qualifier. For alicense_numberquery: the licence isactive.
Approve automatically only on licensed (and licensed_provisional if your policy accepts provisional licences); route everything else to a human.
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.
object
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.
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).
The operator’s display_name.
The key of /v1/operators/{slug}.
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).
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.
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).
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.
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).
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.
The latest read of status_source_url that still said this status. When the status CHANGED is in /v1/licenses/{license_id}/history.
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.
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.
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.
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.
Set when status is right but not the whole truth; null otherwise. provisional_under_assessment: a Curaçao licence the CGA register lists as “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.
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).
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.
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.
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.
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.
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).
When we read verification_page_status off the page. Present on domain matches only, like it.
On a name match (medium/low) only: up to three other operators whose names resembled the label, best first. [] on every other answer.
object
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.
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”.
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.
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.
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
The operator’s licence jurisdiction; null when it holds none.
That host’s link status under that operator.
Present only when a license_number query was answered through an earlier version of a UKGC number.
object
The number as sent.
match.license_number.
The sentence verdict_detail leads with.
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.
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
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.
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.
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.
The stored spelling this link is on — the query or its www-counterpart (one site; each operator appears once, with its better link).
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 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.
The newest last_verified_at among the jurisdiction’s licences — when a load last confirmed its register. Cached up to 10 minutes.
last_read_at is less than sla_hours old. null if never read.
Example
{ "jurisdiction": "UKGC", "last_read_at": "2026-09-30T03:00:26.364Z", "fresh": true, "sla_hours": 24}This entity’s licence status (see LicenseStatus).
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.
Same as match.verification_page_kind, for this link.
Same as match.verification_page_status, for this link.
Same as match.status_qualifier, for this entity’s licence.
Present only when ?as_of= was sent.
object
The resolved instant (UTC). A bare YYYY-MM-DD resolves to the end of that day; today’s date resolves to now.
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.
Licence status as of the date, or null when not observed.
The history transition in effect at the date — when this status was last confirmed relative to the query.
object
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
Lower bound of our knowledge — when we first observed this licence.
What was time-travelled: a licence status, never a domain link.
match.license_id; null when no licence was resolved.
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.
The operator holding that licence (match.operator).
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.
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
The registered name the facts are about: www.bet365.co.uk → bet365.co.uk.
When the domain was registered, as the registry states it.
When the current registration period ends. Most domains renew automatically; a past date is a reason to look, not a fact about the site.
When the registry record last changed (a renewal, a transfer, new nameservers…).
The sponsoring registrar, as the registry names it.
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.
The RDAP record we read.
When we read it.
/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
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.
Reserved; always null — we store no upstream modification time. Use register.last_read_at and match.status_observed_at for freshness.
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.
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.
When this answer was computed (the request time). Always present.
Present when match.jurisdiction is set: that register’s last read and whether it is inside its SLA.
object
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.
The newest last_verified_at among the jurisdiction’s licences — when a load last confirmed its register. Cached up to 10 minutes.
last_read_at is less than sla_hours old. null if never read.
Example
{ "jurisdiction": "UKGC", "last_read_at": "2026-09-30T03:00:26.364Z", "fresh": true, "sla_hours": 24}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.
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
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.
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 } }}Headers
Section titled “ Headers ”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.
Requests left this hour, this one counted. A refused request (400) counts.
Unix epoch seconds of the top of the next UTC hour, when the counter starts again.
tier=public;limit=10;window=hour without a key; tier=authenticated with one.
The same limit in the IETF draft form, "default";q=10;w=3600. Not sent on a keyed call.
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.
The body of every non-2xx response.
object
Human-readable summary. The wording can change; do not branch on it.
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.
Always present. reason is always set; field and suggestion when they apply; limit errors add the numbers below.
object
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.
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).
A hint a person or an agent can act on. Present on every error today.
On a limit error (429, and the 403 plan caps): the ceiling that was reached.
On a keyed limit error: how much of limit is used. Not on the keyless per-IP 429 or the watchlist-poll 429.
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).
The key’s plan, on plan-scoped limit errors and on export_requires_pro.
On export_requires_pro: the lowest plan that includes the feature.
On watchlist_events_poll_limit: always hour.
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'." }}{ "error": "pass ?domain= or ?license_number=", "code": "invalid_query", "details": { "reason": "missing_required_parameter", "suggestion": "Append a query parameter: ?domain=paddypower.com or ?license_number=039028-R-319297-014" }}{ "error": "pass only one of domain / license_number", "code": "invalid_query", "details": { "reason": "conflicting_parameters", "suggestion": "Remove one of the two — `/v1/check` resolves either a domain or a licence number, not both at once." }}{ "error": "as_of must be a calendar date as YYYY-MM-DD (e.g. 2026-03-01) or an ISO-8601 datetime with a UTC offset (e.g. 2026-03-01T12:00:00Z)", "code": "invalid_query", "details": { "reason": "invalid_input", "field": "as_of", "suggestion": "Pass as_of as YYYY-MM-DD (a real calendar date; resolved to the end of that day, UTC — today's date means now) or as an ISO-8601 datetime with a UTC offset, e.g. 2026-03-01T12:00:00Z. Not in the future." }}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.
The body of every non-2xx response.
object
Human-readable summary. The wording can change; do not branch on it.
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.
Always present. reason is always set; field and suggestion when they apply; limit errors add the numbers below.
object
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.
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).
A hint a person or an agent can act on. Present on every error today.
On a limit error (429, and the 403 plan caps): the ceiling that was reached.
On a keyed limit error: how much of limit is used. Not on the keyless per-IP 429 or the watchlist-poll 429.
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).
The key’s plan, on plan-scoped limit errors and on export_requires_pro.
On export_requires_pro: the lowest plan that includes the feature.
On watchlist_events_poll_limit: always hour.
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." }}{ "error": "Authorization header must be \"Bearer <key>\"", "code": "auth_invalid", "details": { "reason": "malformed_header", "suggestion": "Header format: `Authorization: Bearer <your-api-key>`." }}{ "error": "API key not recognized", "code": "auth_invalid", "details": { "reason": "api_key_invalid", "suggestion": "Double-check the key. Create a new one at https://app.igregulator.io/api-keys if the previous one was deleted." }}{ "error": "API key has been revoked", "code": "auth_revoked", "details": { "reason": "api_key_revoked", "suggestion": "Generate a new API key at https://app.igregulator.io/api-keys. Revoked keys cannot be restored." }}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).
The body of every non-2xx response.
object
Human-readable summary. The wording can change; do not branch on it.
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.
Always present. reason is always set; field and suggestion when they apply; limit errors add the numbers below.
object
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.
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).
A hint a person or an agent can act on. Present on every error today.
On a limit error (429, and the 403 plan caps): the ceiling that was reached.
On a keyed limit error: how much of limit is used. Not on the keyless per-IP 429 or the watchlist-poll 429.
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).
The key’s plan, on plan-scoped limit errors and on export_requires_pro.
On export_requires_pro: the lowest plan that includes the feature.
On watchlist_events_poll_limit: always hour.
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." }}{ "error": "Plan ended", "code": "payment_required", "details": { "reason": "plan_ended", "suggestion": "This account's business plan ended on 2027-01-22 (UTC). Keys and settings are kept; email founder@igregulator.io to renew." }}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.
The body of every non-2xx response.
object
Human-readable summary. The wording can change; do not branch on it.
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.
Always present. reason is always set; field and suggestion when they apply; limit errors add the numbers below.
object
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.
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).
A hint a person or an agent can act on. Present on every error today.
On a limit error (429, and the 403 plan caps): the ceiling that was reached.
On a keyed limit error: how much of limit is used. Not on the keyless per-IP 429 or the watchlist-poll 429.
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).
The key’s plan, on plan-scoped limit errors and on export_requires_pro.
On export_requires_pro: the lowest plan that includes the feature.
On watchlist_events_poll_limit: always hour.
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" }}{ "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." }}{ "error": "Per-second rate limit reached", "code": "rate_limited", "details": { "reason": "per_second_limit_exceeded", "current_usage": 6, "limit": 5, "plan_tier": "starter", "suggestion": "Your plan allows 5 req/s. Back off for a second and retry. Higher limits: Pro and Business, by card or by invoice (three months or more) — https://app.igregulator.io/billing, or email founder@igregulator.io." }}Headers
Section titled “ Headers ”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.
Requests left this hour, this one counted. A refused request (400) counts.
Unix epoch seconds of the top of the next UTC hour, when the counter starts again.
tier=public;limit=10;window=hour without a key; tier=authenticated with one.
The same limit in the IETF draft form, "default";q=10;w=3600. Not sent on a keyed call.
Always https://igregulator.io/pricing.
Unexpected server error. Safe to retry.
The body of every non-2xx response.
object
Human-readable summary. The wording can change; do not branch on it.
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.
Always present. reason is always set; field and suggestion when they apply; limit errors add the numbers below.
object
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.
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).
A hint a person or an agent can act on. Present on every error today.
On a limit error (429, and the 403 plan caps): the ceiling that was reached.
On a keyed limit error: how much of limit is used. Not on the keyless per-IP 429 or the watchlist-poll 429.
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).
The key’s plan, on plan-scoped limit errors and on export_requires_pro.
On export_requires_pro: the lowest plan that includes the feature.
On watchlist_events_poll_limit: always hour.
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." }}