# Pagination

Traverse result sets safely with opaque cursors.

List and query responses return `data`, `has_more`, and `next_cursor`. When `has_more` is `true`, pass `next_cursor` back unchanged. Stop when `has_more` is `false`.

The script below requires Node.js 18 or newer for built-in `fetch`. Set `MICRO_TEAM_ID` and `MICRO_API_KEY` first, then save it as `paginate.mjs` and run `node paginate.mjs`.

```js
const teamId = process.env.MICRO_TEAM_ID;
const apiKey = process.env.MICRO_API_KEY;
if (!teamId || !apiKey) throw new Error("Set MICRO_TEAM_ID and MICRO_API_KEY");

let cursor;
do {
  const response = await fetch(
    `https://developers.micro.so/v2/prism/${teamId}/contact/query`,
    {
      method: "POST",
      headers: {"content-type": "application/json", "x-api-key": apiKey},
      body: JSON.stringify({query: {select: ["full_name", "email"], limit: 50, cursor}}),
    },
  );
  if (!response.ok) throw new Error(`Micro returned ${response.status}`);
  const page = await response.json();
  for (const record of page.data) console.log(record.id, record.properties);
  if (page.has_more && !page.next_cursor) {
    throw new Error("Invalid pagination response: has_more without next_cursor");
  }
  cursor = page.has_more ? page.next_cursor : undefined;
} while (cursor);
```

Cursors are opaque and currently offset-based. Do not parse them or reuse them with a changed filter or sort. Records added or removed during a long traversal can shift boundaries, so consistency-sensitive jobs should record processed IDs and make processing idempotent.

The page limit is capped at 50. Ask for totals only when needed: `include_total` costs an additional counting pass.
