Pagination
Lists of the Sursa API are paged with a cursor: you ask for up to limit items, and the response tells you how
to get the next page. You never compute offsets, and items do not repeat or go missing between pages when data
changes in the meantime.
| Parameter | Value |
|---|---|
limit | How many items per page, usually 1 to 200. New lists default to 50; the reference shows the range and the default of each list. |
cursor | The cursor from the previous page. Leave it out for the first page. |
A list returns its next cursor in one of two ways, depending on the shape of its response. The page of each endpoint in the API reference shows which one it uses.
Lists with a data envelope
Lists added for the public API, such as the event feed, return an object with the items in data:
curl "https://api.example.com/v1/webhook-events?limit=2" \
-H "Authorization: Bearer $SURSA_API_KEY"{
"data": [
{"id": "evt_8a1f…", "type": "card.published", "created_at": "2026-10-24T10:00:00Z"},
{"id": "evt_8a20…", "type": "card.updated", "created_at": "2026-10-24T10:00:03Z"}
],
"next_cursor": "eyJ2IjoxLCJrIjpbMTA0Ml19",
"has_more": true
}
When has_more is true, request the next page with cursor set to next_cursor and the same filters:
curl "https://api.example.com/v1/webhook-events?limit=2&cursor=eyJ2IjoxLCJrIjpbMTA0Ml19" \
-H "Authorization: Bearer $SURSA_API_KEY"Lists that existed before cursors and return an object keep their own envelope (for example
{"items": […], "total": 120}). They get next_cursor and has_more in the same object; the items stay where they
were.
Lists that return an array
A few lists return a bare JSON array, for example GET /v1/collections.
Their response shape cannot change, so the next page comes in headers instead:
curl -i "https://api.example.com/v1/collections?limit=50" \
-H "Authorization: Bearer $SURSA_API_KEY"HTTP/1.1 200 OK
Link: <https://api.example.com/v1/collections?limit=50&cursor=eyJ2IjoxLCJrIjpbNTBdfQ>; rel="next"
X-Next-Cursor: eyJ2IjoxLCJrIjpbNTBdfQ
[{"id": 1, "name": "support", …}, …]
Linkwithrel="next"is the full address of the next page;X-Next-Cursoris the same cursor on its own.- No
Linkwithrel="next"and noX-Next-Cursormeans this is the last page. - Without
limit, these lists return every item in one response, as they always did. Passlimitto page them; the cursor remembers the page size, so the next request may leavelimitout. GET /v1/cardsis the exception: it always returns pages, 200 cards by default and up to 1000 withlimit, so followX-Next-Cursoreven withoutlimit.
// Every item of a list that returns an array.
async function* listArray(path) {
let next = `https://api.example.com${path}?limit=200`;
while (next) {
const response = await fetch(next, { headers: { Authorization: `Bearer ${process.env.SURSA_API_KEY}` } });
if (!response.ok) throw new Error(`HTTP ${response.status}`);
yield* await response.json();
const cursor = response.headers.get("X-Next-Cursor");
next = cursor ? `https://api.example.com${path}?cursor=${encodeURIComponent(cursor)}` : null;
}
}The event feed
The event feed GET /v1/webhook-events is a log, so it pages a little
differently:
- Events are in ascending order, oldest first.
next_cursoris returned on every page, even whenhas_moreisfalse. Store it, and ask again later with the same cursor to get the events that happened since. The feed is a queue you can read at your own pace.- An event appears in the feed within about a second of the change it describes, and never before an event that was already read.
- The feed holds the events of the whole organization, so reading it needs a key with the
adminrole and thewebhooks:managescope.
Export to a data warehouse shows a complete polling loop.
Rules for cursors
- A cursor is an opaque string of up to 512 characters. Do not parse it or build it yourself: its format can change.
- A cursor belongs to one list and one set of filters. A cursor that is damaged, comes from another list, or is sent
with different filters returns
400with the codeapi.cursor_invalid. - Each list has a fixed sort order that ends with a unique key, so every item appears on exactly one page.
- Lists added with cursors do not return a total count; lists that had a
totalbefore keep it. Count items as you go if you need a total.
Offset pagination
Some lists accepted offset before cursors were introduced, and still do, so that existing integrations keep
working. Prefer cursor in new code. offset and cursor cannot be combined: sending both returns 422 with the
code validation.failed.