---
title: Pagination
description: Cursor paging, and why there are no page numbers.
---

List endpoints are cursor-paginated.

```bash
curl "https://api.zevcampaign.com/v1/contacts?limit=50" \
  -H "Authorization: Bearer sk_live_your_key_here"
```

```json
{
  "data": [ "…50 contacts…" ],
  "meta": {
    "limit": 50,
    "has_more": true,
    "next_cursor": "Y3RfOHZxMnhrNG0"
  }
}
```

Pass the cursor back for the next page:

```bash
curl "https://api.zevcampaign.com/v1/contacts?limit=50&cursor=Y3RfOHZxMnhrNG0" \
  -H "Authorization: Bearer sk_live_your_key_here"
```

When `has_more` is `false`, `next_cursor` is `null` and you are done.

## Walking everything

```js
let cursor = null;
const all = [];

do {
  const url = new URL('https://api.zevcampaign.com/v1/contacts');
  url.searchParams.set('limit', '100');
  if (cursor) url.searchParams.set('cursor', cursor);

  const res = await fetch(url, {
    headers: { Authorization: `Bearer ${process.env.ZEVCAMPAIGN_KEY}` },
  });
  const { data, meta } = await res.json();

  all.push(...data);
  cursor = meta.next_cursor;
} while (cursor);
```

Loop on `next_cursor`, not on `data.length`. A page can come back
short and still have more behind it.

## `limit`

Between 1 and 100. Defaults to 20.

Anything outside that is refused with `invalid_limit` rather than
clamped. Silently turning `limit=1000` into `limit=100` means your
pagination looks broken and you spend an afternoon finding out why.

## Why no page numbers

Offset paging drifts on a moving list. If somebody subscribes while
you are on page 3, page 4 starts one row late and you never see that
row. On an audience that changes constantly, that is a contact quietly
missing from an export.

Cursors are anchored to a row, so they stay correct while the list
changes underneath them.

## Treat cursors as opaque

A cursor is a string we issued. Pass it back unchanged.

Do not decode it, build one, or store one for later: a cursor from
last week may point at something that has moved. One we did not issue
is refused with `invalid_cursor`.