> ## Documentation Index
> Fetch the complete documentation index at: https://test-8862363a-tembo-docs-api-pagination.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Pagination

> Page through list endpoints with cursor and limit, and read nextCursor correctly.

Every `v1` list endpoint returns a bounded page of results and an opaque cursor
that points at the next page. There is no page number: you follow `nextCursor`
until it is `null`.

## Request parameters

All list endpoints accept the same two query parameters.

| Parameter | Type               | Default | Description                                                                                          |
| --------- | ------------------ | ------- | ---------------------------------------------------------------------------------------------------- |
| `limit`   | integer, `1`–`100` | `50`    | Maximum number of items in the page.                                                                 |
| `cursor`  | string             | none    | Opaque cursor returned as `nextCursor` by the previous request. Omit it entirely for the first page. |

```bash theme={null}
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.tembo.io/v1/sessions?limit=25"
```

<Note>
  Self-hosted deployments use the same parameters behind their own origin and
  the `/api/public-api` prefix, for example
  `https://tembo.example.com/api/public-api/v1/sessions?limit=25`.
</Note>

## Response shape

Paginated responses always wrap results in `items` and pair them with
`nextCursor`. `nextCursor` is `null` on the last page.

```json theme={null}
{
  "items": [
    {
      "id": "123e4567-e89b-12d3-a456-426614174000",
      "title": "Fix flaky checkout test",
      "createdAt": "2026-09-10T22:57:34.000Z"
    }
  ],
  "nextCursor": "q:1757548800000:1757545200000:123e4567-e89b-12d3-a456-426614174000"
}
```

Item fields vary by endpoint; the `items` and `nextCursor` envelope does not.

Treat the cursor as opaque. Its format differs per endpoint — some endpoints
return the ID of the last item, `/v1/sessions` returns a composite sort key —
and it can change without notice. Pass the value back unmodified.

## Walk every page

Stop when `nextCursor` is `null`, not when a page looks short. Some endpoints
bound how much data they scan per request and can return a partial page while
more results remain.

<CodeGroup>
  ```bash cURL theme={null}
  url="https://api.tembo.io/v1/sessions?limit=100"
  while :; do
    page=$(curl -s -H "Authorization: Bearer $TEMBO_API_KEY" "$url")
    echo "$page" | jq -r '.items[].id'
    cursor=$(echo "$page" | jq -r '.nextCursor // empty')
    [ -z "$cursor" ] && break
    url="https://api.tembo.io/v1/sessions?limit=100&cursor=$(jq -rn --arg c "$cursor" '$c|@uri')"
  done
  ```

  ```ts TypeScript theme={null}
  const items = [];
  let cursor: string | null = null;

  do {
    const query = new URLSearchParams({ limit: "100" });
    if (cursor) query.set("cursor", cursor);

    const response = await fetch(`https://api.tembo.io/v1/sessions?${query}`, {
      headers: { Authorization: `Bearer ${process.env.TEMBO_API_KEY}` },
    });
    const page = await response.json();

    items.push(...page.items);
    cursor = page.nextCursor;
  } while (cursor);
  ```
</CodeGroup>

Keep every filter and sort parameter identical across the requests in one walk.
A cursor encodes its position in the ordered, filtered result set, so reusing it
with different filters or a different `sortBy` returns wrong results or a
`400`.

## Total counts

Counting rows is extra work, so totals are opt-in. `GET /v1/sessions`,
`GET /v1/pull-requests`, and `GET /v1/integrations` accept `includeTotal=true`
and then add `totalCount` to the response. Other list endpoints do not return a
total.

```bash theme={null}
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.tembo.io/v1/sessions?limit=25&includeTotal=true"
```

`totalCount` reflects everything matching your filters, not just the current
page. On `/v1/sessions`, `includeTotal=true` is rejected with a `400` when you
also pass `state=active`.

## Errors

Invalid pagination input returns `400` with an `error` message.

```json theme={null}
{ "error": "Invalid request" }
```

Common causes:

* `limit` outside `1`–`100`, or a non-integer value such as `1.5`, `1e2`, or `0`.
* A cursor that does not match the endpoint's cursor format.
* Unknown query parameters. List endpoints reject any parameter they do not
  define, so a stray `page=2` fails the whole request.

## Constraints and pitfalls

* **No `page` parameter.** Page numbers only exist on the deprecated
  `/session/list` and `/session/search` endpoints. Migrating to `v1` means
  replacing `page` with the `nextCursor` loop above.
* **Short pages are not the end of the list.** Always check `nextCursor`.
* **Cursors are not bookmarks.** They are tied to the exact query that produced
  them, so store the filters alongside a cursor if you resume a walk later.
* **Pages count against rate limits.** Each page is one request, so prefer
  `limit=100` for bulk reads. See [Overview](/api) for the current limits.
