since filter for incremental sync. Use pageSize and pageToken to page through results, sortOrder to pick the direction, and since to fetch only what changed after a timestamp.
Cursor pagination
Pass apagination object in the request:
Quick uses:
pageSize:"pageSize": 100for a bulk backfill; a smaller value for interactive browsing.sortOrder:"sortOrder": "SORT_ORDER_DESCENDING"to see the newest reports first. Keep the default ascending for sync.pageToken: pass the previous response’snextPageTokento get the next page.since(a filter, not part ofpagination):"filters": {"since": "2026-08-01T00:00:00Z"}to fetch only what changed after that time.
Repeat until
nextPageToken is empty. Pass it back as pageToken each time:
Ordering
A composite change cursor sets the order. You choose the direction:- Reports use the order
(updatedAt, reportId). Ascending (oldest change first) is the default, and incremental pulls need it. For newest-first browsing, passSORT_ORDER_DESCENDING. - Campaigns use the order
(lastActivityAt, campaignId), with the same directions.
Incremental pull: keep a mirror in sync
A report re-surfaces when its verdict, indicators, or campaign links change; a campaign re-surfaces when it changes. The cursor moves only on a real change, never on a no-op refresh. To pull only what changed since your last sync:1
Keep the default ascending order
Ascending order goes from the oldest change to the newest. This gives you a durable watermark.
2
Track your high-water mark
Store the highest
updatedAt (for reports) or lastActivityAt (for campaigns) you have ingested.3
Pass it as the since filter on the next run
since is a strict greater-than. You never re-fetch a row at that exact timestamp.reportId and upsert, so a repeat just overwrites. A withdrawal is a change too: when withdrawn: true arrives, remove that report on your side.
See Recipes for a complete mirror-and-sync script.
