Pagination
List endpoints in the v1 API return at most a fixed number of records per response. Walk longer collections through cursor pagination.
How it works
Every list response includes a pagination object:
{
"controls": [
{ "id": "ctrl_01H...", ... },
{ "id": "ctrl_01H...", ... }
],
"pagination": {
"nextCursor": "<cursor>"
}
}To fetch the next page, pass the nextCursor value back as a ?cursor= query parameter:
GET /api/v1/governance/controls?cursor=<cursor>When you have reached the end of the collection, the response omits nextCursor (or returns it as null):
{
"controls": [ ... ],
"pagination": {}
}Page size
Each list endpoint has a default and a maximum page size, documented on the per-endpoint reference page. Typical defaults are 50 records per page; typical maximums are 200.
Override the page size with ?limit=N:
GET /api/v1/governance/controls?limit=100&cursor=...Requesting limit above the documented maximum is clamped to the maximum without error.
Cursor opaqueness
The cursor value is an opaque base64-encoded token generated by the server. Do not parse it, decode it, or construct it yourself. The internal format may change between releases.
A cursor is valid for at least 24 hours from the response that produced it. After that the server may reject it with 400 / error: "invalid_cursor"; handle that by restarting the walk from the beginning.
Filters and ordering
Filters (?status=active, ?createdAfter=2026-01-01) apply to the entire walk, not just the current page. Apply the filters once, on every page request:
GET /api/v1/governance/findings?status=open&createdAfter=2026-01-01
GET /api/v1/governance/findings?status=open&createdAfter=2026-01-01&cursor=...
GET /api/v1/governance/findings?status=open&createdAfter=2026-01-01&cursor=...Default ordering on every list endpoint is most-recent-first by creation time. Per-endpoint reference pages document any alternative ordering parameters.
Do not change filter values mid-walk. Cursors capture the filter set in effect when they were issued; passing a cursor with a different filter set returns invalid_cursor.
Walking a full collection
A typical full walk:
cursor = None
while True:
response = api.get("/api/v1/governance/findings", params={"cursor": cursor})
for finding in response["findings"]:
process(finding)
cursor = response["pagination"].get("nextCursor")
if not cursor:
breakFor very large collections, prefer to filter aggressively (date ranges, status) and walk smaller subsets rather than running one open-ended walk over everything.
Concurrent writes during a walk
If your integration is walking a large collection while the workspace is also writing to it, you may:
- Miss records created after the cursor was issued. They simply weren’t in the snapshot the cursor represents.
- See duplicates if a record is updated mid-walk and reordering causes it to appear on a later page.
Strategies if this matters for your use case:
- Use webhooks for new records and the cursor walk for backfill only.
- Filter by
updatedAfterrather than walking the whole collection. - Re-walk after a quiet window to catch anything missed.
What’s not supported
- Offset / page-number pagination (
?page=2). v1 is cursor-only. - Backwards walking. Cursors move forward only; cache pages your application needs to revisit.
- Total-count headers. Counting a full collection requires walking it; the server doesn’t volunteer the count.
- Random access. No “jump to record N” parameter.
Next
- Webhooks - push events instead of polling list endpoints.
- Governance reference - per-resource endpoints with their pagination details.