Skip to content

Operator detail — metadata + licences + domains

GET
/v1/operators/{slug}

Full profile for one operator: display and registered names, upstream regulator IDs, every licence we hold for it in every status (each with its own status provenance), and every domain the registers we cover link to it, each with its own status under this operator (a delisted link is included). Neither list is paginated. Enforcement history is NOT included — fetch GET /v1/operators/{slug}/regulatory-actions. There is no operator-level status: a multi-jurisdiction operator is never collapsed into one — read each licence. The top-level _meta is taken from ONE licence (see OperatorDetail._meta). Pass ?as_of= to attach a point-in-time status to EACH licence (resolved per licence).

slug
required
string
>= 1 characters <= 100 characters

The operator’s slug, from /v1/operators/search or a /v1/check match’s operator_slug. Case-insensitive.

Example

888-uk-limited
as_of
string

Point-in-time lookup. Each licence gains an as_of object: its status reconstructed within our observation window — never extrapolated before tracking_since. Exactly two formats: YYYY-MM-DD, a real calendar date (the end of that day, UTC; today’s date means now), or an ISO-8601 datetime with a UTC offset (2026-03-01T12:00:00Z, 2026-03-01T14:00+02:00). Anything else — 2026/03/01, 2026-02-30, a datetime without an offset — and any future instant is a 400 with details.field: "as_of".

Example

2026-03-01

Operator detail.

Media type application/json
object
operator
required
object
id
required
string format: uuid
slug
required

Lower-case, URL-safe; the key of /v1/operators/{slug}.

string
display_name
required
string
registered_name
required

The name the register gives next to the entity. For UKGC this is often the trading name (e.g. bet365, paddy power), not a legal name.

string | null
country
required

null for almost every operator: the register loaders do not record a country. Only operators first seen through the Curaçao certificate registry carry one (CW). Do not filter on it.

string | null
upstream_ids
required

The operator’s id in each register it appears in, keyed by jurisdiction code.

object
key
additional properties
string
created_at
required

When we first recorded the operator.

string format: date-time
updated_at
required

When the operator’s own row last changed (a rename) — NOT when we last read a register about it; for that use a licence’s last_verified_at or the search row’s _meta.scraped_at.

string format: date-time

Example

{
"id": "fa30b85c-4749-4f91-9490-2615b76a4832",
"slug": "888-uk-limited",
"display_name": "888 UK Limited",
"registered_name": "888",
"country": null,
"upstream_ids": {
"UKGC": "39028"
},
"created_at": "2026-04-17T15:15:41.414Z",
"updated_at": "2026-04-17T15:15:41.414Z"
}
licenses
required
Array<object>
object
id
required

The licence’s iGregulator id — what /v1/licenses/{license_id} takes, and match.license_id on /v1/check.

string format: uuid
license_number
required

The licence number as the regulator’s register prints it — except for KH (Kahnawake), TGC (Tobique), IOM (Isle of Man), ON (Ontario), SE (Sweden), ES (Spain), BE (Belgium), DE (Germany) and UA (Ukraine), whose registers publish none. For those nine it is an iGregulator reference we assign: KH/IG/<slug> or KH/CSPA/<slug>, TGC/B2C/<slug> or TGC/B2B/<slug>, IOM/OGRA/<company key>, ON/IGO/<iGaming Ontario directory id>, SE/ONLINE/<holder key> or SE/BETTING/<holder key>, ES/APUESTAS/<DGOJ profile slug>, ES/CONCURSOS/<…> or ES/OTROS-JUEGOS/<…> (one per general licence), BE/APLUS/<Gaming Commission dossier id>, BE/BPLUS/<…> or BE/F1PLUS/<…> (one per licence class and dossier), DE/SLOTS/<holder key>, DE/POKER/<…>, DE/SPORTS/<…> or DE/HORSES/<…> (one per GGL game type), UA/CASINO/<company code>/<decision date>/<decision number>, UA/POKER/<…> or UA/BETTING/<…> (one per PlayCity issuing decision). The reference is stable and /v1/check?license_number= accepts it, but no regulator issued it: do not present it to a user as the licence number.

string
license_reference_is_ours
required

true when license_number is an iGregulator reference, not a number the regulator published — every KH, TGC, IOM, ON, SE, ES, BE, DE and UA licence (their registers publish none). Never present such a reference to a user as the licence number. Same field as on a /v1/check match.

boolean
jurisdiction_code
required

Jurisdiction code — UKGC, MGA, CW (Curaçao), KH (Kahnawake), AN (Anjouan), TGC (Tobique), IOM (Isle of Man), ON (Ontario — the iGaming Ontario operator directory), SE (Sweden — online gaming and betting licences from the Spelinspektionen register, nothing else), ES (Spain — the DGOJ’s state-level online licences; not the state lotteries (SELAE, ONCE) or regional land-based gambling), BE (Belgium — the Gaming Commission’s online licence classes A+, B+ and F1+ only; not land-based licences or the Loterie Nationale), DE (Germany — the GGL whitelist’s four internet game types (virtual slots, online poker, sports betting, online horse betting); not a Land’s online casino permits, lotteries or land casinos), CO (Colombia — Coljuegos’ online gaming concessions only; not land-based gaming (juegos localizados) or the lotteries), UA (Ukraine — PlayCity’s online casino, online poker and bookmaking licences only; not land casinos or slot halls or the state lotteries). GET /v1/jurisdictions lists them with their names.

string
Allowed values: UKGC MGA CW KH AN TGC IOM ON SE ES BE DE CO UA
status
required

Canonical licence status. Only active means “licensed right now”. Every other value is “not a pass” — but three of them are not enforcement decisions, and a rule that lumps them in with revoked publishes a claim about a real business that no regulator made.

not_in_register — the regulator’s public register no longer lists this licence, since not_listed_since. An observation about the register, not a decision by the regulator: licences drop off because they expired, were surrendered, were renumbered, or the page had a bad day. Nobody published a reason and we do not invent one. It is not a revocation. (Until 2026-09-17 we reported this as revoked; that was wrong, every affected record has been corrected, and its history says so.)

surrendered — the operator gave the licence up (UKGC publishes “Surrendered”; Kahnawake “voluntary termination … in good standing”; the Isle of Man GSC, on its Former Licence Holders list, “Surrendered”). Also not an enforcement decision: nobody took it away. 627 UKGC licences carried revoked for this until 2026-09-17.

unknown — the register lists this licence and published a status we could not classify: a new phrase, a reworded sentence, a typo. We do not guess: two of our scrapers used to default an unreadable status to active, and that is precisely the bug this value exists to make impossible. Read upstream_status for the regulator’s own words. For UA it is also what we store when the last thing PlayCity’s register prints is a court’s ruling on its decision — contested, so neither “licensed” nor “revoked”; there upstream_status is an English rendering of the register’s line. (A UA licence the register says PlayCity terminated by its decision, with no grounds printed, is revoked, its upstream_status keeping that wording.)

None of these is a synonym for any other:

  • None of them means “not licensed” in the enforcement sense. Do not present them as such.
  • None of them means “probably fine”. Do not treat them as a pass.

A rule like if (status === "revoked") block() will silently let surrendered, not_in_register and unknown through. If your flow makes an automated allow/deny decision, approve only on active and escalate everything else to a human.

revoked and suspended are only ever set from a regulator publication we actually read — an enforcement register, a revoked-licences page, an explicit status column — never from a licence disappearing. (The Isle of Man’s Former Licence Holders list says Cancelled for a licence the Commission cancelled under s.13 of the Online Gambling Regulation Act 2001; we store that as revoked. Ukraine’s PlayCity prints each licence’s revocation, with its decision, on the licence’s own row in its register; we store that as revoked, and a term it says has run out as expired.)

Six registers publish nothing a revocation, suspension or exit can be read from, so neither value is ever set there: ON (iGaming Ontario’s operator directory lists current operators and nothing else — a licence is active or not_in_register), SE (Spelinspektionen’s register lists valid licences only — active, not_in_register, or unknown for a status word other than Aktiv), ES (the DGOJ states licence ends in prose resolutions and fines in a sanctions register, neither read as a status — active or not_in_register), BE (the Gaming Commission publishes lists of active licences and no list of warnings, suspensions or withdrawals — active or not_in_register; a licence still listed past its expiration date stays active), DE (the GGL’s whitelist lists permitted operators only, and its public warnings are not licence statuses — active or not_in_register) and CO (Coljuegos’ page lists its authorised online operators and publishes no structured list of ended concessions — active or not_in_register). Absence from any of them is not_in_register, never a revocation.

string
Allowed values: active suspended revoked expired pending unknown surrendered not_in_register
status_qualifier
required

When status is right but not the whole truth; same field as on a /v1/check match. provisional_under_assessment: a Curaçao licence the CGA register reads as “Assessment in progress” past its stated term — active, because the CGA keeps such a licence in force until it decides, but a decision is outstanding and expiry_date may already be past. Derived from the regulator’s wording on every read. null otherwise.

string | null
Allowed values: provisional_under_assessment
license_types
required

The licence’s type(s), in the register’s own words. What each scraper stores today:

  • UKGC — Remote, Non-Remote, Ancillary Remote (one per licence)
  • MGA — the game types of the authorisation, Type 1 … Type 4 (several per licence), or its class (B2C, B2B) when the register gives no type
  • CW (Curaçao) — B2C, B2B
  • KH (Kahnawake) — Interactive Gaming Permit, CSPA
  • AN (Anjouan) — B2C, B2B, White Labeling
  • TGC (Tobique) — B2C, B2B
  • IOM (Isle of Man) — Full, Network Services, Software Supply (the Online Gambling Regulation Act 2001 licence types, as the register writes them)
  • ON (Ontario) — none: iGaming Ontario’s operator directory prints no licence type, so the array is empty
  • SE (Sweden) — Kommersiellt online (commercial online gaming), Kommersiellt vadhållning (betting) — the only two of Spelinspektionen’s licence types we load
  • ES (Spain) — the DGOJ’s singular licences under the general one, in Spanish (Ruleta, Black Jack, Póquer, Apuestas deportivas de contrapartida, …); license_type_raw is the general licence: Apuestas, Concursos or Otros juegos
  • BE (Belgium) — A+ (online casino games), B+ (online arcade games), F1+ (online betting): the Gaming Commission’s online licence classes, each the online extension of a land licence — the only three we load
  • DE (Germany) — Virtuelle Automatenspiele (virtual slot games), Online-Poker, Sportwetten (sports betting), Pferdewetten im Internet (online horse-race betting): the four internet game types the GGL itself permits — the only four we load
  • CO (Colombia) — Juegos operados por internet (online gaming concession): Coljuegos’ register prints no type; every licence we load is one online concession contract, and its license_number is Coljuegos’ own contract number (2182, C2261)
  • UA (Ukraine) — Online casino, Online poker, Bookmaking: PlayCity’s three online licence types, in English (the register’s Ukrainian section titles are kept in the licence’s raw data). One licence per issuing decision; PlayCity prints no licence number, so license_number is our reference (UA/CASINO/<company code>/<decision date>/<decision number>)

A regulator can publish a value not listed here and we pass it through — do not treat the list as closed. It is per licence: a multi-jurisdiction operator has one licence record per jurisdiction. (GET /v1/jurisdictions has a license_types list too; it is a hand-set reference and for MGA and KH uses other words — see Jurisdiction.license_types.)

Array<string>
license_type_raw
required

The type cell exactly as the register prints it, before it is split into license_types (Type 1, Type 2). An empty string when the register gives none.

string
not_listed_since
required

When the regulator’s public register stopped listing this licence (first pull that missed it) — the date behind status: not_in_register. null = listed in the most recent pull. An observation about the register, not a regulator decision: it does not mean revoked. A licence can briefly carry this while still active (one missed pull, inside our grace window).

string | null format: date-time
license_category
required

Cross-jurisdiction harmonised category — derived by @igregulator/normalizer so a multi-regulator query can group by it. UKGC Remote, MGA Type 1/2, CW B2C, IOM Full, both SE types and every ON, ES, BE, DE, CO and UA entry map to remote; UKGC Non-Remote to non-remote; IOM Network Services and Software Supply to ancillary; etc.

string
Allowed values: remote non-remote ancillary permit other
issued_date
required

As the register gives it: a calendar date, YYYY-MM-DD (no time, no zone).

string | null format: date
expiry_date
required

As the register gives it: a calendar date, YYYY-MM-DD. A past date next to status: active is normal for a provisional_under_assessment licence (see status_qualifier).

string | null format: date
last_verified_at
required

When a load last wrote this record from a read of its register. For a licence the register no longer lists it stops moving — see not_listed_since / last_listed_at.

string format: date-time
source_url
required

The register this licence is listed in. Not necessarily where its current status came from — cite status_source_url for that.

string format: uri
status_source_url
required

The page that published THIS status — provenance is per status, not per licence. Often not the register in source_url: a Curaçao or Anjouan revocation comes from the regulator’s enforcement / revoked-licences page, a Kahnawake termination from its advisory notice, an MGA status from the licensee’s verification page. Cite this, not source_url, when you explain a non-active status. null for a status set before we tracked provenance.

string | null format: uri
status_observed_at
required

The latest read of status_source_url that still said this status. When the status CHANGED is in the licence history (/v1/licenses/{license_id}/history), not here. null when status_source_url is.

string | null format: date-time
last_listed_at
required

The last time we saw this licence listed in its register. With not_listed_since it reads “no longer listed since X; last seen listed on Y”. null when we have no such read on record.

string | null format: date-time
raw_data
required

Scraper-specific fields captured verbatim from the upstream register (e.g. UKGC activity categories, CGA company-type flags). Not a stable integration surface. Keys here are added, renamed, or removed without a deprecation window when a scraper is updated — they do not carry the 90-day Sunset guarantee that top-level fields do. Use license_types, license_category, status, issued_date, expiry_date for stable compliance logic; read raw_data only for diagnostic / investigative purposes.

object
key
additional properties
any
as_of

Only with ?as_of= on /v1/licenses/{license_id} and /v1/operators/{slug} (never on /v1/operators/{slug}/licenses).

object
as_of
required

The resolved instant (UTC). A bare YYYY-MM-DD resolves to the end of that day; today’s date resolves to now.

string format: date-time
knowledge
required

observed: the date is within our window, status is known. corrected: the event in effect on that date is one we later WITHDREW (see correction) — status is null. We do not report the withdrawn status, and we do not fall back to the one before it either: neither is something we observed for that date. before_tracking: the date predates when we started watching — status is null, NOT a guess. no_such_license: we have no history for this licence. no_license_resolved: a fuzzy /v1/check match with no specific licence to time-travel.

string
Allowed values: observed corrected before_tracking no_such_license no_license_resolved
status_as_of
required

Licence status as of the date, or null when not observed.

string | null
established_by
required

The history transition in effect at the date — when this status was last confirmed relative to the query.

object
changed_at
string format: date-time
new_status
string
change_type
string
source_url
string
correction

Present only when knowledge is corrected. The event that was in effect at the date and that we later withdrew. withdrawn_status is what we USED to say — never quote it as the status.

object
changed_at
string format: date-time
withdrawn_status
string
corrected_at
string format: date-time
note
string | null
tracking_since
required

Lower bound of our knowledge — when we first observed this licence.

string | null format: date-time

Example

{
"id": "d29fbc19-3ed3-4043-8a74-e001491feb24",
"license_number": "039028-R-319297-014",
"license_reference_is_ours": false,
"jurisdiction_code": "UKGC",
"status": "active",
"status_qualifier": null,
"license_types": [
"Remote"
],
"license_type_raw": "Remote",
"license_category": "remote",
"issued_date": "2014-11-01",
"expiry_date": null,
"not_listed_since": null,
"last_verified_at": "2026-04-19T03:00:12.480Z",
"source_url": "https://www.gamblingcommission.gov.uk/downloads/business-licence-data.zip",
"status_source_url": "https://www.gamblingcommission.gov.uk/downloads/business-licence-data.zip",
"status_observed_at": "2026-04-19T03:00:12.480Z",
"last_listed_at": "2026-04-19T03:00:12.480Z",
"raw_data": {
"activities": [
"Bingo",
"Casino"
]
}
}
domains
required

Every domain ↔ operator link we hold for this operator, in every status, ordered by domain. A delisted link is still returned — filter on status.

Array<object>
object
domain
required
string
status
required

This domain’s status under THIS operator (the same as domain_status on /v1/check) — not a licence status. delisted = no longer listed on the operator’s licence.

string
Allowed values: active parked expired redirecting delisted
association
required

direct — the licensee runs the domain; white_label — the licensee authorises a third-party brand on it.

string
Allowed values: direct white_label
first_seen
required

When we first recorded this link.

string format: date-time
last_verified
required

The latest read that wrote this link’s status — for a delisted link, the read of the de-listing, not the last time it was listed.

string format: date-time
verification_url
required

The regulator’s own page naming this domain — a Curaçao certificate (cert.cga.cw), a Tobique validation seal or an MGA verification page; verification_page_kind says which. null where the jurisdiction publishes none. Before citing it as confirmation, read verification_page_status.

string | null format: uri
verification_page_kind
required

Same as match.verification_page_kind on /v1/check.

string | null
Allowed values: domain_certificate licence_certificate licensee_page other
verification_page_status
required

What that page stated about the licence the last time we read it, verbatim (e.g. Revoked). null when we hold no such reading. When it does not say the licence is in force, the page is NOT confirmation: say what it reads and when (verification_page_read_at). The link’s own status still comes from the register, and a revocation only from a regulator publication — this page alone changes neither.

string | null
verification_page_read_at
required

When we read verification_page_status off the page.

string | null format: date-time
_meta
required
One of:

Single-resource provenance envelope — answers “where did this come from and how fresh is it?” for one record.

object
scraped_at
required

ISO-8601 timestamp when iGregulator’s scraper last fetched this record.

string format: date-time
source_modified_at
required

Reserved: when the regulator last modified the record on its register, as opposed to when we fetched it. Always null today, for every jurisdiction — we do not store an upstream modification time. Kept in the shape so clients need not branch if it is ever populated. For freshness use scraped_at; for when a licence’s status was last read from its source, status_observed_at on the licence.

string | null format: date-time
source_url
required

The regulator page this answer rests on. For a licence — including a /v1/check match — it is the page that published the licence’s current STATUS (status_source_url: an enforcement register, a revoked-licences list, an advisory notice), falling back to the register the licence is listed in when they are the same page. Null when not attributable to a single URL.

string | null format: uri
confidence_hint
required

authoritative — UKGC: the Commission’s own machine-readable register export (a ZIP). scraped — every other jurisdiction (MGA, CW, KH, AN, TGC, IOM, ON, SE, ES, BE, DE, CO, UA): parsed from the published HTML/PDF pages or spreadsheet export; also every operator-search row. derived — a computed answer, not a direct lookup: a fuzzy or no-match /v1/check, with no source row behind it.

string
Allowed values: authoritative scraped derived

Example

{
"scraped_at": "2026-04-19T03:00:00Z",
"source_modified_at": null,
"source_url": "https://www.gamblingcommission.gov.uk/downloads/business-licence-data.zip",
"confidence_hint": "authoritative"
}
X-RateLimit-Limit
integer

Your plan’s monthly request quota (not the per-second limit). Not sent on a plan with no monthly quota (Business, Enterprise).

X-RateLimit-Remaining
integer

What is left of the monthly quota, this request counted. Not sent on a plan with no monthly quota.

X-RateLimit-Reset
integer

Unix epoch seconds of the start of the next UTC month. Not sent on a plan with no monthly quota.

X-RateLimit-Policy
string

tier=unlimited on a plan with no monthly quota; otherwise not sent.

X-Upgrade-URL
string format: uri

Always https://igregulator.io/pricing.

X-Monthly-Quota-Limit
string

The monthly request quota of your plan, or unlimited.

X-Monthly-Quota-Used
integer

Requests counted this UTC month, this one included (a request that ends in 4xx / 5xx is not counted). Not sent when the limit is unlimited.

X-Monthly-Quota-Remaining
integer

What is left of the monthly quota. Not sent when unlimited.

X-Monthly-Quota-Reset
string format: date-time

When the monthly counter starts again: the first of next month, 00:00 UTC, ISO-8601. Not sent when unlimited.

X-Monthly-Quota-Warning
string

80% of monthly limit used — sent from 80% of the quota on, absent before.

as_of is not one of the two accepted formats or lies in the future (the error text says which); or slug is longer than 100 characters.

Media type application/json

The body of every non-2xx response.

object
error
required

Human-readable summary. The wording can change; do not branch on it.

string
code
required

The error class. Mostly one per HTTP status, with two exceptions to know: quota_exceeded is a 429 for the monthly request quota and a 403 for a plan cap (watchlist size, webhook endpoints). A 409 carries conflict (it carried invalid_query before 1.24.0). Branch on the HTTP status and details.reason. forbidden is only ever sent to a signed-in MCP connection, never to an API key.

string
Allowed values: invalid_query invalid_slug invalid_license_id invalid_jurisdiction_code invalid_pagination auth_required auth_invalid auth_revoked payment_required forbidden not_found method_not_allowed conflict quota_exceeded rate_limited server_error
details
required

Always present. reason is always set; field and suggestion when they apply; limit errors add the numbers below.

object
reason
required

Machine-readable refinement of the top-level code — branch on this. Values: invalid_input, missing_required_parameter, conflicting_parameters, not_a_valid_hostname, hostname_too_long, invalid_pagination, invalid_cursor, since_exceeds_retention_window, invalid_event_type, invalid_jurisdiction, as_of_not_supported, api_key_missing, malformed_header, api_key_invalid, api_key_revoked, plan_inactive, plan_ended, export_requires_pro, geo_checks_require_business, watchlist_quota_exceeded, webhook_quota_exceeded, operator_not_found, license_not_found, jurisdiction_not_found, webhook_not_found, dataset_not_found, route_not_found, method_not_allowed, watchlist_duplicate, no_active_secret, quota_exceeded, monthly_request_limit_reached, per_second_limit_exceeded, watchlist_events_poll_limit, export_daily_limit_reached, geo_check_quota_exceeded, internal_error. A rejected webhook URL carries the SSRF check’s reason (invalid_url, invalid_scheme, empty_host, blocked_hostname, unresolvable_host, private_ip_blocked). New values can be added.

string
field

Present only when the error maps to a specific request input (query param, path param, body key). Omitted for errors that aren’t field-scoped (e.g. rate_limited, auth_revoked).

string
suggestion

A hint a person or an agent can act on. Present on every error today.

string
limit

On a limit error (429, and the 403 plan caps): the ceiling that was reached.

integer
current_usage

On a keyed limit error: how much of limit is used. Not on the keyless per-IP 429 or the watchlist-poll 429.

integer
reset_at

When the counter starts again — on the keyless per-IP 429, the monthly quota, the watchlist-poll limit and the export daily limit. Not on the per-second limit (retry after one second).

string format: date-time
plan_tier

The key’s plan, on plan-scoped limit errors and on export_requires_pro.

string
required_plan

On export_requires_pro: the lowest plan that includes the feature.

string
window

On watchlist_events_poll_limit: always hour.

string
key
additional properties
any

Examples

{
"error": "as_of is in the future — iGregulator never answers about a date it has not observed",
"code": "invalid_query",
"details": {
"reason": "invalid_input",
"field": "as_of",
"suggestion": "Pass as_of as YYYY-MM-DD (resolved to end of that day, UTC) or an ISO-8601 datetime — and not in the future."
}
}

The API key is missing, malformed, unknown or revoked. On an endpoint that works without a key this is only ever sent when a key WAS sent and is not valid — drop the header, or fix the key.

Media type application/json

The body of every non-2xx response.

object
error
required

Human-readable summary. The wording can change; do not branch on it.

string
code
required

The error class. Mostly one per HTTP status, with two exceptions to know: quota_exceeded is a 429 for the monthly request quota and a 403 for a plan cap (watchlist size, webhook endpoints). A 409 carries conflict (it carried invalid_query before 1.24.0). Branch on the HTTP status and details.reason. forbidden is only ever sent to a signed-in MCP connection, never to an API key.

string
Allowed values: invalid_query invalid_slug invalid_license_id invalid_jurisdiction_code invalid_pagination auth_required auth_invalid auth_revoked payment_required forbidden not_found method_not_allowed conflict quota_exceeded rate_limited server_error
details
required

Always present. reason is always set; field and suggestion when they apply; limit errors add the numbers below.

object
reason
required

Machine-readable refinement of the top-level code — branch on this. Values: invalid_input, missing_required_parameter, conflicting_parameters, not_a_valid_hostname, hostname_too_long, invalid_pagination, invalid_cursor, since_exceeds_retention_window, invalid_event_type, invalid_jurisdiction, as_of_not_supported, api_key_missing, malformed_header, api_key_invalid, api_key_revoked, plan_inactive, plan_ended, export_requires_pro, geo_checks_require_business, watchlist_quota_exceeded, webhook_quota_exceeded, operator_not_found, license_not_found, jurisdiction_not_found, webhook_not_found, dataset_not_found, route_not_found, method_not_allowed, watchlist_duplicate, no_active_secret, quota_exceeded, monthly_request_limit_reached, per_second_limit_exceeded, watchlist_events_poll_limit, export_daily_limit_reached, geo_check_quota_exceeded, internal_error. A rejected webhook URL carries the SSRF check’s reason (invalid_url, invalid_scheme, empty_host, blocked_hostname, unresolvable_host, private_ip_blocked). New values can be added.

string
field

Present only when the error maps to a specific request input (query param, path param, body key). Omitted for errors that aren’t field-scoped (e.g. rate_limited, auth_revoked).

string
suggestion

A hint a person or an agent can act on. Present on every error today.

string
limit

On a limit error (429, and the 403 plan caps): the ceiling that was reached.

integer
current_usage

On a keyed limit error: how much of limit is used. Not on the keyless per-IP 429 or the watchlist-poll 429.

integer
reset_at

When the counter starts again — on the keyless per-IP 429, the monthly quota, the watchlist-poll limit and the export daily limit. Not on the per-second limit (retry after one second).

string format: date-time
plan_tier

The key’s plan, on plan-scoped limit errors and on export_requires_pro.

string
required_plan

On export_requires_pro: the lowest plan that includes the feature.

string
window

On watchlist_events_poll_limit: always hour.

string
key
additional properties
any

Examples

{
"error": "Authorization header required",
"code": "auth_required",
"details": {
"reason": "api_key_missing",
"suggestion": "Attach an API key: `Authorization: Bearer <key>`. No key yet? Start a 14-day free trial of Starter (card required) at https://app.igregulator.io/signup; an existing account creates keys at https://app.igregulator.io/api-keys."
}
}

The key’s plan does not cover this request: the account has no plan or a canceled one (plan_inactive), or its plan’s end date has passed (plan_ended — keys, watchlist and webhook endpoints are kept, and no webhook is sent until the plan is renewed). A trial account is served as Starter. Paid plans are not on sale yet: email founder@igregulator.io and we will sort it out.

Media type application/json

The body of every non-2xx response.

object
error
required

Human-readable summary. The wording can change; do not branch on it.

string
code
required

The error class. Mostly one per HTTP status, with two exceptions to know: quota_exceeded is a 429 for the monthly request quota and a 403 for a plan cap (watchlist size, webhook endpoints). A 409 carries conflict (it carried invalid_query before 1.24.0). Branch on the HTTP status and details.reason. forbidden is only ever sent to a signed-in MCP connection, never to an API key.

string
Allowed values: invalid_query invalid_slug invalid_license_id invalid_jurisdiction_code invalid_pagination auth_required auth_invalid auth_revoked payment_required forbidden not_found method_not_allowed conflict quota_exceeded rate_limited server_error
details
required

Always present. reason is always set; field and suggestion when they apply; limit errors add the numbers below.

object
reason
required

Machine-readable refinement of the top-level code — branch on this. Values: invalid_input, missing_required_parameter, conflicting_parameters, not_a_valid_hostname, hostname_too_long, invalid_pagination, invalid_cursor, since_exceeds_retention_window, invalid_event_type, invalid_jurisdiction, as_of_not_supported, api_key_missing, malformed_header, api_key_invalid, api_key_revoked, plan_inactive, plan_ended, export_requires_pro, geo_checks_require_business, watchlist_quota_exceeded, webhook_quota_exceeded, operator_not_found, license_not_found, jurisdiction_not_found, webhook_not_found, dataset_not_found, route_not_found, method_not_allowed, watchlist_duplicate, no_active_secret, quota_exceeded, monthly_request_limit_reached, per_second_limit_exceeded, watchlist_events_poll_limit, export_daily_limit_reached, geo_check_quota_exceeded, internal_error. A rejected webhook URL carries the SSRF check’s reason (invalid_url, invalid_scheme, empty_host, blocked_hostname, unresolvable_host, private_ip_blocked). New values can be added.

string
field

Present only when the error maps to a specific request input (query param, path param, body key). Omitted for errors that aren’t field-scoped (e.g. rate_limited, auth_revoked).

string
suggestion

A hint a person or an agent can act on. Present on every error today.

string
limit

On a limit error (429, and the 403 plan caps): the ceiling that was reached.

integer
current_usage

On a keyed limit error: how much of limit is used. Not on the keyless per-IP 429 or the watchlist-poll 429.

integer
reset_at

When the counter starts again — on the keyless per-IP 429, the monthly quota, the watchlist-poll limit and the export daily limit. Not on the per-second limit (retry after one second).

string format: date-time
plan_tier

The key’s plan, on plan-scoped limit errors and on export_requires_pro.

string
required_plan

On export_requires_pro: the lowest plan that includes the feature.

string
window

On watchlist_events_poll_limit: always hour.

string
key
additional properties
any

Examples

{
"error": "Active subscription required",
"code": "payment_required",
"details": {
"reason": "plan_inactive",
"suggestion": "Current plan_tier: none — this account has no active plan. Renew or choose a plan at https://app.igregulator.io/billing (by card, or by invoice for three months or more; Starter starts with a 14-day free trial, card required), or email founder@igregulator.io."
}
}

No operator has that slug.

Media type application/json

The body of every non-2xx response.

object
error
required

Human-readable summary. The wording can change; do not branch on it.

string
code
required

The error class. Mostly one per HTTP status, with two exceptions to know: quota_exceeded is a 429 for the monthly request quota and a 403 for a plan cap (watchlist size, webhook endpoints). A 409 carries conflict (it carried invalid_query before 1.24.0). Branch on the HTTP status and details.reason. forbidden is only ever sent to a signed-in MCP connection, never to an API key.

string
Allowed values: invalid_query invalid_slug invalid_license_id invalid_jurisdiction_code invalid_pagination auth_required auth_invalid auth_revoked payment_required forbidden not_found method_not_allowed conflict quota_exceeded rate_limited server_error
details
required

Always present. reason is always set; field and suggestion when they apply; limit errors add the numbers below.

object
reason
required

Machine-readable refinement of the top-level code — branch on this. Values: invalid_input, missing_required_parameter, conflicting_parameters, not_a_valid_hostname, hostname_too_long, invalid_pagination, invalid_cursor, since_exceeds_retention_window, invalid_event_type, invalid_jurisdiction, as_of_not_supported, api_key_missing, malformed_header, api_key_invalid, api_key_revoked, plan_inactive, plan_ended, export_requires_pro, geo_checks_require_business, watchlist_quota_exceeded, webhook_quota_exceeded, operator_not_found, license_not_found, jurisdiction_not_found, webhook_not_found, dataset_not_found, route_not_found, method_not_allowed, watchlist_duplicate, no_active_secret, quota_exceeded, monthly_request_limit_reached, per_second_limit_exceeded, watchlist_events_poll_limit, export_daily_limit_reached, geo_check_quota_exceeded, internal_error. A rejected webhook URL carries the SSRF check’s reason (invalid_url, invalid_scheme, empty_host, blocked_hostname, unresolvable_host, private_ip_blocked). New values can be added.

string
field

Present only when the error maps to a specific request input (query param, path param, body key). Omitted for errors that aren’t field-scoped (e.g. rate_limited, auth_revoked).

string
suggestion

A hint a person or an agent can act on. Present on every error today.

string
limit

On a limit error (429, and the 403 plan caps): the ceiling that was reached.

integer
current_usage

On a keyed limit error: how much of limit is used. Not on the keyless per-IP 429 or the watchlist-poll 429.

integer
reset_at

When the counter starts again — on the keyless per-IP 429, the monthly quota, the watchlist-poll limit and the export daily limit. Not on the per-second limit (retry after one second).

string format: date-time
plan_tier

The key’s plan, on plan-scoped limit errors and on export_requires_pro.

string
required_plan

On export_requires_pro: the lowest plan that includes the feature.

string
window

On watchlist_events_poll_limit: always hour.

string
key
additional properties
any

Example

{
"error": "Operator not found",
"code": "not_found",
"details": {
"reason": "operator_not_found",
"field": "slug",
"suggestion": "No operator matches this slug. Try GET /v1/operators/search?q=<name> to find the canonical slug."
}
}

A limit of the key was reached. quota_exceeded / monthly_request_limit_reached: the plan’s monthly request quota — wait for reset_at. rate_limited / per_second_limit_exceeded: more requests in one second than the plan allows — retry after Retry-After (1 s). A request refused here is not counted against the monthly quota.

Media type application/json

The body of every non-2xx response.

object
error
required

Human-readable summary. The wording can change; do not branch on it.

string
code
required

The error class. Mostly one per HTTP status, with two exceptions to know: quota_exceeded is a 429 for the monthly request quota and a 403 for a plan cap (watchlist size, webhook endpoints). A 409 carries conflict (it carried invalid_query before 1.24.0). Branch on the HTTP status and details.reason. forbidden is only ever sent to a signed-in MCP connection, never to an API key.

string
Allowed values: invalid_query invalid_slug invalid_license_id invalid_jurisdiction_code invalid_pagination auth_required auth_invalid auth_revoked payment_required forbidden not_found method_not_allowed conflict quota_exceeded rate_limited server_error
details
required

Always present. reason is always set; field and suggestion when they apply; limit errors add the numbers below.

object
reason
required

Machine-readable refinement of the top-level code — branch on this. Values: invalid_input, missing_required_parameter, conflicting_parameters, not_a_valid_hostname, hostname_too_long, invalid_pagination, invalid_cursor, since_exceeds_retention_window, invalid_event_type, invalid_jurisdiction, as_of_not_supported, api_key_missing, malformed_header, api_key_invalid, api_key_revoked, plan_inactive, plan_ended, export_requires_pro, geo_checks_require_business, watchlist_quota_exceeded, webhook_quota_exceeded, operator_not_found, license_not_found, jurisdiction_not_found, webhook_not_found, dataset_not_found, route_not_found, method_not_allowed, watchlist_duplicate, no_active_secret, quota_exceeded, monthly_request_limit_reached, per_second_limit_exceeded, watchlist_events_poll_limit, export_daily_limit_reached, geo_check_quota_exceeded, internal_error. A rejected webhook URL carries the SSRF check’s reason (invalid_url, invalid_scheme, empty_host, blocked_hostname, unresolvable_host, private_ip_blocked). New values can be added.

string
field

Present only when the error maps to a specific request input (query param, path param, body key). Omitted for errors that aren’t field-scoped (e.g. rate_limited, auth_revoked).

string
suggestion

A hint a person or an agent can act on. Present on every error today.

string
limit

On a limit error (429, and the 403 plan caps): the ceiling that was reached.

integer
current_usage

On a keyed limit error: how much of limit is used. Not on the keyless per-IP 429 or the watchlist-poll 429.

integer
reset_at

When the counter starts again — on the keyless per-IP 429, the monthly quota, the watchlist-poll limit and the export daily limit. Not on the per-second limit (retry after one second).

string format: date-time
plan_tier

The key’s plan, on plan-scoped limit errors and on export_requires_pro.

string
required_plan

On export_requires_pro: the lowest plan that includes the feature.

string
window

On watchlist_events_poll_limit: always hour.

string
key
additional properties
any

Examples

{
"error": "Monthly quota exceeded",
"code": "quota_exceeded",
"details": {
"reason": "monthly_request_limit_reached",
"current_usage": 10000,
"limit": 10000,
"reset_at": "2026-11-01T00:00:00.000Z",
"plan_tier": "starter",
"suggestion": "Wait until the monthly reset (reset_at). Higher limits: Pro and Business, by card or by invoice (three months or more) — https://app.igregulator.io/billing, or email founder@igregulator.io."
}
}
Retry-After
integer

On the per-second limit (1). Not on the monthly quota — read details.reset_at.

X-RateLimit-Limit
integer

On the per-second limit only: the requests per second your plan allows.

X-RateLimit-Remaining
integer

On the per-second limit only: 0.

X-RateLimit-Reset
integer

On the per-second limit only: Unix epoch seconds of the next second.

X-Monthly-Quota-Limit
string

On the monthly quota only: the quota of your plan.

X-Monthly-Quota-Used
integer

On the monthly quota only: requests counted this month.

X-Monthly-Quota-Remaining
integer

On the monthly quota only: 0.

X-Monthly-Quota-Reset
string format: date-time

On the monthly quota only: when the counter starts again (ISO-8601).

Unexpected server error. Safe to retry.

Media type application/json

The body of every non-2xx response.

object
error
required

Human-readable summary. The wording can change; do not branch on it.

string
code
required

The error class. Mostly one per HTTP status, with two exceptions to know: quota_exceeded is a 429 for the monthly request quota and a 403 for a plan cap (watchlist size, webhook endpoints). A 409 carries conflict (it carried invalid_query before 1.24.0). Branch on the HTTP status and details.reason. forbidden is only ever sent to a signed-in MCP connection, never to an API key.

string
Allowed values: invalid_query invalid_slug invalid_license_id invalid_jurisdiction_code invalid_pagination auth_required auth_invalid auth_revoked payment_required forbidden not_found method_not_allowed conflict quota_exceeded rate_limited server_error
details
required

Always present. reason is always set; field and suggestion when they apply; limit errors add the numbers below.

object
reason
required

Machine-readable refinement of the top-level code — branch on this. Values: invalid_input, missing_required_parameter, conflicting_parameters, not_a_valid_hostname, hostname_too_long, invalid_pagination, invalid_cursor, since_exceeds_retention_window, invalid_event_type, invalid_jurisdiction, as_of_not_supported, api_key_missing, malformed_header, api_key_invalid, api_key_revoked, plan_inactive, plan_ended, export_requires_pro, geo_checks_require_business, watchlist_quota_exceeded, webhook_quota_exceeded, operator_not_found, license_not_found, jurisdiction_not_found, webhook_not_found, dataset_not_found, route_not_found, method_not_allowed, watchlist_duplicate, no_active_secret, quota_exceeded, monthly_request_limit_reached, per_second_limit_exceeded, watchlist_events_poll_limit, export_daily_limit_reached, geo_check_quota_exceeded, internal_error. A rejected webhook URL carries the SSRF check’s reason (invalid_url, invalid_scheme, empty_host, blocked_hostname, unresolvable_host, private_ip_blocked). New values can be added.

string
field

Present only when the error maps to a specific request input (query param, path param, body key). Omitted for errors that aren’t field-scoped (e.g. rate_limited, auth_revoked).

string
suggestion

A hint a person or an agent can act on. Present on every error today.

string
limit

On a limit error (429, and the 403 plan caps): the ceiling that was reached.

integer
current_usage

On a keyed limit error: how much of limit is used. Not on the keyless per-IP 429 or the watchlist-poll 429.

integer
reset_at

When the counter starts again — on the keyless per-IP 429, the monthly quota, the watchlist-poll limit and the export daily limit. Not on the per-second limit (retry after one second).

string format: date-time
plan_tier

The key’s plan, on plan-scoped limit errors and on export_requires_pro.

string
required_plan

On export_requires_pro: the lowest plan that includes the feature.

string
window

On watchlist_events_poll_limit: always hour.

string
key
additional properties
any

Example

{
"error": "Internal server error",
"code": "server_error",
"details": {
"reason": "internal_error",
"suggestion": "Retry with exponential backoff (1s / 2s / 4s). If the error persists past 4 seconds, surface failure state to the user instead of looping."
}
}