Skip to main content
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 carries a pagination object:
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.

A manual paging loop

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:

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.
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.

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 for that pattern.
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.