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

# Pagination

> Iterate long 0xArchive histories with cursor pagination: limit, meta.has_more, meta.next_cursor, opaque cursors, and resumable backfills.

Pull a long history one page at a time: while `meta.has_more` is `true`, send `meta.next_cursor` back as `cursor` with every other parameter unchanged. Every cursor-paged route follows this rule, and `meta.next_cursor` is present exactly when `meta.has_more` is `true`. Page sizes, defaults, and response grains differ by route, so check the data-type page for the route you are calling.

## How a page is bounded

Two things cap a single response:

* `limit` sets the maximum rows returned. The default and ceiling vary by route family. The ceiling for each venue and data type is `page_limit` in [capabilities](/capabilities); the data-type pages and [OpenAPI](/openapi) list the defaults.
* The time window (`start`/`end`) bounds the range; see [Parameters](/rest-api/parameters) for the accepted formats. A narrow window plus a sensible `limit` keeps each page fast.

## Follow the cursor

This example pages through the hour of BTC trades that ended one hour ago, a window inside every plan's history, including Free's most recent 30 days. It fixes `start` and `end` first, so every page asks for the same window:

```bash theme={"theme":"github-dark"}
END=$(( ($(date +%s) - 3600) * 1000 ))   # one hour ago, in Unix milliseconds
START=$(( END - 3600 * 1000 ))           # one hour earlier
URL="https://api.0xarchive.io/v1/hyperliquid/trades/BTC?start=$START&end=$END&limit=1000"

# First page
curl -sS "$URL" -H "X-API-Key: $OXARCHIVE_API_KEY" > page-1.json

# Next page: send meta.next_cursor back as cursor, with every other parameter unchanged
CURSOR=$(jq -r '.meta.next_cursor' page-1.json)
curl -sS -G "$URL" --data-urlencode "cursor=$CURSOR" \
  -H "X-API-Key: $OXARCHIVE_API_KEY" > page-2.json
```

A page with more to come carries the cursor in `meta`:

```json theme={"theme":"github-dark"}
{
  "meta": {
    "count": 1000,
    "has_more": true,
    "next_cursor": "1790607317295_494699715524397",
    "symbol": "BTC",
    "venue": "hyperliquid",
    "request_id": "ea17ef99-bca1-4d06-9461-0e920e66a2c8"
  }
}
```

When `meta.has_more` is `false`, you have reached the end of the window, and `meta.next_cursor` is absent. The same loop in Python:

```python theme={"theme":"github-dark"}
import os
import time
import requests

end = int(time.time() * 1000) - 3_600_000   # one hour ago, in Unix milliseconds
start = end - 3_600_000                     # one hour earlier

url = "https://api.0xarchive.io/v1/hyperliquid/trades/BTC"
headers = {"X-API-Key": os.environ["OXARCHIVE_API_KEY"], "0xArchive-Version": "2026-10-01"}
params = {"start": start, "end": end, "limit": 1000}
rows = []
while True:
    page = requests.get(url, headers=headers, params=params, timeout=30).json()
    rows.extend(page["data"])
    if not page["meta"]["has_more"]:
        break
    params["cursor"] = page["meta"]["next_cursor"]
```

## Stop on `has_more`, not on a short page

A page can hold fewer rows than `limit` and still have more to come. CVD below 1h is one case: each page covers a bounded span of buckets, and buckets without trades are omitted, so a page can be short with more to come. The opposite also happens: when the previous page was exactly full, the last page can be empty, with `has_more` set to `false`. Keep requesting until `meta.has_more` is `false`, and keep `start`, `end`, `interval`, and every filter unchanged between pages.

## Rebuild running totals across pages

A value computed over one response restarts on the next page. On CVD, `cumulative_delta` is a running total from the first bucket of each response, so pages joined as they arrive reset at every boundary. Rebuild the total from `delta` after joining pages. A CVD response that is one page of several says so in `meta.notice`.

## Treat the cursor as opaque

The cursor is an opaque string, and its encoding differs by route. Do not parse, construct, or compare cursors. Store the exact value you received and pass it back unchanged, so your iteration stays correct if the encoding changes. A cursor that was altered or issued for a different query can return `400` with `error_code` `invalid_cursor`; on some routes it is read as no cursor and the first page is returned, so a changed cursor can silently restart the walk.

## Resume long backfills

Split a long history into windows you can retry independently, then paginate within each. For each window, persist the last `next_cursor` together with the window and filters you are walking. A run that stores its cursor can pause and resume without duplicating rows or pulling the whole range again. Keep `meta.request_id` per page in your logs so a failed page is traceable.

## Next step

Add retries, request-ID logging, and concurrency bounds with [Build a reliable client](/rest-api/reliability).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.