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

# Pagination

> Cursor pagination for every Drin list endpoint: ?limit, ?cursor, nextCursor, and detecting the end of a list.

Every list endpoint returns a forward-only cursor page. You ask for a page size, get back a slice of results and an opaque cursor, then feed that cursor in to fetch the next slice.

List responses always have the same envelope: an array of results in `data`, and a `nextCursor` you pass to the next request.

```json A page theme={null}
{
  "data": [
    { "id": "msg_01HZX…", "subject": "Receipt", "status": "delivered" },
    { "id": "msg_01HZW…", "subject": "Welcome", "status": "delivered" }
  ],
  "nextCursor": "eyJpZCI6Im1zZ18wMUhaVyJ9"
}
```

## Query parameters

<ParamField query="limit" type="integer">
  Page size, `1`–`100`. Defaults to a sensible value (typically `20`) when omitted.
</ParamField>

<ParamField query="cursor" type="string">
  The `nextCursor` from the previous response. Omit it to start at the first page. Cursors are opaque — don't construct or parse them.
</ParamField>

Endpoint-specific filters (such as `status` or `q` on `GET /v1/emails`) combine with `limit` and `cursor`. Keep the filters identical across calls in a walk — a cursor is only valid for the same query it was issued from.

## Walking a list

Request a page, process `data`, then repeat with `?cursor=<nextCursor>` until `nextCursor` comes back `null`.

<CodeGroup>
  ```bash cURL theme={null}
  # First page
  curl "https://api.drin.run/v1/emails?limit=50" \
    -H "Authorization: Bearer $DRIN_API_KEY"

  # Next page — feed back the previous nextCursor
  curl "https://api.drin.run/v1/emails?limit=50&cursor=eyJpZCI6Im1zZ18wMUhaVyJ9" \
    -H "Authorization: Bearer $DRIN_API_KEY"
  ```

  ```typescript Node.js theme={null}
  import { DrinClient } from "@drin00/sdk";

  const drin = new DrinClient({ apiKey: process.env.DRIN_API_KEY });

  // Manual: one page at a time
  const page = await drin.emails.list({ limit: 50 });
  console.log(page.data, page.nextCursor);

  // Or let the SDK walk every page for you
  for await (const message of drin.emails.paginate({ status: "bounced" })) {
    console.log(message.id, message.subject);
  }
  ```
</CodeGroup>

## End of the list

When there are no more results, `nextCursor` is `null`. That is the only end-of-list signal — don't rely on `data` being shorter than `limit`, which can happen mid-list.

```json The final page theme={null}
{
  "data": [ { "id": "msg_01HZA…", "subject": "Magic link", "status": "delivered" } ],
  "nextCursor": null
}
```

<Tip>
  **Let the SDK do it.** The SDK exposes `.paginate()` on every list resource — an async iterator that fetches each page lazily and yields one item at a time, so you never touch a cursor. The CLI auto-paginates too.
</Tip>

<Note>
  **Stable ordering.** Pages are ordered newest-first by creation time. Because the cursor is anchored to a row rather than an offset, inserting new rows while you paginate won't shift or duplicate results across pages.
</Note>
