# Export (CSV / JSON)

URL: https://igregulator.io/docs/export/
Markdown: https://igregulator.io/docs/export.md

> Download every licence, operator or domain link as CSV or NDJSON, from the dashboard or GET /v1/export. Pro and above; every row keeps its status provenance.

Export gives you a whole dataset — or the slice you filtered to — as one file:
every licence, every operator, or every domain ↔ operator link we hold. The rows
are the same records the rest of the API serves, and each one carries its own
provenance, so a spreadsheet you hand to an auditor still says where every status
came from and when we read it.

**Export is part of Pro** (and Business and Enterprise). On Starter the Export
button and the endpoint are there, but they answer with what Pro includes, never
with data — not even a sample. Pro isn't open yet: email
[founder@igregulator.io](mailto:founder@igregulator.io) to get access or to hear
when it opens.

## From the dashboard

Signed in, both Explore views carry an **export: csv · json** control beside the
sort:

- **Operators / Licenses** (`app.igregulator.io/`) exports the licences the
  current filters match — search, jurisdiction, status, category, licence type,
  enforcement flags — every page of them, in the table's sort.
- **Domains** (`app.igregulator.io/domains`) exports the domain links of the
  domains the current filters match. A domain is exported with **every** link it
  has, as its table row shows it, so a brand licensed by two entities never comes
  out half-way.

The file has the same columns as the API export below.

## From the API

```bash
curl -s -OJ "https://api.igregulator.io/v1/export/licences?format=csv&jurisdiction=UKGC,MGA" \
  -H "Authorization: Bearer $IGREGULATOR_KEY"
# → igregulator-licences-2026-09-30.csv
```

`GET /v1/export/{dataset}` — a key is required (no anonymous exports).

| Dataset | One row per | Joins on |
| --- | --- | --- |
| `licences` | licence (`licenses` is accepted too) | `operator_slug` |
| `operators` | legal entity | `operator_slug` |
| `domains` | domain ↔ operator link | `operator_slug`, `license_id` |

| Parameter | Values |
| --- | --- |
| `format` | `csv` (default) or `json` — NDJSON, one object per line (`ndjson` works too). |
| `jurisdiction` | Comma-separated codes (`UKGC,MGA`), case-insensitive; repeating the parameter works too. `licences`: the licence's jurisdiction. `operators`: holds a licence there. `domains`: the jurisdiction of the licence the link's status comes from. An unknown code is a `400`. |
| `status` | Comma-separated licence statuses (`active`, `suspended`, `revoked`, `expired`, `pending`, `unknown`, `surrendered`, `not_in_register`). `operators`: holds a licence with that status (in the `jurisdiction` given, when both are). `domains`: the status of the licence the link answers to. |

Any other parameter is a `400` rather than being silently ignored — a filter we
dropped would hand you more rows than you asked for. `as_of` is not supported
(`400`, `details.reason: "as_of_not_supported"`): an export is the current state.
For a status on a past date, use `?as_of=` on `/v1/licenses/{id}`,
`/v1/operators/{slug}` or `/v1/check` ([point-in-time](https://igregulator.io/docs/point-in-time/)).

### Response headers

| Header | Meaning |
| --- | --- |
| `Content-Disposition` | `attachment; filename="igregulator-<dataset>-<YYYY-MM-DD>.<csv\|ndjson>"` |
| `X-Dataset-Generated-At` | When the rows were read (ISO-8601 UTC). |
| `X-Export-Row-Count` | Rows in the file (the CSV header / NDJSON `_meta` line not counted). |
| `X-Export-Daily-Limit` / `-Used` / `-Remaining` / `-Reset` | Your export allowance today — see [limits](https://igregulator.io/docs/export/#limits). |

The body is streamed. The row count and the rows come from one database
statement, so they describe the same moment; if the lines you received are fewer
than `X-Export-Row-Count`, the download was cut off — fetch it again.

## Formats

**CSV** — RFC 4180: UTF-8 without a byte-order mark, CRLF line ends, a header
row, a field quoted when it contains a comma, quote or line break. A list
(`license_types`, `jurisdictions`) is `|`-joined; an object (`upstream_ids`) is
its JSON text; null is an empty cell. A text value that starts with `=`, `+`,
`-` or `@` gets a leading `'` so a spreadsheet shows it instead of running it as
a formula — a UKGC trading name really is `@gamingfun`. In Excel, open the file
with Data → From Text/CSV so accented names come through as UTF-8.

**JSON** (`format=json`) — NDJSON. Line 1 is a metadata object; every other line
is one row, keys in the column order below, values untouched (lists as arrays,
nulls as `null`, no formula guard):

```json
{"_meta":{"dataset":"licences","generated_at":"2026-09-30T18:00:00.000Z","row_count":6664,"filters":{"jurisdiction":null,"status":null},"source":"api","format":"ndjson","columns":["license_id","license_number","…"],"as_of":null,"note":"…"}}
{"license_id":"4db0141c-…","license_number":"039028-R-319297-013","license_number_kind":"regulator","jurisdiction_code":"UKGC","status":"active","status_source_url":"https://www.gamblingcommission.gov.uk/downloads/business-licence-data.zip","status_observed_at":"2026-09-30T03:10:12.000Z","…":"…"}
```

No row has a `_meta` key, so skip the line that has one:

```bash
tail -n +2 igregulator-licences-2026-09-30.ndjson | jq -r 'select(.status != "active") | [.license_number, .status, .status_source_url] | @tsv'
```

```python
import pandas as pd
df = pd.read_json("igregulator-domains-2026-09-30.ndjson", lines=True).iloc[1:]
```

## What a row says

Every row carries the same truth the API serves — and no more:

- **A status comes with where and when we read it.** `status_source_url` is the
  page that published THIS status (an enforcement register, a revoked-licences
  list, an advisory notice), which is often not the register the licence is
  listed in (`source_url`); `status_observed_at` is when we read it. Cite
  `status_source_url`.
- **Only `active` means licensed now.** `not_in_register` means the register
  stopped listing the licence and published no reason — `not_listed_since` says
  since when and `last_listed_at` when we last saw it. It is not a revocation, and
  neither are `surrendered` or `unknown`. [How we verify](https://igregulator.io/methodology/) says
  which regulator page sets which status.
- **Kahnawake, Tobique and the Isle of Man publish no licence number.** For KH,
  TGC and IOM the `license_number` is a reference we assigned (`KH/IG/…`,
  `TGC/B2C/…`, `IOM/OGRA/…`), and `license_number_kind` says so:
  `igregulator_reference`, not `regulator`. Never quote it as the regulator's
  number.
- **A domain row is a link, with the link's own status.** `domain_status` is the
  domain's status under THAT operator (`delisted` = the regulator removed it from
  that licensee); the same domain can be `active` on one row and `delisted` on
  another. The licence columns are the operator's best licence — `active` first,
  then the most recently verified — the same one `/v1/check` reports.
- `upstream_status` is the register's own word, left out where it would
  contradict the status, exactly as on `/v1/check`.

### `licences` columns

| Column | Meaning |
| --- | --- |
| `license_id` | Our licence id — what `/v1/licenses/{id}` takes. |
| `license_number` | The licence number; for KH, TGC and IOM our reference. |
| `license_number_kind` | `regulator` or `igregulator_reference`. |
| `jurisdiction_code` | `UKGC`, `MGA`, `CW`, `KH`, `AN`, `TGC`, `IOM`. |
| `operator_slug`, `operator_name` | The licensee. |
| `status` | The licence status. |
| `status_qualifier` | `provisional_under_assessment` for a Curaçao licence under the CGA's final assessment; else empty. |
| `upstream_status` | The register's own word, where it doesn't contradict `status`. |
| `status_source_url`, `status_observed_at` | Where and when this status was read. |
| `last_listed_at` | When we last saw the licence listed. |
| `not_listed_since` | When the register stopped listing it; empty while listed. |
| `license_types`, `license_type_raw`, `license_category` | Types as named, the raw string, and our cross-jurisdiction category. |
| `issued_date`, `expiry_date` | Where the register publishes them. |
| `last_verified_at` | When we last read the register for it. |
| `source_url` | The register it is listed in. |

### `operators` columns

| Column | Meaning |
| --- | --- |
| `operator_id`, `operator_slug`, `operator_name` | The legal entity. |
| `registered_name` | The name the register gives next to it (UKGC: often a trading name). |
| `country` | ISO country code, where known. |
| `upstream_ids` | Its id per register; for KH, TGC and IOM a key made from the name. |
| `jurisdictions` | Where we hold a licence record for it, any status. |
| `active_jurisdictions` | Where it holds an `active` licence. |
| `licences_total`, `licences_active` | Licence records; those `active`. |
| `licences_last_verified_at` | When we last read a register about it. |
| `domains_total`, `domains_active` | Domain links; those whose own status is `active` (says nothing about the licence). |
| `created_at`, `updated_at` | When we first recorded it; when its own record last changed. |

There is no operator-level status: an operator licensed in two jurisdictions is
never collapsed into one. The `licences` export has each licence.

### `domains` columns

| Column | Meaning |
| --- | --- |
| `domain` | As the register lists it. |
| `operator_slug`, `operator_name` | The operator this link is to. |
| `domain_status` | The link's own status: `active`, `delisted`, … |
| `domain_association` | `direct` (the licensee runs it) or `white_label`. |
| `verification_url` | The regulator's own page for this domain (Curaçao certificate, Tobique seal), where one exists. |
| `standing` | `licensed` (link and licence both `active`), `licence_not_active`, `delisted`, or `other` — derived from `domain_status` and `license_status` on the same row. |
| `link_first_seen`, `link_last_verified` | When we first saw the link; when a source last confirmed it. |
| `license_id`, `jurisdiction_code`, `license_number`, `license_number_kind` | The licence the link's status comes from. |
| `license_status`, `status_qualifier`, `upstream_status` | That licence's status, as in `licences`. |
| `status_source_url`, `status_observed_at` | Where and when that status was read. |
| `license_last_verified_at` | When we last read that licence's register. |

## Limits

| Plan | Exports per UTC day |
| --- | --- |
| Starter, legacy trial | None — `402` |
| Pro | 10 |
| Business | 100 |
| Enterprise | Unlimited |

- The allowance is **per account**, and the dashboard and the API share it.
- An API export counts as **one** request against your monthly quota, whatever
  its size. A dashboard export doesn't touch the API quota.
- A refused request (`400`, `402`, `429`) uses neither. An export whose query
  failed before the first byte is given back.
- Today's counter resets at 00:00 UTC.

## Errors

Below Pro — `402`, with `X-Upgrade-URL`:

```json
{
  "error": "CSV/JSON export is a Pro feature",
  "code": "payment_required",
  "details": {
    "reason": "export_requires_pro",
    "plan_tier": "starter",
    "required_plan": "pro",
    "suggestion": "CSV/JSON export is part of Pro. Pro isn't open yet — email founder@igregulator.io to get access or to hear when it opens."
  }
}
```

Over today's allowance — `429`, with `Retry-After` (seconds to 00:00 UTC):

```json
{
  "error": "Daily export limit reached",
  "code": "rate_limited",
  "details": {
    "reason": "export_daily_limit_reached",
    "current_usage": 10,
    "limit": 10,
    "reset_at": "2026-10-01T00:00:00.000Z",
    "plan_tier": "pro",
    "suggestion": "…"
  }
}
```

`400 invalid_query` for a bad `format`, `jurisdiction`, `status` or an unknown
parameter (`details.field` names it), `400` `as_of_not_supported`, and `404`
`dataset_not_found` for a dataset other than the three. The full list is on
[error handling](https://igregulator.io/docs/errors/).

## Not included

- **`as_of`** — exports are the current state (see above).
- **Licence history** — use `/v1/licenses/{id}/history`, or
  `/v1/operators/{slug}/licenses?include_history=true`.
- **Regulatory actions** — `/v1/operators/{slug}/regulatory-actions`.
- **MCP** — there is no export tool on the [MCP server](https://igregulator.io/docs/mcp/); a file
  download isn't a tool call. Use the API.
