---
title: "Pagination"
description: "How list operations return a page at a time, and how to read the next page with a cursor."
---

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

# Pagination

List operations answer one page at a time, in a stable order. Each page carries opaque cursors that lead to the pages around it.

## Request a page

| Parameter | Default | Meaning |
| --- | --- | --- |
| `limit` | `25` | The most items in one page, from 1 to 100. Reading more takes another page. |
| `cursor` | None | A cursor from the previous answer's `pageInfo`. Leave it out for the first page. |
| `direction` | `forward` | Which way to traverse the order from `cursor`: `forward` or `backward`. |

Most lists are `GET` operations and take these as query parameters. Searches sent with `POST` take them in the JSON body instead, such as [`agenda.appointments.search`](/api/agenda/appointments/search/) and [`raiz.knowledge.search`](/api/raiz/knowledge/search/).

A few operations set their own `limit` bounds. `agenda.appointments.list` allows up to 500, and `raiz.knowledge.search` defaults to 8 with a maximum of 20. The operation's reference states its bounds.

## Read the page

A list's `data` is a page:

| Field | Meaning |
| --- | --- |
| `object` | Always `"list"`. |
| `data` | The items on this page. |
| `pageInfo.hasNextPage` | Whether a later page exists in the current order. |
| `pageInfo.hasPreviousPage` | Whether an earlier page exists in the current order. |
| `pageInfo.nextCursor` | The cursor for the next page, or `null` when there is none. |
| `pageInfo.previousCursor` | The cursor for the previous page, or `null` when there is none. |

For example, the first page of [`account.apiKeys.list`](/api/account/api-keys/list/), one key long. The call needs the `account:api-keys:read` scope:

```bash
curl "https://api.getarbol.com/v1/account/api-keys?limit=1" \
  --header "Authorization: Bearer $ARBOL_API_KEY"
```

```json
{
  "success": true,
  "data": {
    "object": "list",
    "data": [
      {
        "id": "exampleClientId0001",
        "name": "Billing backend",
        "scopes": ["account:billing:read"],
        "createdAt": "2026-10-01T15:00:00.000Z",
        "createdBy": null,
        "restrictions": null
      }
    ],
    "pageInfo": {
      "hasNextPage": true,
      "hasPreviousPage": false,
      "nextCursor": "EXAMPLE_CURSOR_2",
      "previousCursor": null
    }
  },
  "error": null,
  "metadata": { "requestId": "00000000-0000-4000-8000-000000000003" }
}
```

## Read every page

Call without a cursor, then call again with `cursor` set to `pageInfo.nextCursor` while `pageInfo.hasNextPage` is `true`. Keep every other parameter the same.

### curl

```bash
cursor=""
while :; do
  params=(--data-urlencode "limit=100")
  if [ -n "$cursor" ]; then params+=(--data-urlencode "cursor=$cursor"); fi
  page=$(curl -sS --get https://api.getarbol.com/v1/account/api-keys "${params[@]}" \
    --header "Authorization: Bearer $ARBOL_API_KEY")
  jq -e '.success' <<<"$page" >/dev/null || { echo "$page" >&2; break; }
  jq -c '.data.data[]' <<<"$page"
  [ "$(jq -r '.data.pageInfo.hasNextPage' <<<"$page")" = "true" ] || break
  cursor=$(jq -r '.data.pageInfo.nextCursor' <<<"$page")
done
```

### TypeScript

```ts
import { createClient } from "@arbol-ai/sdk";

const arbol = createClient({
  baseUrl: "https://api.getarbol.com",
  token: process.env.ARBOL_API_KEY!,
});

let cursor: string | undefined;
do {
  const { data: page } = await arbol.account.apiKeys.list({ limit: 100, cursor });
  for (const key of page.data) console.log(key.id, key.name);
  cursor = page.pageInfo.hasNextPage ? (page.pageInfo.nextCursor ?? undefined) : undefined;
} while (cursor);
```

## Cursor rules

- **Cursors are opaque.** Use them exactly as the API returned them. Don't build, edit or decode one.
- **A cursor belongs to its query.** Send it with the same filters that produced it. Raíz refuses a cursor from another list with `400 INVALID_CURSOR`.
- **Some reads don't page.** A few take only `limit` and answer no `pageInfo`, such as [`agenda.firstAvailability.get`](/api/agenda/first-availability/get/). Their reference shows it.

Source: https://docs.getarbol.com/en/developers/concepts/pagination/index.mdx
