# Pagination

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

> How limit and offset page through iGregulator's list endpoints — operator search, jurisdiction rosters, regulatory actions, watchlists — defaults and caps.

Paginated list endpoints accept `limit` and `offset` query parameters and
return a `total` in the envelope so callers know when they've walked to
the end.

## Endpoints that paginate

| Endpoint | `limit` default | `limit` max | Order |
| --- | --- | --- | --- |
| `GET /v1/operators/search?q=…` | 20 | 100 (keyless: 3 rows) | Exact slug match, then names starting with `q`, then the rest — each group by display name |
| `GET /v1/jurisdictions/:code/operators` | 50 | 200 | Internal operator id — stable across pages, not alphabetical |
| `GET /v1/operators/:slug/regulatory-actions` | 20 | 100 | `?sort=` — `date_desc` (default), `date_asc`, `amount_desc`, `type_asc` |
| `GET /v1/watchlist/operators` | 50 | 200 | Most recently added first |

`offset` defaults to 0 and is zero-based. High offsets have linear scan
cost; prefer a stable cursor if you're walking 10k+ rows.

A `limit` outside the range or a negative `offset` is a `400`, not a
silent clamp — `invalid_pagination` on `/v1/jurisdictions/:code/operators`,
`invalid_query` on the others ([errors](https://igregulator.io/docs/errors/)). Refused requests
aren't charged against your monthly quota.

**Not paginated** — these return everything in one response and ignore
`limit` / `offset`: `GET /v1/operators/:slug/licenses`,
`GET /v1/licenses/:license_id/history`, and the `licenses[]` / `domains[]`
arrays inside `GET /v1/operators/:slug`. `GET /v1/watchlist/events` uses a
cursor instead ([watchlist](https://igregulator.io/docs/watchlist/)), and
`GET /v1/webhooks/:id/deliveries` takes only a `limit` (1–200, default 100).

## Response envelope

```json
{
  "jurisdiction_code": "UKGC",
  "total": 2797,
  "limit": 200,
  "offset": 400,
  "operators": [ ]
}
```

- `total` — rows matching the query, ignoring limit/offset.
- `operators[].length <= limit`.
- Next to `total`, `limit` and `offset`, each endpoint names its own
  context and rows: `q` + `operators` + `_meta` on search,
  `operator_slug` + `sort` + `regulatory_actions` on regulatory actions.
  See [endpoints](https://igregulator.io/docs/endpoints/#response-shape-conventions).

## Walking a result set

```bash
# Bash loop — fetch all operators for UKGC.
offset=0
while :; do
  resp=$(curl -sH "Authorization: Bearer $KEY" \
    "https://api.igregulator.io/v1/jurisdictions/UKGC/operators?limit=200&offset=$offset")
  rows=$(echo "$resp" | jq '.operators | length')
  [ "$rows" -eq 0 ] && break
  echo "$resp" | jq '.operators[]'
  offset=$((offset + rows))
done
```

## Why not cursor-based?

Offset pagination is simpler to document, easier for UIs that render
page numbers, and cheap for our table sizes (~5,600 operators, ~6,600
licences). When any list crosses 100k rows we'll add a `cursor` query
param alongside — offset stays supported for back-compat.

## Rate-limit interplay

Each paginated request is one API call against your quota. A full
sweep of the ~2,800 UKGC operators at `limit=200` is 14 pages (15 calls
with the loop above, which stops on the empty page) — well within
Starter's 10k monthly quota, trivial within Pro's 100k.
