> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dairo.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Pagination

> Page through Dairo lists with one cursor: the pagination block, the cursor and limit parameters, and SDK helpers that follow it for you.

Long lists come back one page at a time. The cursor pattern is the same on every list that
paginates, so learn it once on messages and it works on letters, audit logs, and the event
ledger.

## The pagination block

Every [list response](/concepts/the-envelope) carries a `pagination` object:

```json theme={null}
{
  "object": "list",
  "data": [ /* … */ ],
  "pagination": {
    "nextCursor": "msg_8f2a4c",
    "hasMore": true
  }
}
```

| Field        | Type             | Meaning                                                      |
| ------------ | ---------------- | ------------------------------------------------------------ |
| `nextCursor` | `string \| null` | Cursor for the next page. `null` when there is no next page. |
| `hasMore`    | `boolean`        | Whether another page exists.                                 |

Treat `nextCursor` as opaque: it is not an offset, a timestamp, or an ID to build or
parse. Pass it back exactly as you got it. A list that fits in one response returns
`nextCursor: null` and `hasMore: false`.

## Get the next page

To fetch the next page, send the previous page's `nextCursor` as the `cursor` query
parameter. Set `limit` (1–100, default 25) to size each page. Stop when `nextCursor` comes
back `null`.

```bash theme={null}
# First page
curl "https://api.dairo.app/v1/messages?limit=50" \
  -H "Authorization: Bearer $DAIRO_API_KEY"

# Next page — pass the previous nextCursor back as ?cursor=
curl "https://api.dairo.app/v1/messages?limit=50&cursor=msg_8f2a4c" \
  -H "Authorization: Bearer $DAIRO_API_KEY"
```

### A manual paging loop

<CodeGroup>
  ```ts title="TypeScript" theme={null}
  let cursor: string | undefined;
  const all = [];
  do {
    const page = await dairo.messages.list({ limit: 100, cursor });
    all.push(...page.data);
    cursor = page.pagination.nextCursor ?? undefined;
  } while (cursor);
  ```

  ```python title="Python" theme={null}
  cursor = None
  all_messages = []
  while True:
      page = dairo.messages.list(limit=100, cursor=cursor)
      all_messages.extend(page.data)
      cursor = page.pagination.next_cursor
      if cursor is None:
          break
  ```
</CodeGroup>

### Or let the SDK do it

The SDKs ship a helper that follows the cursor for you and yields every item across all
pages, so you never hold a `cursor` yourself:

<CodeGroup>
  ```python title="Python" theme={null}
  # Iterates every message across every page, following nextCursor automatically.
  for message in dairo.messages.iter(inbox_id="inbox_123"):
      print(message.id)
  ```

  ```bash title="CLI" theme={null}
  # The CLI follows pagination for you when you ask for all rows.
  dairo messages list --inbox-id inbox_123 --all
  ```
</CodeGroup>

## Which lists paginate

Cursor pagination applies to the lists that grow without bound: messages, threads, letters
and their events, storage objects, audit logs, and a contact's message history. On those,
`nextCursor` walks the pages. Smaller collections — domains, inboxes, webhooks, API keys,
templates, audiences — return their full set in one response, with `nextCursor: null`.

<Note>
  Filters (such as `inboxId`, `threadId`, `direction`, or `channel=a2a` on messages) compose
  with pagination. Keep the same filters on every page so the cursor walks a stable result
  set.
</Note>

## Tailing the event stream

The event stream (`GET /v1/events`) pages with the same `pagination.nextCursor`, takes its
cursor as `since`, and adds `wait` and `tail` parameters for real-time long-polling. See
[the event ledger](/events/event-ledger) for that pattern.

<Warning>
  Paging with no filter walks an entire collection. For a large mailbox, add a filter
  (`inboxId`, `threadId`) and a bounded loop; for live updates, use the event stream's `wait`
  and `tail` rather than re-listing from the start.
</Warning>
