# Pagination

> How cursors, limits and ordering work across the data API.

Page: https://docs.aggre.org/concepts/pagination · Section: Concepts · All pages: https://docs.aggre.org/llms.txt

## Cursor paging {#cursors}

List routes that can return more rows than one page give you `next_cursor` in the envelope. Pass it back unchanged as `cursor` to get the next page; it is `null` on the last page.

```bash
# first page
curl -s "https://api.aggre.org/v1/finance/macro/series?limit=100" -H "Authorization: Bearer $AGGRE_API_KEY"
# next page: the previous answer's next_cursor, as it came
curl -s "https://api.aggre.org/v1/finance/macro/series?limit=100&cursor=$NEXT" -H "Authorization: Bearer $AGGRE_API_KEY"
```

Rules:

- A cursor is **opaque**. Do not parse, build or edit it. A cursor taken from another route, or a garbled one, is `400 invalid_request`.
- Keep the same filters while paging. (Statute listings tolerate a filter change by continuing after the last id, but do not rely on that elsewhere.)
- `limit` is the page size. Its range is on each route (commonly 1 to 1000, default 100; statutes 1 to 200, default 50; statute provisions 1 to 1000, default 500).
- A page never splits one period's versions on the macro routes (`versions=all`).

Paging loop:

```python
import os, requests

def pages(url, params):
    cursor = None
    while True:
        q = dict(params, **({"cursor": cursor} if cursor else {}))
        r = requests.get(url, params=q, headers={"Authorization": f"Bearer {os.environ['AGGRE_API_KEY']}"}, timeout=30)
        r.raise_for_status()
        body = r.json()
        yield from body["data"]
        cursor = body.get("next_cursor")
        if not cursor:
            return
```

## Time-window paging {#windows}

Time series (candles, funding rates, open interest) do not use cursors: `next_cursor` is always `null`. Read further with `from` and `to` (Unix ms, `from` inclusive, `to` exclusive) and `order`:

- `order=desc` (the default for candles) returns the newest rows first; take the oldest `ts` you got and use it as the next `to`.
- `order=asc` returns the oldest first; take the newest `ts` plus one as the next `from`.

## Order {#order}

Each route states its default order and which ones it allows (`order=asc|desc`). Calendars read forward (`asc`), series of bars read backward (`desc`). Name-list searches, statute lists and similar are ordered by an identifier so that paging is stable.

## Change feeds {#change-feeds}

Where a route has `updated_since`, rows are ordered by `(updated_at, key)` ascending and `updated_at` moves only when the content changed, so `updated_since` plus the cursor is a change feed: store the newest `updated_at` you processed and ask for rows since then.
