Skip to content

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.

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 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:
    • Deprecation: true header on affected responses the day the field is marked.
    • Sunset header (RFC 9745) with the removal date, minimum 90 days out.
    • Changelog entry below with the migration guidance.
    • For agents: Link: …; rel="deprecation" surfaced alongside the header.

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_of no longer serves back events we withdrew. New knowledge value: corrected, with status_as_of: null and a correction object (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, and as_of read the latest event without checking whether it had been withdrawn — so a date inside one of those windows still answered revoked / observed. It now answers corrected. We do not fall back to the previous status either: that would report active for a period in which the register did not list the licence. If you branch on knowledge === 'observed', nothing changes for you — corrected lands in your “unknown” path, which is where it belongs. See point-in-time.
  • /v1/check_meta.source_url now 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/check was silently ignored. The parameter has been documented since point-in-time shipped, but on /v1/check — and therefore on the MCP check_domain tool — it never reached the handler: the response carried no as_of object and match.status was simply today’s. /v1/operators/{slug} and /v1/licenses/{id} were not affected. It works now, and a malformed or future as_of answers 400 invalid_query instead of 200. If you relied on /v1/check?as_of= for a historical verdict, you were reading the current status — check for the as_of object in the response and re-run those lookups.
  • MCP: get_operator returns license_id on each licence. It is the UUID get_license and get_license_history take, 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.

Additive. Every status now says where it came from, and one billing bug is fixed.

  • Per-status provenance on licencesstatus_source_url, status_observed_at and last_listed_at on GET /v1/licenses/{id} and on the licences inside GET /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_url is that page; last_listed_at is when we last saw the licence in its register.
  • _meta.source_url for 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 eventsGET /v1/licenses/{id}/history gains snapshot_sha256 and snapshot_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. null where 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 active or not_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 active because the register “only lists live licensees”. We now read the per-licence word the MGA publishes: Licensed/Approvedactive, Surrenderedsurrendered, Expiredexpired, Suspended/Voluntary Suspendedsuspended (verbatim wording in upstream_status). A handful of MGA licences changed status as a result. A word we have never seen maps to unknown, never to a default.
  • GET /v1/operators/search_meta.scraped_at is 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_range follows.
  • Fixed: authenticated calls to /v1/operators/{slug}, /v1/licenses, /v1/watchlist and /v1/webhooks were 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.

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_status values: not_in_register and surrendered. Neither is a revocation, and neither is active.
    • not_in_register — the regulator’s public register no longer lists the licence, since not_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 now not_in_register, except the seven where a regulator does publish an explicit decision (those are revoked or suspended, sourced to that publication). Separately, 627 UKGC licences published as Surrendered were mapped to revoked and are now surrendered. 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 on active and route the rest to a human. GET /v1/licenses/{id}/history gains note, corrected_at and correction_note; licences gain not_listed_since.
  • upstream_status is null when a licence is not_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”.

All additive — no breaking changes.

  • verification_url on /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_url links straight to it, so you can confirm any verdict against the primary source in one click. null where the regulator has no such page (UKGC, MGA, KH, AN). We re-read every stored verification page nightly and move domain_status only 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-first jurisdictions[] array with per-link status, domain_status, domain_association, and verification_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 /token registry (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 regulator verification_url, kept fresh by the nightly re-verification pass and a weekly /token discovery 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_domain output now includes verification_url and (when present) the compact jurisdictions[] 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, the DomainJurisdiction schema and CheckResult.jurisdictions are documented in the spec.

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_jurisdictions is returned once at the top, and a malformed hostname comes back as a per-row error without failing the batch. See Batch domain check.
  • match_absence_reason + checked_jurisdictions on /v1/check. When match is null, the response now says whygeneric_term (ambiguous label) vs no_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 before tracking_since; future dates 400). See Point-in-time lookups.
  • domains[].association on operator detail. GET /v1/operators/{slug} now returns direct / white_label per domain (the column was always populated; the field was missing from the response).
  • /v1/check domain matching now collapses www / apex / subdomain variants via the Public Suffix List (registrable-domain fallback after exact-host), so virginbet.com and www.virginbet.com resolve 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-URL now accompany authenticated /v1/* responses, not just the public path.
  • OpenAPI completeness. GET /v1/operators/{slug}/regulatory-actions and GET /v1/health/coverage are now in the spec; ApiError.code documents payment_required + quota_exceeded.
  • MCP server caught up to the API. New tools check_domain_batch, get_operator_regulatory_actions, check_coverage; check_domain + get_operator accept as_of. Tool output is now lean (a compact verdict, not the full REST payload) — and match_absence_reason + checked_jurisdictions are preserved on a miss, so agents never collapse “not found” into “unlicensed”. See MCP server.
  • Tobique Gaming Commission (TGC) re-enabled — 6th jurisdiction live. The Cloudflare Worker proxy at igregulator-scraper-proxy.scvgr-agent.workers.dev now 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 via USE_PROXY=true; other scrapers unaffected.
  • New @igregulator/scraper-utils package carrying the reusable proxyFetch helper. 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’s ALLOWED_DOMAINS list — no code change in the scraper itself.
  • Migration 0018 re-inserts the TGC jurisdiction row removed in 0017.
  • Tobique Gaming Commission (TGC) ingestion deferred. PR #119 shipped the scraper, but the upstream thetgc.ca blocks 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 on scvgr-agent.workers.dev. Investigation + path-forward documented in docs/scrapers/tobique-investigation.md.
  • 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 as TGC/<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/check includes 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.
  • 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.description so 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.
  • MGA domain enrichment. The MGA scraper now fans out from each B2C license to the legacy authorisation.mga.org.mt/verification.aspx page 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/coverage now exposes domain_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 — see scope doc)
    • 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/check matches. Findings documented in docs/scrapers/ukgc-domain-gap.md.
  • MCP server verified live in production. End-to-end smoke against https://mcp.igregulator.io/mcp: tools/list returns all 7 tools, check_domain resolves UKGC + AN matches with correct confidence hints, api_request_log rows tagged source='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 via claude mcp add --transport http.
  • 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.json on all three iGregulator surfaces.
  • api_request_log.source column added. New requests are tagged http or mcp so admin analytics can split MCP usage from direct-HTTP usage. No client-visible change.
  • 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/check includes 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).
  • Flat field removal on /v1/check. Top-level legacy keys (licensed, jurisdiction, license_number, operator, status, expires_at) removed along with the Deprecation / Sunset / Link headers. Read match.* instead. Removed ahead of the announced 2026-05-19 sunset because no customers are integrated against the flat shape yet.
  • Pre-launch surface gating. trial keys are now restricted to /v1/check only (1,000/day per-key cap). Other authenticated endpoints return 402 payment_required with details.reason=endpoint_requires_paid_plan. Behaviour lifts automatically once PRELAUNCH_DAILY_CAP=0 (post-Stripe).
  • Webhook retention 7 → 30 days. webhook_deliveries history now matches the webhook_events replay window. Existing rows live longer immediately; nothing to migrate.
  • Generic-label blocklist on /v1/check widened. Now substring-match instead of exact-match — bestcasino.com, casino-bonus.com return confidence: low like casino.com already 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_types vocabulary inline so client-side enums can be coded against the audited values (UKGC Remote/Non-Remote/Ancillary Remote, MGA Type 1-4/B2B/B2C, CW B2C/B2B, KH Interactive Gaming Permit/CSPA).

Initial public docs release.

  • /v1/check response shape: { query, match, alternatives, confidence }. match.confidencehigh | medium | low, match_typedomain_exact | trading_name_fuzzy | name_similarity, domain_associationdirect | 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 with association = '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/docs now 301-redirects to /docs/api/ — unified docs at /docs supersede it.
  • 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).
  • 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.
  • First operational release. UKGC-only coverage.
  • Core schema: operators, licenses, domains, jurisdictions.
  • Dashboard lives at app.igregulator.io.