Pagination and cursors
01In a nutshell #sec_4400178a
All API lists are traversed by cursor, never by page number — and the cursor is opaque: its form is not a contract, its usage is.
02The response envelope #sec_95dbbbe4
Any call point that returns a list returns the same envelope: the data on one side, the position on the other.
{
"data": [ ... ],
"page": {
"limit": 100,
"nextCursor": "YzE6NDIx"
}
}
data carries the elements. page.limit recalls the size actually applied — which is not always the one requested, see below. page.nextCursor carries the position to be resumed, or null when there is nothing more after.
03Traversing #sec_6f1e95f9
The first call does not carry a cursor. Each of the following ones resumes the previous nextCursor.
GET /api/v1/campaigns?limit=100
GET /api/v1/campaigns?limit=100&cursor=YzE6NDIx
GET /api/v1/campaigns?limit=100&cursor=YzE6Mzgy
The loop stops on nextCursor: null, not on an incomplete page. A page may return fewer elements than the requested limit without being the last one; stopping on this signal would miss the continuation, silently.
04The limit #sec_f8bab3ec
One hundred elements by default, five hundred at most. An out-of-bounds value is not rejected: it is brought within bounds, and page.limit then says what was actually applied.
| Requested | Applied |
|---|---|
| absent | 100 |
| 0 or negative | 1 |
| 250 | 250 |
| 5000 | 500 |
This is why page.limit is in the response: without it, a client that requests 5000 would believe it has received everything.
05The cursor is opaque #sec_1c8cde92
The cursor is a string. What it encodes is an implementation detail that can change without notice, and decoding it to make another one is the surest way to break its integration in the next version.
Two rules suffice: take the one that the previous response gave, as is; never build one.
A malformed or tampered cursor is not an error: it is treated as an absence of cursor, and the traversal starts over from the beginning. An integration that would fabricate its cursors would therefore go round in circles on the first page without anything signaling it.
06The traversal order #sec_3157b042
The order is stable and decreasing on the identifier: the most recent elements first. This is what makes the traversal safe — an element added while you are paging appears before your position, so it does not shift what remains to be read and you will not see it twice.
The counterpart is frankly said: a long traversal does not see what arrived after its first call. To catch up with the recent, restart a traversal from the beginning rather than continuing the old one.
07Resume later #sec_98015cd3
A cursor remains usable beyond the life of the token that produced it: renewing the token does not require restarting the traversal. Keep the position, not the session.
08To remember #sec_35ad77bf
A single envelope for all lists. A limit between 1 and 500, brought within the bounds and recalled in the response. A cursor that is resumed and never fabricated. And only one stop condition: nextCursor set to null.