Skip to Content
Welcome to the Novantra documentation.

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: break

For 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 updatedAfter rather 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

Last updated on