page parameter and no
offset.
Reading a full collection
Pass the previous response’snext_cursor back as after, and stop when has_more is
false.
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:Do not construct, decode or modify a cursor
Do not construct, decode or modify a cursor
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 only works on the endpoint that issued it
A cursor only works on the endpoint that issued it
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.Do not persist a cursor long term
Do not persist a cursor long term
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.
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 carryhas_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.