Every collection on this API is paged by cursor. There is no page parameter and no offset.

Reading a full collection

Pass the previous response’s next_cursor back as after, and stop when has_more is false.
Loop on has_more, never on whether data is empty and never on a fixed page count. next_cursor is absent on the last page.

Limit

A limit above the maximum is refused rather than silently clamped, so you find out at the first call rather than wondering why a bulk read is slower than you expected. Use limit=100 for bulk reads. It is five times the data for the same single request against your rate limit budget.

Cursors are opaque, signed and route bound

A cursor is a signed token. Three rules follow, and all three are enforced:
It is base64 and will decode into something that looks editable. Changing any part of it invalidates the signature and the request is refused. Pass back exactly the string you were given.
A cursor from /products is refused on /orders. The binding is to the route, so you cannot accidentally page one collection using another’s position and receive a plausible but wrong window of data.
A cursor is a position in a result set, meaningful while you are walking it. Store the record IDs you fetched, not the cursor you fetched them with.
An invalid, foreign or tampered cursor returns 400:

Why cursors instead of page numbers

Offset paging is wrong on a live store and the failure is silent. If a record is inserted while you are on page 3, offset paging shifts every subsequent row down by one, so the record that was at the top of page 4 moves to the bottom of page 3 and you never see it. Deletions do the reverse and show you a record twice. A nightly sync built on ?page= quietly drops orders and nobody notices until the totals disagree. A cursor encodes a position in a stable ordering rather than a count of skipped rows, so inserts and deletes elsewhere in the collection cannot make you miss a record. Offset paging also gets slower the deeper you go, because the database must walk and discard every skipped row. Cursor paging costs the same on page 500 as on page 1.

Filters stay stable across a walk

Keep every other query parameter identical as you page. The cursor encodes its position relative to the filter and ordering you started with, so changing a filter mid-walk is refused rather than silently returning a mixed result set.

No total count

Responses carry has_more, not a total. Counting the full collection would mean a scan on every page request, and it can disagree with the page just returned if a row lands mid-scroll. has_more is answered by fetching one extra row, which is exact and free. If you need a count, page through and count what you receive.