# API pagination

> How the Ranqo API pages through long lists with a cursor, the page sizes each list takes, and what to do when a cursor is refused.

Source: https://ranqo.ai/docs/api/pagination

A list answers one page at a time. Every list has the same envelope:

```json
{
  "data": [],
  "has_more": true,
  "next_cursor": "eyJ2IjoxLCJvcCI6Imxpc3RSdW5zIn0..."
}
```

- `data` holds this page's items.
- `has_more` says whether another page follows.
- `next_cursor` is what you send to get it; it is `null` on the last page.

## Reading every page

Send `next_cursor` back as `cursor`, with the same filters, until `has_more` is `false`:

```python title="Python"
import os, requests

url = "https://api.ranqo.ai/v1/brands/BRAND_ID/prompts"
headers = {"Authorization": f"Bearer {os.environ['RANQO_API_KEY']}"}
params = {"limit": 100, "status": "tracked"}

prompts = []
while True:
    page = requests.get(url, headers=headers, params=params).json()
    prompts.extend(page["data"])
    if not page["has_more"]:
        break
    params["cursor"] = page["next_cursor"]
```

Treat a cursor as opaque: its contents can change without notice.

## Page sizes

`limit` sets how many items a page holds. Most lists take up to 100 and return 50 when `limit` is left out. A prompt's answers carry their full text, so they come 20 to a page by default and at most 50.

The competitor ranking is not paged: it returns the top `limit` rivals, 25 by default and at most 100, or every rival you watch with `watched=true`.

## Order

| List | Order |
| --- | --- |
| Brands | Oldest first |
| Prompts | Oldest first |
| Runs | Newest first |
| A prompt's answers | Newest first |
| Page audits | Newest first |
| Recommendations | Newest first |
| Outreach targets | By opportunity score, as the Outreach board ranks them |

Each page is read when you ask for it. In a newest-first list, an item added while you page lands before the pages you have read, so it appears the next time you start from the first page; in an oldest-first list it appears on a later page.

## When a cursor is refused

A cursor belongs to the request that issued it. It is refused with `invalid_cursor` when:

- it is sent with different filters, or to another endpoint;
- it pages a prompt's answers over a preset period and the day has turned in `tz` since, so the window has moved;
- it pages a prompt's answers over a window that held no run and was reading the latest one before it, and a run has completed in the window since;
- the Outreach board was refreshed and its targets re-scored since it was issued.

In each case, start again from the first page, without a cursor. Refusing is deliberate: continuing would skip or repeat items without telling you.
