- Pass
cursor=<nextCursor>to get the next page. limitis 1–500, default 100.- Ordering is ascending on the sort key, so a feed reads like a log: older first, newest last.
nextCursoris returned even whenhasMoreisfalse. Store it, and the next call returns only rows added or updated since — that is the whole incremental-sync mechanism. Cursors never expire.
Cursor kinds
A cursor is only valid for the endpoint that issued it; sending one elsewhere returns
400 INVALID_CURSOR. Opaque cursors are base64url-encoded JSON — you may inspect them, but do not build them by hand.
Rows are never skipped or repeated
Keyset pagination on a stable sort key means a row inserted while you are paging cannot cause the page boundary to shift under you. What can happen is a row being returned again because it changed:- A call whose summary is regenerated gets a new
updatedAtand moves forward in/calls. - A Decision Room that sees new activity moves forward in
/decision-rooms.
id, never insert blindly, and you are safe on both counts.
The three-step recipe
1
Backfill
Call each feed from an empty cursor with
limit=500 until hasMore is false. Store the final nextCursor per feed. For Decision Rooms, follow each room with GET /decision-rooms/{id}/metrics if you want the roll-ups.2
Incremental
On a schedule (every few minutes is plenty), call each feed with its stored cursor. Whatever comes back is new or changed. Store the new
nextCursor.3
Webhooks for latency
Register an endpoint so events arrive in seconds instead of at the next tick. Apply the payload, dedupe on the envelope
id, and — optionally — trigger an early incremental pass. A dropped webhook then costs latency, never data.