Skip to content

Licence detail by id

GET
/v1/licenses/{license_id}

One licence by its iGregulator UUID — match.license_id on /v1/check, or id in an operator’s licenses — not the licence number (for a number use GET /v1/check?license_number=). Returns the licence, the owning operator’s id, slug and name, the status’s own provenance (status_source_url, status_observed_at, and not_listed_since / last_listed_at for a licence the register no longer lists), and a single-resource _meta with scraped_at, source_url (the status’s source page when we have it, else the register) and confidence_hint.

license_id
required
string format: uuid

The licence’s iGregulator UUID: match.license_id on /v1/check, or id in an operator’s licenses. Not the licence number.

Example

d9832e56-10bb-458b-82af-ac7762f7a502
as_of
string

Point-in-time lookup. Adds an as_of object reconstructing this licence’s status 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

Licence + provenance.

Media type application/json

A licence with its operator and a single-resource provenance _meta. Used by /v1/licenses/{license_id}.

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
operator_id
required
string format: uuid
operator_slug
required
string
operator_name
required

The operator’s display_name.

string
confidence_score
required

An internal record-quality weight, 1 unless a loader lowered it. Not the match confidence of /v1/check, and not a statement about the licence — do not decide on it.

number
_meta
required

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.

license_id is not a UUID, or as_of is not one of the two accepted formats or lies in the future. An unknown id is answered 404 before as_of is read.

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": "license_id must be a UUID",
"code": "invalid_license_id",
"details": {
"reason": "invalid_input",
"field": "license_id",
"suggestion": "license_id is the `id` UUID returned by /v1/operators/:slug/licenses — not the human-readable licence_number."
}
}

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 licence has that id.

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": "License not found",
"code": "not_found",
"details": {
"reason": "license_not_found",
"field": "license_id",
"suggestion": "No licence with that UUID. Fetch the operator first via /v1/operators/:slug and use the `id` field from the returned licences array."
}
}

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