Skip to content

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.

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:

verdictWhenWhat it means
generic_termmatch is null, match_absence_reason: generic_termA 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_listedmatch is null, match_absence_reason: shared_registrable, and at least one host in related_hosts[] is listed nowYour 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_foundmatch is null, match_absence_reason: no_record_found — or shared_registrable with no related host listed nowIn none of the registers we cover. Not a finding that the site is unlicensed.
name_match_onlyconfidence is medium or lowA name resembled an operator’s; no licence we hold lists this domain. status is that operator’s, not this site’s.
licence_not_activestatus is not active — whether or not the regulator still lists the domain on itverdict_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_listedstatus: 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_listedmatch.match_type: related_host — status: active, that host listedYour 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_provisionalstatus: active, domain listed, a status_qualifier is setLicensed now, provisionally — today a Curaçao licence the CGA keeps in force pending its final assessment.
licensedstatus: active, domain_status: active, no qualifierThe 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”.

confidenceWhat it meansRender as
highA 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.
mediumDomain 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.
lowA 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.

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_reasonMeaningSay to your user
generic_termThe 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_foundA 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_registrableYour 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:

  1. The site: the exact host and its www. counterpart. www.example.com and example.com are the same site, and registers list either spelling (the UKGC lists www.betfair.com, Anjouan spinsup.com). The links of both are ranked together, so both spellings get the same answer — a full domain_exact match, with match.matched_domain naming the host the register lists (paddypower.com answers for www.paddypower.com). Only a leading www. is dropped or added this way.

  2. Other hosts on the same registrable domain — the domain under its public suffix: bet365.com for sports.bet365.com, example.co.uk for m.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 never licensed, and related_hosts[] names those hosts:

    • One operator holds them: match describes that operator’s best host there (match.match_type: related_host, match.matched_domain), so you can see which licence it is. The verdict is related_host_listed — or licence_not_active / domain_not_listed when that licence is not active or that host is de-listed.
    • Several operators hold them: no match, match_absence_reason: shared_registrable. The verdict is related_host_listed when at least one of them is listed now (an active link under a licence we hold), else not_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.

  3. 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.

  4. Otherwise, not_found.

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.

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.input is your string and query.domain the 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.

Tells you how we arrived at the match, useful for debugging and UX differentiation.

match_typeSource
domain_exactA regulator source lists the host — or its www. counterpart (matched_domain says which) — on the licence. Carries domain_association (direct or white_label).
related_hostYour 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_numberA ?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_fuzzyTrigram 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_similarityLast-chance similarity against operators.display_name — rarely fires for B2C domains, useful when no trading name was populated upstream.

When match_type = domain_exact, we differentiate:

  • direct — the licensee runs the site themselves. The operator field 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. The operator field 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.

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 /token cluster certificates listing every approved domain under the licence. A link stored on the CGA’s legacy host cert.gcb.cw is served on cert.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.

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:

FieldMeaning
operator, operator_slug, jurisdictionThe licensee and its regulator.
license_number, license_reference_is_oursThe 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_qualifierThat licence’s status, and the qualifier when active is not the whole truth (provisional_under_assessment).
domain_status, domain_associationThis domain’s listing under this operator (active / delisted), and direct / white_label.
matched_domainThe 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_atThe 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.

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.cw certificate 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.

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:

  1. similarity DESC — closeness wins first, as ever.
  2. has_active DESC — operators with at least one active licence are preferred over operators whose licences are all in a non-active state (expired, revoked, suspended, surrendered, or no longer listed in the register).
  3. 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.
  4. total_licenses DESC — more licences across the register → more likely a parent entity rather than a single-purpose subsidiary.
  5. 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.

Up to 3 runner-up candidates, sorted by similarity descending.

  • On confidence: medium or low (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: high and on a no_record_found miss, also [].

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.