# Pagination

> Page through Kustomer list endpoints with page and pageSize, follow links.next, and handle cursor-based and very large result sets.

Source: https://developer.kustomer.com/docs/concepts/pagination

List endpoints return results a page at a time. Most use **page numbers**; a few use **cursors**. Either way, the safest approach is to follow `links.next` until it's `null`.

## Page-number pagination

Pass `page` (starting at 1) and `pageSize`:

```bash
curl "https://acme.api.kustomerapp.com/v1/customers?page=2&pageSize=100" \
  -H "Authorization: Bearer $KUSTOMER_API_KEY"
```

```json title="200 OK"
{
  "meta": { "pageSize": 100, "page": 2, "total": 4210, "totalPages": 43 },
  "links": {
    "self": "/v1/customers?page=2&pageSize=100",
    "first": "/v1/customers?page=1&pageSize=100",
    "prev": "/v1/customers?page=1&pageSize=100",
    "next": "/v1/customers?page=3&pageSize=100"
  },
  "data": [ ... ]
}
```

| Field                                     | Meaning                                                |
| ----------------------------------------- | ------------------------------------------------------ |
| `meta.page`, `meta.pageSize`              | The page you got and its size                          |
| `meta.total`, `meta.totalPages`           | Total matches and pages, on endpoints that report them |
| `links.next`                              | Path to the next page, or `null` on the last page      |
| `links.prev`, `links.first`, `links.last` | Other pages, where available                           |

_An animated walk through a 47-record customer list. The reader picks a pageSize of 5, 10 or 20 and follows links.next one request at a time: each GET goes out, the highlighted page window slides along the record strip, and the response shows meta (pageSize, page, total, totalPages) and links (self, first, prev, next), with prev null on page 1 and next null on the last page, where the loop stops. A cursor toggle switches to an audit-log list paged with count and a before cursor carried inside links.next, which has no page numbers to jump to. A panel below shows that search stops at the first 10,000 matches (page times pageSize), so large result sets must be split into updated_at windows._

### Page sizes

`pageSize` limits vary by endpoint. The reference page for each list shows its default and maximum.

| Endpoints          | Typical maximum |
| ------------------ | --------------- |
| Most lists         | 100             |
| Users and teams    | 500             |
| Search             | 500             |
| Conversation times | 1,000           |

Request the largest page size you can handle. Fewer, larger pages use less of your [rate limit](https://developer.kustomer.com/docs/concepts/rate-limits).

### Iterate every page

```ts title="Node.js"
async function* listAll(path: string) {
  let next: string | null = `${path}${path.includes('?') ? '&' : '?'}pageSize=100`
  while (next) {
    const res = await kfetch(next) // retries 429s; see Making requests
    const body = await res.json()
    yield* body.data
    next = body.links?.next ?? null
  }
}

for await (const customer of listAll('/v1/customers')) {
  console.log(customer.id, customer.attributes.name)
}
```

```python title="Python"
def list_all(path):
    url = f"{base}{path}{'&' if '?' in path else '?'}pageSize=100"
    while url:
        body = requests.get(url, headers=headers).json()
        yield from body["data"]
        nxt = body.get("links", {}).get("next")
        url = f"{base}{nxt}" if nxt else None

for customer in list_all("/v1/customers"):
    print(customer["id"], customer["attributes"]["name"])
```

```bash title="kusty"
# --all follows links.next and streams one JSON object per line
kusty api GET /v1/customers --all --fields id,attributes.name
```

`links.next` is a path. Prefix it with your base URL.

## Cursor pagination

A few endpoints, such as the [audit log](https://developer.kustomer.com/api/core-resources/query-events-associated-with-your-organization), page with `before` and `after` cursors instead. Their `links.next` already contains the cursor:

```json
{ "links": { "next": "/v1/audit-logs?count=1&before=62799c44a96e7f160834199c" } }
```

The same "follow `links.next`" loop works without changes.

## Large result sets

Deep pagination gets slower and can hit limits. For exports and syncs:

- **Search returns only the first 10,000 matches.** A request where `page` × `pageSize` is over 10,000 fails with `400`, and `pageSize` goes up to 500. Split large queries into time windows on `updated_at` or `created_at`, and page through each window. See [Search recipes](https://developer.kustomer.com/docs/guides/search-recipes#export-more-than-the-page-limit).
- **Sort for stability.** Records created or updated while you page can shift between pages. Window by time, or sort by a stable field where the endpoint supports it.
- **Incremental sync.** After the first full pass, query only records updated since your last run instead of re-listing everything.
- **Subscribe instead of polling.** If you poll for changes, consider [outbound webhooks](https://developer.kustomer.com/docs/events/outbound-webhooks).
