# Pagination

Page through list endpoints with page and limit, and stop when a page comes back short.

Canonical: https://chikaraintel.com/docs/api/pagination

## Parameters

| Parameter | Default | Notes |
| --- | --- | --- |
| `page` | 1 | 1-based |
| `limit` | 20 | Up to 100, or 200 on `GET /v1/events`. A larger value is capped |

## The meta object

meta:

```json
{
  "total": 180,
  "rows_per_page": 50,
  "page": 2,
  "total_pages": 4,
  "links": {
    "first-page": {
      "href": "/v1/events?eventType=move&limit=50&page=1",
      "method": "GET"
    },
    "previous-page": {
      "href": "/v1/events?eventType=move&limit=50&page=1",
      "method": "GET"
    },
    "current-page": {
      "href": "/v1/events?eventType=move&limit=50&page=2",
      "method": "GET"
    },
    "next-page": {
      "href": "/v1/events?eventType=move&limit=50&page=3",
      "method": "GET"
    },
    "last-page": {
      "href": "/v1/events?eventType=move&limit=50&page=4",
      "method": "GET"
    }
  }
}
```

## Stop on a short page

Counting every match on a large filter can take longer than the API allows, so on broad queries `total` and `total_pages` fall back to an estimate for the whole table. A filter that matches four events can report thousands of pages.

So loop on the page itself. Keep going while a page is full, stop as soon as one returns fewer rows than `limit`, and set a hard cap on pages so a mistake can't run away. The samples below use 50 rows and a cap of 20 pages.

**cURL**

```bash
page=1
while [ "$page" -le 20 ]; do
  body=$(curl -sS -G "https://api.chikaraintel.com/v1/events" \
    -H "Authorization: Bearer $CHIKARA_API_TOKEN" \
    --data-urlencode "eventType=move" \
    --data-urlencode "limit=50" \
    --data-urlencode "page=$page")
  rows=$(echo "$body" | jq '.success | length')
  echo "$body" | jq -c '.success[]'
  [ "$rows" -lt 50 ] && break
  page=$((page + 1))
done
```

**JavaScript**

```javascript
const limit = 50;
const maxPages = 20;
const rows = [];

for (let page = 1; page <= maxPages; page++) {
  const params = new URLSearchParams({ eventType: "move", limit: String(limit), page: String(page) });
  const res = await fetch(`https://api.chikaraintel.com/v1/events?${params}`, {
    headers: { Authorization: `Bearer ${process.env.CHIKARA_API_TOKEN}` }
  });
  if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);

  const { success } = await res.json();
  rows.push(...success);
  if (success.length < limit) break;
}

console.log(rows.length);
```

**Python**

```python
import os
import requests

LIMIT = 50
MAX_PAGES = 20
rows = []

for page in range(1, MAX_PAGES + 1):
    res = requests.get(
        "https://api.chikaraintel.com/v1/events",
        headers={"Authorization": f"Bearer {os.environ['CHIKARA_API_TOKEN']}"},
        params={"eventType": "move", "limit": LIMIT, "page": page},
        timeout=30,
    )
    res.raise_for_status()
    batch = res.json()["success"]
    rows.extend(batch)
    if len(batch) < LIMIT:
        break

print(len(rows))
```

## Endpoints that don't paginate

Single-record endpoints return `meta: null`. So do `GET /v1/organisations/{organisationReference}/people` and the two ownership endpoints, which return everything in one response.

## Related

- https://chikaraintel.com/docs/api.md
- https://chikaraintel.com/docs/api/errors.md
- https://chikaraintel.com/docs/limits.md
