Changelog
API-level changes only. Internal refactors, scraper updates, and infra changes don’t appear here unless they surface in a response shape or error behaviour.
Versioning policy
Section titled “Versioning policy”All /v1/* endpoints are maintained indefinitely. We do not silently
retire versioned routes. Future major versions will be introduced under a
new path (/v2/*), with v1 and v2 running in parallel for a minimum of
12 months after v2 launch. Migration guidance lands in this changelog at
v2 launch.
Breaking-change policy
Section titled “Breaking-change policy”- Breaking changes always land behind a new URL path (
/v2/...) — we never break a stable endpoint’s response shape in place. - Additive changes (new fields, new endpoints, loosened validation) ship at any time without a version bump.
- Deprecation flow for individual fields inside a stable version:
Deprecationheader (RFC 9745) on affected responses the day the field is marked — a structured-field date, e.g.Deprecation: @1790812800.Sunsetheader (RFC 8594) with the removal date as an HTTP-date, minimum 90 days out.- Changelog entry below with the migration guidance.
- For agents:
Link: …; rel="deprecation"(RFC 9745) surfaced alongside the header. - No field is deprecated today, so none of these headers is sent.
1.21.0 — 2026-10-01
Section titled “1.21.0 — 2026-10-01”- Connect from claude.ai, ChatGPT and Claude Code by signing in — no key to
paste. Add
https://mcp.igregulator.io/mcp/accountas a custom connector and sign in with your iGregulator account; the app gets read-only licence lookups — all ten MCP tools — on your plan, counted against your monthly quota exactly as an API key’s calls are. The endpoint is an OAuth 2.1 protected resource (RFC 9728 metadata);app.igregulator.iois its authorization server (RFC 8414 metadata): dynamic client registration, PKCE (S256only), one scope (mcp:read), one-hour opaque access tokens, rotating refresh tokens,isson every authorization response. Without a token it answers401withWWW-Authenticate: Bearer resource_metadata="…", scope="mcp:read". It also accepts an API key. See MCP server. - Connected apps under API keys: every app you approved, where it returns to, when you approved it and when it last got a token — and Revoke, which stops its tokens on the next request.
/mcpis unchanged: anonymous forcheck_domain,search_operators,list_jurisdictionsandcheck_coverage(10/hour per IP), or an API key for all ten.auth.md, the MCP server card and/.well-known/mcp.jsonnow describe both ways in.
1.20.0 — 2026-10-01
Section titled “1.20.0 — 2026-10-01”Fixes from a blind test by four context-free testers (a KYB analyst on the REST API, a developer integrating it, an assistant on the MCP server, and an edge-case tester). Every field that existed keeps its name and type. Answers that change for the same request are marked Behaviour change.
/v1/check
Section titled “/v1/check”- Behaviour change — the licence comes before the listing. A licence that is
not
activeislicence_not_activeeven when the regulator has also de-listed the domain. It used to bedomain_not_listed, which hid a revocation behind “no longer listed”.verdict_detailnow says both. 344 stored hosts changed verdict this way (211 surrendered, 93 not_in_register, 18 revoked, 18 expired, 3 pending, 1 suspended). - Behaviour change —
www.XandXare one site. Both spellings are looked up together, and both get the same answer, from the one the register lists (match.matched_domain). 712 licensed domains queried withwww.and 24www.-listed ones queried bare (betfair.com) used to answernot_foundorgeneric_term. - Behaviour change — new verdict
related_host_listed, in place oflicensedfor other subdomains. A host that isn’t listed, on a domain where other hosts are, is never answered as licensed: a listing covers the host the regulator names (zz-fake.unibet.comused to answerlicensed).- One operator holds those hosts:
matchdescribes that operator’s host, with the newmatch_type: related_host. The verdict isrelated_host_listed, orlicence_not_active/domain_not_listed. - Several operators:
matchisnull, with the newmatch_absence_reason: shared_registrable. The verdict isrelated_host_listedif one of the hosts is listed now, elsenot_found. No name match is tried. - Both cases: the new
related_hosts[]lists those hosts (host,operator,operator_slug,jurisdiction,domain_status; at most 10). The registrable domain is taken from the Public Suffix List with its private section, so customers of a shared hosting platform stay separate. See which host answers.
- One operator holds those hosts:
- Behaviour change — strict
as_of. Only a realYYYY-MM-DDdate or an ISO-8601 datetime with a UTC offset is accepted.2026/09/22,2026-02-30and2026-09-22T10:00(no offset) now return400 invalid_query(field: as_of); they used to answer200for a different instant. Today’s date now answers as of now; it used to be refused as “future”. The same applies on/v1/licenses/{id}and/v1/operators/{slug}. as_ofsays which licence it answers for. On/v1/check,as_ofgainsscope: "licence",license_id,license_number,operatorand, on a domain query,link_note: we do not record when a domain was linked to a licence, so the answer is that licence’s status on the date.verdictstill describes today.verdict_detailnow opens with the answer for your date (“On 2026-03-01 we were not yet tracking … licence …, so its status then is unknown. Today: …”).- Licence numbers. Case, spaces and separators are ignored.
- Behaviour change: a licence-number match is
match_type: license_number(it wasdomain_exact). - An earlier UKGC version of a number we hold (
055148-R-331498-001) finds the licence as the register lists it now. The newsuperseded_number({ requested, current, note }) says so, andverdict_detailopens with the note.
- Behaviour change: a licence-number match is
- Hostnames.
- The input is trimmed and lowercased, one trailing dot is dropped, and a
Unicode name is converted to punycode.
query.inputechoes what you sent when it differs. - A
domainlonger than 253 characters is400 hostname_too_long.
- The input is trimmed and lowercased, one trailing dot is dropped, and a
Unicode name is converted to punycode.
- Behaviour change — batch: one bad entry (not a string, too long, not a
hostname) is a per-row
error(invalid_input/invalid_hostname) witherror_detail, never a whole-request400. Every row carriesquery.input, andrelated_hosts[]where it applies.GET /v1/check/batchis now405withAllow: POST(it was404). jurisdictions[]rows gainlicense_reference_is_ours,matched_domainandregister({ jurisdiction, last_read_at, fresh, sla_hours }— that link’s own register freshness).- Freshness wording.
verdict_detaildates the read that listed the domain. A stale register read gets its own clause, naming the register and the date of its last read. - Scoping.
checked_jurisdictionsand_meta.stale_jurisdictionsnow also come with a name match and a related-host answer. - What the regulator’s own page says.
verification_page_statusandverification_page_read_atare added onmatchandjurisdictions[]: the licence status the page atverification_urlprinted at our latest read, verbatim (Active,Revoked,VALID), and when. A word that isn’t “in force” means the page is not proof. It moves no status. Acert.gcb.cwlink is served oncert.cga.cw.
Licences, operators, jurisdictions
Section titled “Licences, operators, jurisdictions”status_qualifierandlicense_reference_is_ourson/v1/licenses/{id},licenses[]of/v1/operators/{slug}, and/v1/operators/{slug}/licenses, as on a/v1/checkmatch. A provisional Curaçao licence no longer reads as plainactivethere.- Behaviour change — dates:
issued_dateandexpiry_dateareYYYY-MM-DD, the day the register prints. They were midnight-UTC timestamps (2026-06-09T00:00:00.000Z). domains[]on/v1/operators/{slug}carryverification_urlandverification_page_status/verification_page_read_at.- Regulatory actions:
/v1/operators/{slug}/regulatory-actionsadds_meta._meta.notesays what the list does and doesn’t mean. On an empty list, it says that is not a clean record._meta.sources_readlists the UKGC, MGA and CGA publications we read.
- Behaviour change:
/v1/jurisdictions/{code}/operatorswith an unknown code is404 jurisdiction_not_found. It was200with an empty list. - Jurisdiction codes and operator slugs resolve in any case (
ukgc,Flutter-UK-Limited). - Keyless 429:
X-RateLimit-Resetis the top of the next UTC hour (it washh:59:59). The body addsdetails.limitanddetails.reset_at. - Export: the domains export’s
standinggainslicensed_provisional(link and licenceactive, with a status qualifier). - OpenAPI:
info.versionis the API version (1.20.0).- 401/402/429 are documented on every keyed operation.
- 400s are documented on
/v1/licenses/{id}and/v1/operators/{slug}.
- Licence history notes no longer point at an internal file.
MCP server
Section titled “MCP server”- Tool output uses tool names. API sentences that named a REST route (“see GET /v1/check”) now name the tool instead.
get_operator:- It returns up to 50 domains by default, listed links first, with
domains_total,domains_returnedanddomains_more.domains_limit(up to 1,000) asks for more. - Its licences carry
status_qualifierandlicense_reference_is_ours.
- It returns up to 50 domains by default, listed links first, with
- Tool input:
get_jurisdictiontakes any case.get_licensegiven a licence number explains that it needs the UUID, and where to get it.
- New fields pass through
check_domainandcheck_domain_batch:related_hosts,superseded_number,query.input,error_detail, the newas_ofandjurisdictions[]fields, andverification_page_status. - Empty regulatory actions lead with the
note.
Data — registers and corrections
Section titled “Data — registers and corrections”Corrections applied on 2026-10-01 at 01:19 UTC. Nothing was deleted: wrong events
are marked corrected_at and stay on the record (see
corrections).
- 755
→ revokedevents withdrawn. They sat on licences the regulator lists as given up: 711 UKGC licences the Commission lists asSurrendered, and 44 Curaçao licences “revoked at the request of the operator”.as_ofinside those windows now answerscorrected. When a mapping fix re-reads the same wording as a different status, the UKGC and CW loaders now withdraw the old reading themselves. - Curaçao “Assessment in progress”. 148
active → pendingevents, written under the old rule, are withdrawn; the CGA keeps such a licence in force. 13 licences still heldpendingare back toactive(provisional_under_assessment), citing the latest OGL register read, each with a history row explaining why. - 95 register domains stored as URLs (
https://49s.co.uk/,www.winkbingo.com/) are re-keyed to their hostname, so they now match. Loaders normalise listings: scheme, case, trailing slash and dot, punycode. A listing of a page on a shared host stays a page listing and never licenses the bare host. - Kahnawake: the regulator website is
https://gamingcommission.ca; the old value did not resolve.
Loader changes take effect at each register’s next read:
- Kahnawake:
upstream_statusisnullfor KH licences. The KGC’s lists publish no status, and “Active” was our word. - Anjouan and Tobique: dates now follow each read — AN expiry and issue dates and licence types, TGC expiry dates. Renewals were stuck a year behind (319 AN licences, 34 TGC). Tobique status provenance also moves on a read of an unchanged page.
- Malta: a verification page whose status line and licence row name different
statuses is
unknown, with both words kept, rather than either one. - Curaçao certificate pages: the verifier records the status each page prints,
which feeds
verification_page_status. - UKGC regulatory actions: a “payment in lieu of a financial penalty” now fills
fine_amount_cents.
- Every client example branches on
verdictand quotesverdict_detail. - New pages or sections:
- which host answers
- hostnames we accept
- what a batch row carries and leaves out
as_ofdate formats
- Keyless counters are per route, and MCP tools count against the route they call.
- Only
corrected_atmeans an event was withdrawn. - Example licence numbers are current, and the headline counts come from
/v1/stats.
1.19.0 — 2026-09-30
Section titled “1.19.0 — 2026-09-30”- Embeddable licence badge —
https://app.igregulator.io/badge/<domain>.svg. An SVG of the verdict/v1/check?domain=returns for the domain (same link, same rank), with the domain and the date of our last read of the record on it. Green only for anactivelicence on a domain link the regulator lists; amber for a provisional Curaçao licence, or when our last read is older than the register’s freshness target (it then says “last verified”); grey for every other licence status (named as what it is), a de-listed domain (“no longer listed by Anjouan”; the regulator is named) and a miss (“not found in the 7 registers we cover”). No licence number for Kahnawake, Tobique or the Isle of Man, whose registers publish none.?style=largedraws a two-line card with the licence number where there is one. Cached for an hour (Cache-Control: public, max-age=3600, s-maxage=3600); a non-hostname is a 400 badge. Free, no key. Every brand page has the embed code. See Licence badge.
1.18.0 — 2026-09-30
Section titled “1.18.0 — 2026-09-30”Additive: every existing field keeps its name, type and meaning.
verdictandverdict_detailon/v1/checkand every batch row.verdictis the answer in one field —licensed,licensed_provisional,domain_not_listed,licence_not_active,name_match_only,not_foundorgeneric_term— derived fromstatus,domain_status,status_qualifierandconfidenceby the rules on confidence scoring.verdict_detailis one sentence to quote as it stands: it names the regulator, operator and licence, says when we last read the listing, scopes a miss to the registers we cover and names any that are stale, and never calls anything “unlicensed”. In blind tests, agents givenstatus: active,confidence: highanddomain_status: delistedanswered “licensed”; that response now saysdomain_not_listed.- New
matchfields:regulator_name(e.g.Curaçao Gaming Authority),license_id(the UUID for/v1/licenses/{id}),license_reference_is_ours(truefor KH, TGC and IOM, whoselicense_numberis our reference),status_source_urlandstatus_observed_at(the status’s own provenance, as on/v1/licenses/{id}),matched_domain(the host the register lists — it can differ from the query, e.g.www.) anddomain_last_listed_at(when a regulator source last listed that host on the licence;nullonce it is de-listed — we keep no “de-listed since” time we could stand behind). _metaon/v1/check:checked_at(when the answer was computed, on every response);register—{ jurisdiction, last_read_at, fresh, sla_hours }for the matched jurisdiction, the same freshness rule as/v1/health/coverage; and, on a miss or a name-only match,stale_jurisdictions—[{ code, last_read_at }]for the covered registers past their SLA, each of which weakens a “not found”.scraped_atis unchanged; on a name match or a miss it has always been the request time, and is now documented as such.- Batch: each row carries
verdict,verdict_detailand, when a jurisdiction matched,_meta.register;_meta.checked_atand_meta.stale_jurisdictionsare sent once at the top.
1.17.0 — 2026-09-30
Section titled “1.17.0 — 2026-09-30”GET /v1/export/{dataset}— CSV/JSON export (Pro and above). A whole dataset as one streamed file:licences(one row per licence),operators(one per legal entity) ordomains(one per domain ↔ operator link), as CSV (?format=csv, the default) or NDJSON (?format=json, a_metaline first), optionally sliced by?jurisdiction=and?status=. Every row carries its status provenance (status_source_url,status_observed_at,last_listed_at,not_listed_since) and alicense_number_kindthat marks the KH, TGC and IOM numbers as iGregulator references. Headers:X-Dataset-Generated-At,X-Export-Row-Count,X-Export-Daily-*. Current state only:as_ofis a400(as_of_not_supported), as is any parameter exports don’t take. See export.- Plans. Pro gets 10 exports per UTC day, Business 100, Enterprise no cap —
per account, shared with the dashboard’s new Export button. An export counts as
one request against the monthly quota. Starter and legacy trial keys get
402 payment_required,details.reason: "export_requires_pro"(withX-Upgrade-URL); over the daily cap →429 rate_limited,details.reason: "export_daily_limit_reached", withRetry-After. Pro isn’t open yet — email founder@igregulator.io. - MCP: no export tool (a file download isn’t a tool call).
1.16.0 — 2026-09-30
Section titled “1.16.0 — 2026-09-30”GET /v1/stats— headline counts, each with its definition. Licence records (licences.total,licences.by_status— every status present, onlyactivemeans licensed now); per jurisdiction its licences, active licences, when we last read its register and whether that read is inside its 24 h / 48 h window (the same rule as/v1/health/coverage); operators with at least one licence record; domains with an active link (domains.linked) and how many of those carry the regulator’s own certificate or seal page (domains.regulator_verified); brand pages (the /brands directory’s total); licence status changes a scraper read in the last 30 days (status_changes_30d— first sightings, data-only updates, withdrawn events, our correction migrations, and a read that only puts a status back after a withdrawn event are excluded); withdrawn events (corrections); the latest register read; andgenerated_at. The body’sdefinitionssays what each number counts. No key and no per-IP cap: every caller gets one payload, recounted at most every 10 minutes (Cache-Control: public, max-age=300). The homepage prints it under the hero.
1.15.0 — 2026-09-30
Section titled “1.15.0 — 2026-09-30”- Isle of Man coverage. The Isle of Man Gambling Supervision Commission
(
IOM, countryIM) is the seventh jurisdiction. Its Online Gambling Licensee Register (isleofmangsc.com) is read nightly at 04:25 UTC: 53 licences, allactiveon the first read, across the three licence types the Commission names (Full,Network Services,Software Supply), and 50 domain links on 27 licensees. The register page is the source; the.xlsxit links is read as a cross-check, and a licence the two disagree on isunknownrather than a guess. A status other thanactivecomes only from the Commission’s Former Licence Holders page:Surrendered→surrendered,Cancelled(a cancellation under OGRA 2001 s.13) →revoked; any other wording sets nothing, and a licence that merely leaves the register becomesnot_in_register. The Commission publishes no per-domain verification page, soverification_urlisnullfor IOM. license_numberfor KH, TGC and IOM is our reference. The Kahnawake, Tobique and Isle of Man registers publish no licence number (the Isle of Man publishes no company number either). The value we return —KH/IG/<slug>,KH/CSPA/<slug>,TGC/B2C/<slug>,TGC/B2B/<slug>,IOM/OGRA/<name key>— identifies the record in our API and round-trips through/v1/check?license_number=, but no regulator issued it: don’t quote it as one. The field is unchanged; the OpenAPI spec now says so./v1/health/coverage: IOM’s freshness SLA is 24 h, like the other single-fetch registers (AN, TGC) — it had fallen back to the 48 h default — and itsdomain_coveragecountsFulllicensees as the consumer-facing subset (14 of 16 have a domain). See coverage methodology.checked_jurisdictionson a/v1/checkmiss has includedIOMsince the first load, and/v1/jurisdictionslists it with its licence types.- MCP: tool descriptions and server instructions name seven jurisdictions, and
get_jurisdictionacceptsIOM. - The Terms of Service (§1) now name the Isle of Man GSC among the registers we read. A coverage update only: nothing about your rights or obligations changed.
1.14.1 — 2026-09-29
Section titled “1.14.1 — 2026-09-29”- Fixed:
GET /v1/watchlist/eventswas empty without a webhook endpoint. It returned only events queued for your endpoints. It now returns every operator event (license.*,regulatory_action.added) for the operators on your watchlist, endpoint or not, plus anything queued for your endpoints, as before. Same envelope andevent_idas the webhook. An event we have since withdrawn as wrong is not served. - Fixed: each page of
/v1/watchlist/eventsrepeated the previous page’s last event, and withlimit=1has_morenever turnedfalse. Saved cursors keep working. - Fixed: a legacy
trialkey got402on endpoints that work without a key (/v1/jurisdictions,/v1/operators/search,/v1/health/coverage). They now answer it, within the key’s 1,000/day cap. - Limit and plan errors no longer send you to the pricing page (paid tiers are not open yet); they name founder@igregulator.io.
Docs corrections — 2026-09-29
Section titled “Docs corrections — 2026-09-29”Documentation only — nothing changed on the wire. Pages that described the API differently from how it behaves now match the code.
- Rate-limit headers. On keyed calls
X-RateLimit-Limit/-Remaining/-Resetdescribe your monthly quota, not the per-second limit;X-RateLimit-Policyis only evertier=unlimitedortier=authenticated, andRateLimit-Policyis sent on keyless calls only. See rate limits. - Enforcement. Monthly quotas and per-second limits are enforced now. There is
no 10,000/day cap on every plan: the daily cap is 1,000 per key and applies only to
legacy
trialkeys, which are also limited toGET /v1/check. Business is 100 req/s, not uncapped. - Errors. Error handling is now the full
code+details.reasonmatrix. The keyless 429 body is{ reason: "quota_exceeded", suggestion }(nolimit/window_seconds/upgrade_url); a monthly quota 429 isquota_exceeded, notrate_limited; a rejected webhook URL is400 invalid_querywith an SSRFdetails.reason(there is noinvalid_webhook_urlcode). - Webhooks.
webhook.endpoint_degradedis reserved and not sent — pollGET /v1/webhooks/:id/deliveries?status=abandonedand watchX-iGregulator-Missed-Deliveriesinstead.license.status_changedalways has aprevious_status, and is not sent for a move toexpired(license.expired) or for a new licence (license.issued).api_versionis a single value; per-subscriber pinning is not built. Seven attempts means the first plus six retries.GET /v1/watchlist/eventsreturned only events queued for your active webhook endpoints (fixed in 1.14.1). The Python polling example now runs. - Pagination and endpoints.
/v1/operators/:slug/licensesand/v1/licenses/:id/historyare not paginated; search and regulatory actions default to 20 rows (max 100);/v1/jurisdictions/:code/operatorsis ordered by internal id, not by name. Endpoints now maps batch, regulatory actions, watchlist, webhooks and/v1/health/coverage. Operator search matches display name, slug and registered name — not trading names. - MCP. Rate-limit headers do not pass through the MCP server; the API’s error
body does, as
{ http_status, … }. Quotas are per account, not per key. Deprecationis RFC 9745 andSunsetis RFC 8594 — we had cited RFC 9745 forSunset. Neither header is sent today.as_ofand_meta. The AI-agents guide now lists everyknowledgevalue (corrected,no_such_license,no_license_resolvedwere missing)._meta.source_modified_atis alwaysnulltoday.- Numbers refreshed to 2026-09-28: 6,597 licences, 5,638 operators, 12,009 domains, per-jurisdiction domain coverage.
- Verification-page cadence. “Re-read nightly” overstated it: the verifier re-reads up to 500 certificates per jurisdiction per night, oldest first — every Tobique seal nightly, each Curaçao certificate every 2–3 days.
- Regulatory actions. An empty
GET /v1/operators/:slug/regulatory-actionsmeans no action is linked to that operator, not a clean record — many published actions are not matched to an operator yet. - The 1.13.0 entry below said to dedupe on
type+data; the envelope field isevent. - Freshness wording. The docs intro no longer says “updated within 24 hours of regulator changes”: every register is read daily, each status shows when we last read it, and live per-register freshness is at igregulator.io/status.
- Removed links to our source repository, which is private — they answered 404. The MCP page no longer calls the server open source; tool feedback goes to founder@igregulator.io.
1.14.0 — 2026-09-28
Section titled “1.14.0 — 2026-09-28”- The MCP server works without a key.
initializeandtools/listno longer need anAuthorizationheader, and four tools answer anonymously —check_domain,search_operators(top 3),list_jurisdictions,check_coverage— under the REST API’s public limit of 10 requests per hour per IP (your IP, not the gateway’s). The other six say in their descriptions that they need a free key, and answer an anonymous call with a structured error plus ahow_to_fixpointing at signup. A malformedAuthorizationheader is still a 401. The discovery manifests carryauth.required: falseand ananonymousblock.
1.13.3 — 2026-09-28
Section titled “1.13.3 — 2026-09-28”status_observed_atis the latest read that still says the status. OnGET /v1/licenses/{id}and the licences inGET /v1/operators/{slug}, it (and the evidence snapshot behind it) now moves every time we re-readstatus_source_urland find the same status. It used to stay at the read that first set the status for licences whose status comes from the main register — most UKGC licences cited April. Statuses from enforcement registers and MGA’s verification pages already behaved this way. When a status changed is inlicense_history.
1.13.2 — 2026-09-27
Section titled “1.13.2 — 2026-09-27”upstream_statusno longer contradictsstatus. Whenstatusisrevoked,suspended,surrenderedorexpiredand the register’s word says the licence is in force (valid,Active,Licensed,Assessment in progress…),upstream_statusis nownull. The status came from a different publication — an enforcement register, an advisory notice — and the register row had not caught up; served side by side, the word read as the regulator disagreeing. 10 licences were affected (Anjouan, Kahnawake, Curaçao). A negative word next toactiveis always shown. The raw register row is unchanged inraw_dataon/v1/licenses.
1.13.1 — 2026-09-27
Section titled “1.13.1 — 2026-09-27”- Refused and failed requests no longer count against the monthly quota. Any 4xx (bad parameter, unknown slug) or 5xx response is not charged, and its quota headers report usage without it. The per-second burst limit still counts them. Until now every response was charged, although the quota was always meant to bill answers only.
1.13.0 — 2026-09-27
Section titled “1.13.0 — 2026-09-27”Additive on the wire. Two data corrections change what some Curaçao licences answer.
- New field
status_qualifieron/v1/check→matchand on eachjurisdictions[]entry, and on the MCPcheck_domainverdict. Set whenstatusis right but not the whole truth;nullotherwise. Today it has one value:provisional_under_assessment— a Curaçao licence the CGA register lists as “date Assessment in progress”. Under the CGA’s transitional regime a provisional licence past its stated term stays in force until the CGA communicates a final decision, sostatusstaysactive(andexpires_atmay be in the past). Treat it as licensed, flag that a decision is outstanding, and re-check. 263 licences carry it today. We considered answeringpendingand did not: that would tell you a licensed operator is not licensed. - Curaçao: “Revoked as of …, at the request of the operator” is now
surrendered(43 licences), notrevoked. The CGA’s verb, the operator’s act — the same distinction UKGCSurrenderedgot in 1.10.0.upstream_statuskeeps the CGA’s exact words. A licence the CGA also lists in its Enforcement Register staysrevoked. - Webhooks: fixed a bug that re-sent the most recent event every minute. The
emitter’s cursor lost microseconds and re-matched its own last row. If you
received repeated events whose
datawas identical, they were duplicates of one event — dedupe onevent+data. - Provenance: those 263 provisional licences now cite the latest register read as their status source, and 54 Curaçao status changes that were never written to licence history now have a history row saying so.
1.12.0 — 2026-09-20
Section titled “1.12.0 — 2026-09-20”Additive on the wire. One of these changes what as_of answers for a small set of
licences — read the first bullet if you use point-in-time.
as_ofno longer serves back events we withdrew. Newknowledgevalue:corrected, withstatus_as_of: nulland acorrectionobject (changed_at,withdrawn_status,corrected_at,note). The 93 revocations we had inferred from a licence dropping off a register were corrected on 2026-09-17 (see 1.10.0), but history is never deleted here, andas_ofread the latest event without checking whether it had been withdrawn — so a date inside one of those windows still answeredrevoked/observed. It now answerscorrected. We do not fall back to the previous status either: that would reportactivefor a period in which the register did not list the licence. If you branch onknowledge === 'observed', nothing changes for you —correctedlands in your “unknown” path, which is where it belongs. See point-in-time./v1/check—_meta.source_urlnow cites the page that published the matched licence’s status, as/v1/licenses/{id}has since 1.11.0. For most licences that is still the register. Where it is not — a Curaçao revocation from the CGA Enforcement Register, an MGA status from the company’s verification page, a certificate-backed Curaçao licence — you now get that page instead of a register that never said so.- Fixed:
?as_of=on/v1/checkwas silently ignored. The parameter has been documented since point-in-time shipped, but on/v1/check— and therefore on the MCPcheck_domaintool — it never reached the handler: the response carried noas_ofobject andmatch.statuswas simply today’s./v1/operators/{slug}and/v1/licenses/{id}were not affected. It works now, and a malformed or futureas_ofanswers400 invalid_queryinstead of200. If you relied on/v1/check?as_of=for a historical verdict, you were reading the current status — check for theas_ofobject in the response and re-run those lookups. - MCP:
get_operatorreturnslicense_idon each licence. It is the UUIDget_licenseandget_license_historytake, and no tool returned it, so those two were unreachable from an agent. Their descriptions now cover the provenance fields (status_source_url,status_observed_at,not_listed_since,last_listed_at) and corrected history events.
1.11.0 — 2026-09-18
Section titled “1.11.0 — 2026-09-18”Additive. Every status now says where it came from, and one billing bug is fixed.
- Per-status provenance on licences —
status_source_url,status_observed_atandlast_listed_atonGET /v1/licenses/{id}and on the licences insideGET /v1/operators/{slug}. A licence’s register (source_url) is often not where its status was published: a Curaçao revocation comes from the CGA Enforcement Register, an Anjouan suspension from the Authority’s suspended-licences page, a Kahnawake termination from an advisory notice.status_source_urlis that page;last_listed_atis when we last saw the licence in its register. _meta.source_urlfor a licence now cites the status’s source (falling back to the register when they are the same page). If you show “source” next to a status, this is the URL that actually published it.- Evidence on history events —
GET /v1/licenses/{id}/historygainssnapshot_sha256andsnapshot_fetched_at: the hash of the bytes we read and when we first read them. It is the hash, not a file — checkable against your own copy of the regulator’s page.nullwhere we hold no stored copy — we started keeping the bytes on 2026-09-17. - Enforcement statuses are read from the regulator’s own publication on every
run, in every jurisdiction that has one: AGA’s revoked / suspended lists, the
CGA Enforcement Register, KGC’s advisory notices, the MGA enforcement register.
Tobique publishes none, so a Tobique licence can only ever be
activeornot_in_register. A “Notice of Cancellation” from the MGA is a notice, not a cancellation, and changes no status. - MGA statuses are now read, not assumed. Every MGA licence used to be
activebecause the register “only lists live licensees”. We now read the per-licence word the MGA publishes:Licensed/Approved→active,Surrendered→surrendered,Expired→expired,Suspended/Voluntary Suspended→suspended(verbatim wording inupstream_status). A handful of MGA licences changed status as a result. A word we have never seen maps tounknown, never to a default. GET /v1/operators/search—_meta.scraped_atis now when we last read a register about that operator (its freshest licence check). It used to be the operator row’s own timestamp, which only moves on a rename — so it claimed April for records verified three days ago._meta.freshness_rangefollows.- Fixed: authenticated calls to
/v1/operators/{slug},/v1/licenses,/v1/watchlistand/v1/webhookswere metered twice (fixed 2026-09-16). The per-second limit behaved as half your plan’s, and monthly quota was consumed 2× on those routes. If you saw unexplained 429s on operator lookups, that was us. September usage counters for affected keys may still read high until the 1 October reset.
1.10.0 — 2026-09-17
Section titled “1.10.0 — 2026-09-17”Additive on the wire, but two status values changed on existing records. If you
store our status verbatim, re-read the licences you hold.
- Two new
license_statusvalues:not_in_registerandsurrendered. Neither is a revocation, and neither isactive.not_in_register— the regulator’s public register no longer lists the licence, sincenot_listed_since. Registers drop rows for many reasons — expiry, surrender, renumbering, a bad day on their side — and none of them is published, so we do not publish one either.surrendered— the operator gave the licence up. UKGC publishes it verbatim (“Surrendered”); Kahnawake calls it a voluntary termination.
- What changed on existing records. Licences that had dropped off a register were
reported as
revoked. That asserted an enforcement decision no regulator had made, and it was wrong: 80 licences across six jurisdictions carried it. They are nownot_in_register, except the seven where a regulator does publish an explicit decision (those arerevokedorsuspended, sourced to that publication). Separately, 627 UKGC licences published asSurrenderedwere mapped torevokedand are nowsurrendered. Every corrected record’s history says what happened and when, and the superseded event is kept and marked corrected rather than deleted. - If you branch on status,
if (status === 'revoked') block()now misses these values — by design, because they are not revocations. Approve only onactiveand route the rest to a human.GET /v1/licenses/{id}/historygainsnote,corrected_atandcorrection_note; licences gainnot_listed_since. upstream_statusisnullwhen a licence isnot_in_register: there is no current regulator wording for a licence the register does not list, and repeating the last one we read produced pages that said “revoked · upstream: valid”.
1.9.0 — 2026-07-16
Section titled “1.9.0 — 2026-07-16”All additive — no breaking changes.
verification_urlon/v1/check. Where the regulator publishes a per-domain verification page — a Curaçao certificate (cert.cga.cw) or a Tobique validation seal (validate.thetgc.ca) —match.verification_urllinks straight to it, so you can confirm any verdict against the primary source in one click.nullwhere the regulator has no such page (UKGC, MGA, KH, AN). We re-read every stored verification page nightly and movedomain_statusonly on a clean read of the regulator’s own words — in both directions, never on a guess.- Multi-jurisdiction domains:
jurisdictions[]on/v1/check. The data model is now many-to-many — a brand can be licensed by two legal entities in two jurisdictions at once (spinsup.com: Anjouan + Tobique; me88.com: Anjouan active + Curaçao). When a matched domain has more than one (operator, jurisdiction) pair, the response carries a best-firstjurisdictions[]array with per-linkstatus,domain_status,domain_association, andverification_url. Per-link status means “delisted in Curaçao” can no longer clobber “active in Anjouan” — both are true at once, and now both are visible. Absent for ordinary single-licence domains. - Curaçao domain coverage 0% → 91%. Curaçao publishes no bulk domain list, so we
built the harvest around the regulator’s own verification portal: the CGA
/tokenregistry (which lists each licence’s complete approved-domain cluster — and is more complete than the published OGL PDF) plus archived certificate URLs. ~3,500 CW domains loaded, every one carrying its regulatorverification_url, kept fresh by the nightly re-verification pass and a weekly/tokendiscovery sweep. - Tobique domain coverage 0% → live. Same authoritative-seal approach against
validate.thetgc.ca: one seal page lists the licence’s whole registered-website cluster. Weekly headless-render discovery finds new seals (they’re JS-injected in casino footers, invisible to plain fetches); the nightly shared verifier keeps every found seal current. Coverage is growing run by run — see coverage methodology. - MCP
check_domainoutput now includesverification_urland (when present) the compactjurisdictions[]array, so agents cite the regulator’s own page and never phrase a single-register verdict about a dual-licensed brand. - OpenAPI:
CheckMatch.verification_url, theDomainJurisdictionschema andCheckResult.jurisdictionsare documented in the spec.
1.8.0 — 2026-05-29
Section titled “1.8.0 — 2026-05-29”All additive — no breaking changes.
- Batch domain check. New
POST /v1/check/batch(authenticated) resolves up to 100 domains in one request; each result mirrors the single-check shape,checked_jurisdictionsis returned once at the top, and a malformed hostname comes back as a per-rowerrorwithout failing the batch. See Batch domain check. match_absence_reason+checked_jurisdictionson/v1/check. Whenmatchisnull, the response now says why —generic_term(ambiguous label) vsno_record_found(checked, not present) — and lists the exact registers checked, so a “not licensed” verdict is scoped to coverage, never absolute. Present only on a miss. See Confidence scoring.- Point-in-time lookups (
?as_of=)./v1/check,/v1/licenses/{id}, and/v1/operators/{slug}accept?as_of=to reconstruct a licence’s status as of a past date from transition history — strictly within our observation window (knowledge: observed | before_tracking | no_such_license; never extrapolated beforetracking_since; future dates400). See Point-in-time lookups. domains[].associationon operator detail.GET /v1/operators/{slug}now returnsdirect/white_labelper domain (the column was always populated; the field was missing from the response)./v1/checkdomain matching now collapses www / apex / subdomain variants via the Public Suffix List (registrable-domain fallback after exact-host), sovirginbet.comandwww.virginbet.comresolve to the same operator. Single-owner guard prevents over-matching shared white-label platform domains.- Rate-limit headers on authenticated responses.
X-RateLimit-Limit/-Remaining/-Reset+X-Upgrade-URLnow accompany authenticated/v1/*responses, not just the public path. - OpenAPI completeness.
GET /v1/operators/{slug}/regulatory-actionsandGET /v1/health/coverageare now in the spec;ApiError.codedocumentspayment_required+quota_exceeded. - MCP server caught up to the API. New tools
check_domain_batch,get_operator_regulatory_actions,check_coverage;check_domain+get_operatoracceptas_of. Tool output is now lean (a compact verdict, not the full REST payload) — andmatch_absence_reason+checked_jurisdictionsare preserved on a miss, so agents never collapse “not found” into “unlicensed”. See MCP server.
1.7.0 — 2026-04-29
Section titled “1.7.0 — 2026-04-29”- Tobique Gaming Commission (TGC) re-enabled — 6th jurisdiction live. The Cloudflare Worker proxy at
igregulator-scraper-proxy.scvgr-agent.workers.devnow bridges btc → thetgc.ca, bypassing the IP-reputation block that paused us in 1.6.1. Same scraper code, same daily 04:15 UTC slot — only the transport changed (HMAC-signed POST to the Worker, the Worker fetches the upstream CF→CF). Scraper opts in viaUSE_PROXY=true; other scrapers unaffected. - New
@igregulator/scraper-utilspackage carrying the reusableproxyFetchhelper. Future jurisdictions whose upstream blocks our IP can opt in by setting two env vars (USE_PROXY=true,PROXY_HMAC_SECRET=…) and adding their hostname to the Worker’sALLOWED_DOMAINSlist — no code change in the scraper itself. - Migration 0018 re-inserts the TGC jurisdiction row removed in 0017.
1.6.1 — 2026-04-28
Section titled “1.6.1 — 2026-04-28”- Tobique Gaming Commission (TGC) ingestion deferred. PR #119 shipped the scraper, but the upstream
thetgc.cablocks the production origin IP at the Cloudflare edge (HTTP 403 across all UAs and header shapes). Other hosts return 200; this is an IP-reputation block on the Hetzner range. We’ve surgically rolled the public surfaces back to 5 jurisdictions while the scraper code stays merged. Re-enables cleanly once we land a Cloudflare Worker proxy onscvgr-agent.workers.dev. Investigation + path-forward documented in an internal write-up.
1.6.0 — 2026-04-28
Section titled “1.6.0 — 2026-04-28”- Tobique Gaming Commission (TGC) added — 6th jurisdiction. (Reverted on the public surface — see 1.6.1. Scraper code stays merged for re-enable when the proxy lands.) ~160 licences ingested daily from thetgc.ca/license-holders/. License-type vocabulary
B2C/B2B. License numbers are synthesised asTGC/<TYPE>/<slug>(TGC doesn’t publish IDs, same convention as KH). Cron 04:15 UTC; regulatory tier shifted +15 min. The fuzzy + domain-exact match flow on/v1/checkincludes TGC automatically. - Domain coverage 0% out of the box for TGC. Same upstream-doesn’t-publish-websites bucket as Curaçao. Documented at /docs/coverage-methodology. Phase 4 WHOIS / Tranco enrichment will close this for both regulators in one pass.
1.5.0 — 2026-04-28
Section titled “1.5.0 — 2026-04-28”- Trust-signals + positioning sweep. New pages: /about, /terms, /privacy. Footer reorganised — Company column now lists About / Changelog / Terms / Privacy. Stale “MCP server (soon)” replaced with a live link.
- Legal disclaimer surfaced. Now rendered as a callout at the top of /docs and embedded in the OpenAPI spec’s top-level
info.descriptionso agents reading the spec see it. Same wording: results are informational, customers responsible for their own compliance decisions. - Hero copy iteration — pain-driven. “iGaming licensing intelligence API” → “Verify gambling operator licenses before they cost you.” Sub-copy mentions the buyer profiles (compliance teams, payment providers, affiliate networks) explicitly. Meta description, og:description, llms.txt opening line aligned. No data or endpoint changes.
1.4.0 — 2026-04-28
Section titled “1.4.0 — 2026-04-28”- MGA domain enrichment. The MGA scraper now fans out from each B2C license to the legacy
authorisation.mga.org.mt/verification.aspxpage and pulls the Website URL(s) field. Runs daily in the same 03:15 UTC slot as the primary register pass, ~30 s wall time at p-limit 5, no separate cron entry. Recovers ~110 B2C operator domains from a previous baseline of zero. B2B / CRP licenses are skipped — the upstream verification page omits the Website URL section for non-consumer-facing license classes. - Coverage methodology update.
/v1/health/coveragenow exposesdomain_coverage.{operators_with_domain, normative_operators, coverage_pct}per jurisdiction. The denominator is operators where domain disclosure is normative for their license type — excludes B2B-only types (CSPA, B2B, Non-Remote, Ancillary, supplier permits, etc.) that don’t have consumer-facing domains by design. Methodology change only; no data changes. Affected metrics under the new denominator:- AN — 98% of B2C operators have ≥1 domain
- CW — 0% (upstream ceiling, no scraper-side work possible)
- KH — 69% of Interactive Gaming Permit holders (gap is upstream non-disclosure)
- MGA — 0% pre-1.4.0 enrichment, ~90% post
- UKGC — 40% of
Remote-license operators (upstream meaningful ceiling, the rest don’t operate consumer sites)
- UKGC parser audit closed without code changes. Time-boxed re-audit confirmed the ~17.3% raw figure is 96% of the 482-operator meaningful ceiling (Active + White Label distinct accounts in
domain-names.csv). Inactive-domain rows aren’t ingested deliberately to avoid stale/v1/checkmatches. Findings documented in an internal write-up.
1.3.1 — 2026-04-28
Section titled “1.3.1 — 2026-04-28”- MCP server verified live in production. End-to-end smoke against
https://mcp.igregulator.io/mcp:tools/listreturns all 7 tools,check_domainresolves UKGC + AN matches with correct confidence hints,api_request_logrows taggedsource='mcp'. DNS via Cloudflare proxy, TLS via the existing Origin CA cert (extended to cover the new subdomain). Pricing page lists MCP support on Starter onwards. Claude Code path documented at /docs/mcp viaclaude mcp add --transport http.
1.3.0 — 2026-04-28
Section titled “1.3.0 — 2026-04-28”- MCP server live at
mcp.igregulator.io. Streamable-HTTP transport (current MCP spec, SSE for streaming). Seven tools exposed:check_domain,search_operators,get_operator,list_jurisdictions,get_jurisdiction,get_license,get_license_history. Bearer-token auth — same API keys as the direct HTTP API; tool calls forward to api.igregulator.io and count against your existing per-key quota. Setup walkthrough at /docs/mcp. Discovery manifests at/.well-known/mcp.jsonon all three iGregulator surfaces. api_request_log.sourcecolumn added. New requests are taggedhttpormcpso admin analytics can split MCP usage from direct-HTTP usage. No client-visible change.
1.2.0 — 2026-04-28
Section titled “1.2.0 — 2026-04-28”- Anjouan jurisdiction live. 5th regulator: Anjouan Gaming Authority (AGA), ~1,275 licences ingested daily from anjouangaming.com/license-register/. License-type vocabulary
B2C/B2B/White Labeling. The fuzzy + domain-exact match flow on/v1/checkincludes AN automatically — no client-side change needed. Coverage table at /docs/ refreshed. - Marketing copy correction. Hero / meta / llms.txt now say “Daily-refreshed … updated within 24 hours” instead of “Real-time.” The data was never real-time; the new wording matches what scrapers actually deliver (cron 03:00–04:00 UTC, depending on jurisdiction).
1.1.0 — 2026-04-28
Section titled “1.1.0 — 2026-04-28”- Flat field removal on
/v1/check. Top-level legacy keys (licensed,jurisdiction,license_number,operator,status,expires_at) removed along with theDeprecation/Sunset/Linkheaders. Readmatch.*instead. Removed ahead of the announced 2026-05-19 sunset because no customers are integrated against the flat shape yet. - Pre-launch surface gating.
trialkeys are now restricted to/v1/checkonly (1,000/day per-key cap). Other authenticated endpoints return402 payment_requiredwithdetails.reason=endpoint_requires_paid_plan. Behaviour lifts automatically oncePRELAUNCH_DAILY_CAP=0(post-Stripe). - Webhook retention 7 → 30 days.
webhook_deliverieshistory now matches thewebhook_eventsreplay window. Existing rows live longer immediately; nothing to migrate. - Generic-label blocklist on
/v1/checkwidened. Now substring-match instead of exact-match —bestcasino.com,casino-bonus.comreturnconfidence: lowlikecasino.comalready did. Licensed brands containing a generic keyword (bet365.com,pokerstars.com) still resolve via the domain hit before the gate runs. - OpenAPI spec documents the per-jurisdiction
license_typesvocabulary inline so client-side enums can be coded against the audited values (UKGCRemote/Non-Remote/Ancillary Remote, MGAType 1-4/B2B/B2C, CWB2C/B2B, KHInteractive Gaming Permit/CSPA).
1.0.0 — 2026-04-19
Section titled “1.0.0 — 2026-04-19”Initial public docs release.
- /v1/check response shape:
{ query, match, alternatives, confidence }.match.confidence∈high | medium | low,match_type∈domain_exact | trading_name_fuzzy | name_similarity,domain_association∈direct | white_label | null. - Public endpoints:
/v1/check,/v1/jurisdictions,/v1/operators/search. Rate-limited 10 req / IP / hour. - White Label ingestion live for UKGC — domains with
Status = 'White Label'in the UKGC register now load withassociation = 'white_label'on the domain row. - OpenAPI 3.1 spec available at api.igregulator.io/openapi.json (canonical source; Scalar + starlight-openapi both consume it). The old Swagger UI on
api.igregulator.io/docsnow 301-redirects to /docs/api/ — unified docs at /docs supersede it.
0.3.0 — 2026-04-17
Section titled “0.3.0 — 2026-04-17”- Regulatory actions surfaced on the operator detail page. Cross-jurisdiction feed: UKGC Public Register, MGA Decisions, CGA Warnings.
- New endpoint:
/v1/operators/:slug/regulatory-actions(authenticated).
0.2.0 — 2026-04-08
Section titled “0.2.0 — 2026-04-08”- Kahnawake Gaming Commission jurisdiction added.
- Curaçao scraper migrated to the post-LOK OGL PDF source.
- Licence category harmonisation:
remote | non-remote | ancillary | permit | other.
0.1.0 — 2026-03-28
Section titled “0.1.0 — 2026-03-28”- First operational release. UKGC-only coverage.
- Core schema:
operators,licenses,domains,jurisdictions. - Dashboard lives at
app.igregulator.io.