Skip to content

All licences for an operator

GET
/v1/operators/{slug}/licenses

Every licence record we hold for the operator, in every status, sorted by jurisdiction_code, license_number — the whole list in one response (not paginated). The same licence objects as in GET /v1/operators/{slug}, without the operator and its domains. Append ?include_history=true to embed the status-change timeline under each licence. ?as_of= is not read here — use /v1/operators/{slug} or /v1/licenses/{license_id} for a point-in-time status.

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
include_history
boolean

Exactly true or false (lower case); anything else — 1, yes, TRUE — is a 400. When true, each licence carries its history[].

All of the operator’s licences.

Media type application/json
object
operator_slug
required
string
licenses
required
Array
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
history

Only with ?include_history=true: every event for this licence, most recent first ([] when there is none). An event with corrected_at set was withdrawn.

Array
object
id
required
string format: uuid
changed_at
required

When we recorded the event — the read that found it, not the date the regulator made the decision.

string format: date-time
previous_status
required

The status before this event (the LicenseStatus values). null on the first event of a licence (change_type: created).

string | null
Allowed values: active suspended revoked expired pending unknown surrendered not_in_register
new_status
required

The status this event set (the LicenseStatus values).

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

created — the first time we recorded the licence (the start of our observation window, not its issue date). status_change — the status moved. data_update — a field other than the status changed. revoked — how some events label a move to revoked instead of status_change. renewed — in the vocabulary, written by no loader today. Read new_status for what the licence became, never change_type alone, and skip any event with corrected_at set (it was withdrawn).

string
Allowed values: created status_change renewed revoked data_update
source_url
required

The regulator page this event was read from.

string
detected_by
required

What wrote the event: scraper:<name>:<run id> for a register read, seal-verify:<source> for the certificate re-verifier, correction:… / backfill:… for our own corrections.

string
note
required

What we observed, in words — e.g. “No longer listed in the public register since 2026-08-04.”

string | null
snapshot_sha256
required

SHA-256 of the exact payload this change was read from, stored on our side. null for events recorded before 2026-09-17 and for runs whose snapshot could not be filed. Ask support for that payload by hash if you need to audit a claim — a hash you can check against your own copy of the regulator’s page is the point.

string | null
snapshot_fetched_at
required

When that payload was fetched from the regulator.

string | null format: date-time
corrected_at
required

Set when a later review found this event asserted something we had not read from the regulator. The event is kept for the audit trail; treat it as withdrawn and read correction_note.

string | null format: date-time
correction_note
required
string | null
license_id
required
string format: uuid
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.

include_history is not true / false, 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": "include_history must be \"true\" or \"false\"",
"code": "invalid_query",
"details": {
"reason": "invalid_input",
"field": "include_history",
"suggestion": "Use ?include_history=true or omit the parameter (defaults to false)."
}
}

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."
}
}