# Pagination

# Pagination

Every collection returns the same envelope:

```json
{
  "data": [ … ],
  "next_cursor": "4788"
}
```

Ask for the next page by passing that value back:

```bash
curl "https://acme.open-helpdesk.com/api/v1/tickets?limit=100&cursor=4788" \
  -H "Authorization: Bearer $OHD_TOKEN"
```

**Stop when `next_cursor` is `null`.** Do not count pages, and do not stop
because a page came back shorter than `limit` — a filtered page can be short and
still have more behind it.

```js
let cursor = null;
do {
  const url = new URL("https://acme.open-helpdesk.com/api/v1/tickets");
  url.searchParams.set("limit", "100");
  if (cursor) url.searchParams.set("cursor", cursor);

  const page = await fetch(url, {
    headers: { authorization: `Bearer ${token}` },
  }).then((r) => r.json());

  for (const ticket of page.data) handle(ticket);
  cursor = page.next_cursor;
} while (cursor);
```

## Why cursors, not offsets

Because your agents keep working while your script reads. With `offset=200`, a
ticket created during the walk shifts every later row down by one: you skip one
and you read another twice, silently. A cursor points at a row, not at a
position, so it survives writes.

`limit` is capped at 100 and defaults to 25.

## Syncing incrementally

For tickets, `updated_since` is the one you want. Store the instant you started,
walk everything modified since the last run, then keep the new instant:

```bash
curl "https://acme.open-helpdesk.com/api/v1/tickets?updated_since=2026-09-01T00:00:00Z" \
  -H "Authorization: Bearer $OHD_TOKEN"
```

Take the timestamp *before* the run, not after: anything modified while you were
reading then arrives on the next pass instead of falling into the gap.
