400 or a silent empty page.
Read this table before writing a paging loop.
The four dialects
Three things to notice:
- Library is camelCase. Feeds is snake_case.
nextCursorandnext_cursorare never interchangeable. Sendingcursorto a feeds operation that wantsnext_cursorback is fine — the request field is calledcursorin both families — but reading the wrong response field silently yieldsundefinedand your loop stops after one page. - Content cursors are numbers, not strings.
POST /v1/articles/readreturnsnextCursor: 47. That is a paragraph anchor.GET /v1/library/items/{id}/contentreturns a character offset. Both are integers and both are validated as integers — passing a string is a422. cursor: "latest"is a bootstrap, not a page. It sets a checkpoint and returns no events. See below.
The absolute rule
Concretely, all four of these are errors the server detects and rejects:- Constructing a cursor. The string dialects are base64url over an internal structure. That structure changes. Decode it and your client breaks on a deploy.
- Reusing a cursor across a changed predicate.
GET /v1/library/itemsbinds its cursor to theviewyou paged with. Switching fromview=alltoview=historymid-loop returnsinvalid_request: “Choose a valid library page cursor.” - Reusing a feeds cursor with a different
where.POST /v1/feeds/changesfingerprints the predicate into the cursor. Changewhereand you get: “This cursor belongs to a different where predicate. Restore the original where, or restart withcursor:"latest".” - Acknowledging a cursor from another consumer.
POST /v1/feeds/consumers/{id}/ackrejects stale, foreign, and invented cursors outright.
nextCursor you were given. Skipping ahead to an anchor you invented returns a window you did not mean to read.
When you change a filter, start over. Drop the cursor, re-issue the first call, and let the server hand you a fresh one. See Errors for the codes these failures arrive with.
Paging the library
Loop whilenextCursor is non-null. The page size is capped at 100 regardless of what you ask for.
Response
200 OK
"nextCursor": null. That is the only stop condition — never infer the end from an item count, because a page can come back short and still have more behind it.
Paging the feeds
POST /v1/feeds/changes is the polling primitive, and it is where the latest bootstrap lives.
The sequence is: bootstrap once, store the cursor, then poll forever.
Response
The bootstrap call and a drained poll look the same except for the cursor:200 OK — the bootstrap
next_cursor and poll again. The second call returns the events discovered since.
Persist the cursor before you act on the events, or after — but pick one and
be consistent. Persisting after means a crash redelivers the batch; persisting
before means a crash drops it. For at-least-once delivery without writing
your own state, use a managed consumer and let smry hold the checkpoint. See
Retries and idempotency.
Paging article content
POST /v1/articles/read walks a long article in token-budgeted windows. The cursor is the anchor to resume from.
curl
200 OK
nextCursor is null. Each continuation is a separate operation against the account budget, but a continued read hits the cache, so it costs no fresh extraction. See Rate limits.
Checklist
1
Match the dialect to the family
Library reads
nextCursor. Feeds reads next_cursor. Content cursors are
integers.2
Freeze the predicate for the whole loop
Filters,
view, and where must be byte-identical across every page.
GET /v1/library/items only checks view: change status, tag, or
query mid-loop and the cursor is accepted, then pages one result set with
another set’s position. POST /v1/feeds/changes does check the whole where
and rejects a mismatch.3
Stop on null, never on a count
A short page is not the last page.
4
Persist the cursor, not the page number
A crash resumes from the cursor. There is no page number to resume from.
5
Restart clean when the filter changes
Drop the cursor and re-issue the first call. Reuse is an error, not a
shortcut.
Next
Retries and idempotency
Safe retries for saves, mutations, and managed consumer batches.
Errors
The codes a rejected cursor arrives with, and what to do about each.