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; the data-type pages and OpenAPI list the defaults.
- The time window (
start/end) bounds the range; see 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:
A page with more to come carries the cursor in meta:
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:
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. Last modified on October 6, 2026