Confidence scoring
The /v1/check endpoint returns a match object with a confidence
field. This page explains what each level means, how we pick it, and
how UIs should render it.
confidence is about the match, not the licence. It says how sure
we are which licence the domain belongs to — never whether that
licence is in force. Only match.status: "active" means licensed now,
and match.domain_status: "delisted" means the regulator no longer
lists this domain on that licence. The top-level verdict puts these
together for you.
verdict — the answer in one field
Section titled “verdict — the answer in one field”Every /v1/check response (and every batch row that is not an
error) carries a verdict and a verdict_detail. The verdict is derived by
fixed rules, checked in this order — the first that applies is the answer:
verdict | When | What it means |
|---|---|---|
generic_term | match is null, match_absence_reason: generic_term | A generic gambling label we won’t map to one operator. The domain is on no licence we hold — it would have matched domain_exact otherwise. |
related_host_listed | match is null, match_absence_reason: shared_registrable, and at least one host in related_hosts[] is listed now | Your host — and its www. counterpart — is on no licence we hold; other hosts on the same registrable domain are, under more than one operator, so we tie your host to none of them. |
not_found | match is null, match_absence_reason: no_record_found — or shared_registrable with no related host listed now | In none of the registers we cover. Not a finding that the site is unlicensed. |
name_match_only | confidence is medium or low | A name resembled an operator’s; no licence we hold lists this domain. status is that operator’s, not this site’s. |
licence_not_active | status is not active — whether or not the regulator still lists the domain on it | verdict_detail names the status: surrendered, expired, revoked, suspended, pending, not_in_register or unknown — and, if the regulator no longer lists the domain on that licence either, says that too. Only revoked/suspended are enforcement decisions. |
domain_not_listed | status: active, domain_status is not active (delisted) | The regulator no longer lists this domain on the matched licence, which is itself active — that licence does not cover this site. |
related_host_listed | match.match_type: related_host — status: active, that host listed | Your host is on no licence we hold; match describes another host on the same registrable domain (match.matched_domain), the only operator’s there. Never licensed: a listing covers the host the regulator names. |
licensed_provisional | status: active, domain listed, a status_qualifier is set | Licensed now, provisionally — today a Curaçao licence the CGA keeps in force pending its final assessment. |
licensed | status: active, domain_status: active, no qualifier | The regulator lists this domain — or its www. counterpart, the same site — on an active licence. |
related_host_listed appears twice because the related-host stage has two
outcomes (which host answers):
one operator’s host stands in as match, or several operators’ hosts are listed in
related_hosts[] with no match. Either way it is not an answer about your host.
The licence comes before the listing: a revoked, suspended or expired licence is
licence_not_active even when the domain has also been de-listed, so the
revocation is never hidden behind “no longer listed”.
Only licensed and licensed_provisional mean “licensed now”. Every other value
is “not confirmed as licensed by the registers we cover” — and none of them is a
finding that the site is unlicensed.
For a ?license_number= query there is no domain, so the verdict is about the
licence alone: licensed, licensed_provisional, licence_not_active or
not_found.
verdict describes match — the link the regulator lists now. A dual-licensed
domain keeps its other links, each with its own status and domain_status, in
jurisdictions[], and verdict_detail
says how many there are. The verdict is about the licence today, even when you
pass as_of: then verdict_detail leads with the answer
for your date (“On 2026-03-01 we were not yet tracking … licence …, so its status then
is unknown. Today: …”) and as_of carries it in fields.
verdict_detail is one sentence written to be quoted as it stands:
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.
It names the regulator (match.regulator_name), the operator and the licence —
but never our Kahnawake / Tobique / Isle of Man reference as if the regulator
had issued it (match.license_reference_is_ours); dates the read that listed
the domain (“as of our read on …” — for a Curaçao or Tobique link that can be
the certificate or seal read, not the register read), and when the register
behind the answer was last read outside its freshness window, names that register
and the date of that read (_meta.register); scopes every miss to the registers
we cover and names the stale ones (_meta.stale_jurisdictions); and never calls
anything “unlicensed”. Branch on verdict; quote verdict_detail. The wording
may improve over time; a verdict value, once published, keeps its meaning.
const body = await (await fetch( 'https://api.igregulator.io/v1/check?domain=bet365.com',)).json();switch (body.verdict) { case 'licensed': approve(body.verdict_detail); break; case 'licensed_provisional': // in force, provisionally — accept per your policy, and re-check approveProvisionally(body.verdict_detail); break; default: // everything else is "not confirmed as licensed": review it, quote the sentence review(body.verdict, body.verdict_detail);}Treat a verdict value you don’t know like the default branch: values can be
added (related_host_listed was, after the first seven), and no new one will
ever mean “licensed”.
The three levels
Section titled “The three levels”confidence | What it means | Render as |
|---|---|---|
high | A register lists this host — or its www. counterpart — on a licence (or, for ?license_number=, the register has that licence). | We’re sure which licence this domain belongs to. Read status (only active = licensed now) and domain_status before saying anything about the site. |
medium | Domain root matched a trading name or operator name. We can identify the operator but can’t prove this domain is theirs. | Amber / neutral. “Likely operated by X” phrasing — and status is X’s licence, not this site’s. |
low | A weak fuzzy match: an operator is returned, but below the strong-similarity bar. | Gray / warning. “We can’t confirm this domain.” |
On a miss (match: null) the top-level confidence is absent —
treat it as none — or low when the domain root is a generic gambling
term (casino.org, poker.com) that too many operators share to pick
one. Batch rows always carry confidence, with none
spelled out on a miss.
Why a query missed: match_absence_reason
Section titled “Why a query missed: match_absence_reason”When match is null, confidence on its own is ambiguous — it used to
conflate “generic term” with “we checked and it isn’t there”. So on a miss
the response says why, in match_absence_reason (present only when match
is null), and which registers it checked, in checked_jurisdictions, so you
can phrase the answer precisely instead of guessing:
match_absence_reason | Meaning | Say to your user |
|---|---|---|
generic_term | The label is an ultra-generic gambling word (casino.org); we can’t map it to one operator. | ”Can’t identify a specific operator from this domain.” |
no_record_found | A specific query we checked against every covered register and did not find. | ”Not found in any of the N jurisdictions iGregulator covers.” — never an unqualified “unlicensed”. |
shared_registrable | Your host is on no licence; other hosts on its registrable domain are stored, linked to more than one operator — so we tie your host to none of them. Comes with related_hosts[]; the verdict is related_host_listed when one of them is listed now, else not_found. | ”This exact host isn’t listed; these other hosts on the same domain are, under different operators.” |
checked_jurisdictions — the exact register codes we checked (e.g.
["AN","CW","IOM","KH","MGA","TGC","UKGC"]) — accompanies every answer that does
not tie your host itself to a licence: a miss, a name match (name_match_only),
and a related-host answer. A “not licensed” claim is always scoped to our
coverage, never stated as an absolute.
{ "query": { "domain": "some-unknown-site.com" }, "verdict": "not_found", "verdict_detail": "some-unknown-site.com was not found in the registers of the 7 jurisdictions we cover (AN, CW, IOM, KH, MGA, TGC, UKGC) — not a finding that it is unlicensed; our last read of TGC (2026-09-21) is past its freshness window, so a recent listing there may be missing.", "match": null, "alternatives": [], "match_absence_reason": "no_record_found", "checked_jurisdictions": ["AN", "CW", "IOM", "KH", "MGA", "TGC", "UKGC"], "_meta": { "checked_at": "2026-09-30T18:40:00.000Z", "stale_jurisdictions": [{ "code": "TGC", "last_read_at": "2026-09-21T04:15:08.476Z" }] }}(_meta trimmed to the fields that matter here.) stale_jurisdictions lists the
covered registers whose last read is past its freshness window — a domain a
regulator listed since then is not in our data yet, so each entry weakens the
“not found”. It is [] when every register is fresh.
A generic label is the one miss that carries a confidence:
{ "query": { "domain": "casino.org" }, "verdict": "generic_term", "verdict_detail": "casino.org is on no licence we hold in the 7 jurisdictions we cover (AN, CW, IOM, KH, MGA, TGC, UKGC), and its name contains the generic gambling term \"casino\", so we will not guess an operator from it — not a finding that it is unlicensed.", "match": null, "alternatives": [], "confidence": "low", "match_absence_reason": "generic_term", "checked_jurisdictions": ["AN", "CW", "IOM", "KH", "MGA", "TGC", "UKGC"]}match_absence_reason is absent when there’s a match — check
match === null first, then branch on match_absence_reason (or simply branch
on verdict, which already did).
Which host answers — exact, www., then the rest of the domain
Section titled “Which host answers — exact, www., then the rest of the domain”A register lists hosts, and /v1/check answers for the host you send. It looks,
in this order, for:
-
The site: the exact host and its
www.counterpart.www.example.comandexample.comare the same site, and registers list either spelling (the UKGC listswww.betfair.com, Anjouanspinsup.com). The links of both are ranked together, so both spellings get the same answer — a fulldomain_exactmatch, withmatch.matched_domainnaming the host the register lists (paddypower.comanswers forwww.paddypower.com). Only a leadingwww.is dropped or added this way. -
Other hosts on the same registrable domain — the domain under its public suffix:
bet365.comforsports.bet365.com,example.co.ukform.example.co.uk. These are never taken as the host you asked about: a listing covers the host the regulator names, not every subdomain someone can make up, and a subdomain can be a different site run by a different company. So the answer is neverlicensed, andrelated_hosts[]names those hosts:- One operator holds them:
matchdescribes that operator’s best host there (match.match_type: related_host,match.matched_domain), so you can see which licence it is. The verdict isrelated_host_listed— orlicence_not_active/domain_not_listedwhen that licence is not active or that host is de-listed. - Several operators hold them: no
match,match_absence_reason: shared_registrable. The verdict isrelated_host_listedwhen at least one of them is listed now (anactivelink under a licence we hold), elsenot_found.
The public suffix list we use includes its private section, so on a shared hosting platform (
*.raffleentry.org.uk,*.it.com) each customer’s host is its own registrable domain — one customer’s listing says nothing about a neighbour’s. - One operator holds them:
-
A name match — only when step 2 had nothing to answer with (no other host on the registrable domain is stored, or the one operator holding them has no licence we hold): the domain’s label against operators’ trading and company names (
name_match_only), unless the label is a generic gambling word (generic_term). Hosts shared by several operators never fall through to a name match. -
Otherwise,
not_found.
related_hosts[] — hosts a register does list
Section titled “related_hosts[] — hosts a register does list”Whenever step 2 answered, the response (and the batch row)
carries related_hosts[]: up to 10 hosts on your host’s registrable domain, best
first, each with the operator it is linked to.
{ "query": { "domain": "sports.bet365.com" }, "verdict": "related_host_listed", "match": null, "match_absence_reason": "shared_registrable", "checked_jurisdictions": ["AN", "CW", "IOM", "KH", "MGA", "TGC", "UKGC"], "related_hosts": [ { "host": "bet365.com", "operator": "Hillside (New Media Malta) Plc", "operator_slug": "hillside-new-media-malta-plc", "jurisdiction": "MGA", "domain_status": "active" }, { "host": "bet365.com", "operator": "Hillside (UK Gaming) ENC", "operator_slug": "hillside-uk-gaming-enc", "jurisdiction": "UKGC", "domain_status": "active" }, { "host": "bet365.com", "operator": "Hillside (UK Sports) ENC", "operator_slug": "hillside-uk-sports-enc", "jurisdiction": "UKGC", "domain_status": "active" } ]}(Trimmed to the fields that matter here.) Each entry is a fact about that host:
domain_status is its listing under that operator, and jurisdiction the
regulator of the licence that host’s link reports (which one) — not a licence status. If one of them
is the site you meant, check that host: its own answer carries the licence and its
status. If none is, report your host as not listed in the registers we cover;
never carry a related host’s licence over to it. checked_jurisdictions and
_meta.stale_jurisdictions come with every related-host answer, as with a miss.
Hostnames we accept
Section titled “Hostnames we accept”domain is a hostname, not a URL. Before it is looked up it is trimmed,
lowercased, stripped of one trailing dot (bet365.com., the fully-qualified form,
is bet365.com), and an internationalised name is converted to the punycode form
registers and DNS use (bücher.example → xn--bcher-kva.example). What is left
must be at least two labels of letters, digits and hyphens (no underscores), each
1–63 characters, 253 in all. Nothing else is repaired: a scheme, path or port
(https://bet365.com/) is 400 not_a_valid_hostname on GET /v1/check, and more
than 253 characters 400 hostname_too_long; in a batch, either is an error row.
query.input— when what you sent differs from what we looked up (capitals, a trailing dot, a Unicode name),query.inputis your string andquery.domainthe hostname. A batch row always carries both.- IP addresses — no register lists one. An IPv4 address passes the syntax
check and answers
not_found; an IPv6 address is not a valid hostname.
match_type
Section titled “match_type”Tells you how we arrived at the match, useful for debugging and UX differentiation.
match_type | Source |
|---|---|
domain_exact | A regulator source lists the host — or its www. counterpart (matched_domain says which) — on the licence. Carries domain_association (direct or white_label). |
related_host | Your host is not stored; one operator’s other host on the same registrable domain is, and match describes that host (matched_domain). The verdict is never licensed — see which host answers. |
license_number | A ?license_number= query found the licence in a register we cover. No domain, so domain_association, domain_status and matched_domain are null. |
trading_name_fuzzy | Trigram similarity ≥ 0.55 against operators.trading_names[] after stripping the TLD. Used when the domain isn’t registered but the brand exists. 0.55 was picked empirically against the UKGC register: it catches legitimate variants (paddypower ↔ paddy-power, skybet ↔ sky-bet) while rejecting the long tail of single-syllable collisions (gold, star, royal) where the label is too generic to mean one operator. Below 0.55 we land in low-confidence territory either way; above it the trigger is stable. |
name_similarity | Last-chance similarity against operators.display_name — rarely fires for B2C domains, useful when no trading name was populated upstream. |
domain_association
Section titled “domain_association”When match_type = domain_exact, we differentiate:
direct— the licensee runs the site themselves. Theoperatorfield is the company your end-user is gambling with.white_label— the licensee has authorised a third-party brand to trade on the domain under their permit. Theoperatorfield is the licensee, not the brand. UK-licensed white-label arrangements are legal and common; surfacing the relationship lets you show “operated by Brand X under ProgressPlay’s UKGC permit”.
Fuzzy matches (trading_name_fuzzy, name_similarity) don’t populate
domain_association — we don’t have a domain row to read it from, so
the field is null.
domain_status vs status
Section titled “domain_status vs status”status is the licence; domain_status is the domain. They
move independently: a regulator can withdraw one site from a licensee’s
permitted surface while the licence itself stays active. domain_status: "delisted" means exactly that — check it before treating status: "active" as a green light for the site you were asked about.
verification_url — check us against the regulator
Section titled “verification_url — check us against the regulator”Where the regulator publishes a per-domain verification page of its
own, match.verification_url links straight to it:
- Curaçao — the CGA certificate portal (
cert.cga.cw), including/tokencluster certificates listing every approved domain under the licence. A link stored on the CGA’s legacy hostcert.gcb.cwis served oncert.cga.cw(same path; the old host redirects there). - Tobique — the TGC validation seal (
validate.thetgc.ca), which lists the seal holder’s main domain and the domain queried — not the licence’s whole website cluster.
null for regulators with no such page (UKGC, MGA, KH, AN, IOM) and for
fuzzy matches. A nightly pass re-reads the stored verification pages
oldest-first (every Tobique seal each night; each Curaçao certificate
every 2–3 days) and updates domain_status only from a clean read of the regulator’s
own words — so the URL is not just a citation, it’s the mechanism that
keeps the row fresh.
What the page itself says. Next to the link, match.verification_page_status
is the licence status that page printed at our latest read of it, verbatim — a
CGA page’s Active or Revoked, a Tobique seal’s VALID — and
match.verification_page_read_at is when we read it (null for both while we
hold no reading). When the word is not one that says “in force”, the page is
not confirmation of anything: say what it reads and when, and don’t present
the link as proof. It moves no status by itself — the link’s domain_status
comes from the register or the page’s domain list, and a revocation only from a
regulator publication (status_source_url). With an in-force word, surface the
link next to the verdict: “verify on the regulator’s own page” is the strongest
trust signal we can hand you.
jurisdictions[] — dual-licensed domains
Section titled “jurisdictions[] — dual-licensed domains”A brand can be licensed by different legal entities in different
jurisdictions at the same time (spinsup.com: Anjouan + Tobique;
me88.com: Anjouan + Curaçao). When the matched domain has more than
one (operator, jurisdiction) pair, the response carries a best-first
jurisdictions[] array — jurisdictions[0] is the pair match
reports — each entry with its own:
| Field | Meaning |
|---|---|
operator, operator_slug, jurisdiction | The licensee and its regulator. |
license_number, license_reference_is_ours | The licence number — or, when license_reference_is_ours is true (KH, TGC, IOM), our reference, never to be quoted as the regulator’s. |
status, status_qualifier | That licence’s status, and the qualifier when active is not the whole truth (provisional_under_assessment). |
domain_status, domain_association | This domain’s listing under this operator (active / delisted), and direct / white_label. |
matched_domain | The host this link is on: your host or its www. counterpart (the links of both spellings are one list). |
verification_url, verification_page_status, verification_page_read_at | The regulator’s own per-domain page for this link, where one exists, and what it printed about the licence at our latest read, verbatim, with when — as on match. |
register | { jurisdiction, last_read_at, fresh, sla_hours } — when we last read the register behind this link, and whether that read is inside its freshness window (null when the link has no licence — see below). A secondary link can rest on a stale register while match rests on a fresh one. |
Per-link status matters: the same domain can be active under one
register and delisted under another — both true at once. Before
phrasing a single-jurisdiction verdict (“licensed in Anjouan”), check
whether the array is present and report the full picture. Absent for
ordinary single-licence domains.
Which licence a domain link reports
Section titled “Which licence a domain link reports”A register links a domain to an operator, not to one of its licences, and an
operator can hold several in one jurisdiction (a UKGC licensee holds one per
activity). The licence on a link — on match and on every jurisdictions[]
entry — is the operator’s best licence that can cover a website, active
first, then the most recently verified:
- UKGC — Remote or Ancillary Remote (the register’s licence type); a Non-Remote licence is land-based and never covers a website.
- MGA — B2C only (
MGA/B2C/…); B2B and corporate (CRP) licences never do. - Curaçao — the licence the link’s
cert.cga.cwcertificate names (the certificate id is the licence number’s last segment); any of the operator’s licences when it names none of them. - Anjouan, Kahnawake, Tobique, Isle of Man — any of the operator’s licences.
An operator that holds no such licence gives the link no licence — never a
licence that could not cover the site. The listing still stands: match names the
operator and the regulator (jurisdiction, regulator_name, domain_status,
matched_domain) with license_number, license_id and status null, and the
verdict is licence_not_active — “brc-uk.com is listed … by the UK Gambling
Commission under BRC Promotions Ltd, which holds no licence that can cover a website
(its UK Gambling Commission licences are non-remote only)”. Never not_found: the
register does list the domain. And a site whose remote licence is suspended reads
licence_not_active too, even when the same company’s betting shops hold an active
non-remote licence.
Tiebreaking for equal similarity
Section titled “Tiebreaking for equal similarity”Brand names like Paddy Power trigram-match several sister companies
(PPB Counterparty, PPB Entertainment, PPB GE, Power Leisure
Bookmakers) at similarity 1.0. To keep the primary match stable
across DB reindex and VACUUM, /v1/check applies a documented
tiebreaker cascade whenever the top candidates are tied on similarity:
- similarity DESC — closeness wins first, as ever.
- has_active DESC — operators with at least one
activelicence are preferred over operators whose licences are all in a non-active state (expired, revoked, suspended, surrendered, or no longer listed in the register). - oldest_active_issued ASC — among active-licence candidates, the one whose oldest active licence issued first wins. Stability signal: a parent entity that has been licensed longest is the most useful “who actually runs this brand” answer.
- total_licenses DESC — more licences across the register → more likely a parent entity rather than a single-purpose subsidiary.
- operator_slug ASC — lexicographic final fallback. Always deterministic even when every previous rank is tied.
Clients that cache domain → operator mappings can rely on the primary result remaining stable between index rebuilds; any change in primary reflects a change in the underlying registry data, not PG query randomness.
alternatives[]
Section titled “alternatives[]”Up to 3 runner-up candidates, sorted by similarity descending.
- On
confidence: mediumorlow(a fuzzy match), these are operators with the same or similar trading name that we ranked below the primary match. - On a generic label (
match: null,match_absence_reason: generic_term), this is always[]— we refuse to guess when the label is ambiguous. - On
confidence: highand on ano_record_foundmiss, also[].
Why the generic-label filter exists
Section titled “Why the generic-label filter exists”Without it, GET /v1/check?domain=casino.org would return “Casino MK
Limited” with confidence: medium — deterministically, because “casino”
matches that trading name at similarity 1.0. But casino.org is on no
licence we read, and “Casino MK runs it” would be a guess dressed up as
an answer.
The blocklist is a substring regex over the normalised label —
casino, poker, bingo, gambling, bet, slot/slots,
sportsbook, roulette, blackjack, wager, lottery,
gaming. Matches anywhere in the label, so casino.org,
bestcasino.com, and casino-bonus.com all return match: null,
confidence: low, match_absence_reason: generic_term and empty
alternatives[]. Domains we read on a licence still resolve even when
they contain one of these keywords (bet365.com, pokerstars.com,
casino.com) because the domain-exact match runs before the generic
gate — and then, as always, status and domain_status say whether
that licence covers the site today. The related-hosts check runs before it
too: sports.bet365.com is related_host_listed, not generic_term.