# Pagination

List endpoints in the Rho API return results in pages using **opaque cursor-based pagination**. You ask for a page size, the response includes the items plus a cursor to the next page, and you follow that cursor to fetch each subsequent page.

Cursors are stable across changes to the underlying data: new items inserted while you are iterating will not shift existing pages or cause items to be skipped or duplicated.

## Request parameters

Every list endpoint accepts the same two pagination parameters as query string values:

| Parameter | Type | Notes |
|  --- | --- | --- |
| `page_size` | integer | How many items to return. Each endpoint defines its own minimum, maximum, and default - see the [API Reference](/api/v1/openapi). Values outside the allowed range return `400 Bad Request`. |
| `page_token` | string | Opaque cursor returned as `next_page_token` by a previous response. Omit to fetch the first page. |


Cursors are opaque - pass back exactly the string you received.

A token returned by one endpoint is only valid for that **same endpoint with the same filters and sort order**. Using a cursor with changed filters or sorting will result in `400 Bad Request`.

## Response shape

Every paginated response wraps the items in a top-level array and includes a `page` object:

```json
{
  "transactions": [
    { "id": "…", "amount": { "amount": 1299, "currency": "USD" }, "…": "…" },
    { "id": "…", "amount": { "amount": 4500, "currency": "USD" }, "…": "…" }
  ],
  "page": {
    "next_page_token": "eyJvIjoxMDAsImQiOiIyMDI2LTA1LTE0In0"
  }
}
```

The array key matches the resource being listed (`accounts`, `transactions`, …). The `page.next_page_token` field is either:

- A string - pass it as `page_token` on the next request to fetch the following page.
- `null` - you have reached the last page; there is nothing more to fetch.


## Walking through every page

The canonical iteration pattern is a loop that stops when the cursor goes null. In Python:

```python
import os
import requests

def list_all_transactions(account_id: str):
    token = os.environ["RHO_API_TOKEN"]
    page_token = None
    while True:
        params = {"account_id": account_id, "page_size": 100}
        if page_token is not None:
            params["page_token"] = page_token

        resp = requests.get(
            "https://rhoapi.rho.co/api/v1/transactions",
            params=params,
            headers={"Authorization": f"Bearer {token}"},
            timeout=30,
        )
        resp.raise_for_status()
        body = resp.json()

        yield from body["transactions"]

        page_token = body["page"]["next_page_token"]
        if page_token is None:
            return
```

Equivalent with `curl`:

```bash
# First page
curl "https://rhoapi.rho.co/api/v1/transactions?page_size=100" \
  -H "Authorization: Bearer $RHO_API_TOKEN"

# Subsequent page - pass the next_page_token from the previous response
curl "https://rhoapi.rho.co/api/v1/transactions?page_size=100&page_token=eyJvIjoxMDAsImQiOiIyMDI2LTA1LTE0In0" \
  -H "Authorization: Bearer $RHO_API_TOKEN"
```

## Common mistakes

- **Modifying filters between pages.** A `page_token` is bound to the query it was issued for. Change `account_id`, `status`, `sort_by`, etc., and you must start over from the first page.
- **Storing cursors long-term.** Cursors are designed for live iteration, not for bookmarks. Their format and lifetime are not part of the API contract and may change without notice.