Pagination
Every collection is cursor-paged. There is no offset, deliberately.
The envelope
Collections return data and meta. A single resource returns the object directly, with no envelope.
{
"data": [ { "id": "3f1a8c2e-...", "name": "Acme Cloud Inc" }, { "id": "7b2d4f9a-...", "name": "Globex" } ],
"meta": {
"has_more": true,
"next_cursor": "eyJjIjoiMjAyNi0wNy0zMFQxMjowMDowMFoiLCJpIjoiLi4uIn0",
"feature_status": "available"
},
"currency": "USD"
}Walking the pages
Pass meta.next_cursor back as cursor. When it is null, you have reached the end.
cursor=""
while :; do
page=$(curl -s "$ARANIS/suppliers?limit=100&cursor=$cursor" \
-H "Authorization: Bearer $ARANIS_API_KEY")
echo "$page" | jq -r '.data[].name'
cursor=$(echo "$page" | jq -r '.meta.next_cursor // empty')
[ -z "$cursor" ] && break
doneThe SDK does this for you:
for await (const supplier of aranis.paginate(p => aranis.listSuppliers(p))) {
console.log(supplier.name)
}
// Or, for a set you know is small:
const all = await aranis.collect(p => aranis.listSuppliers(p))Why not offset?
Because your data keeps moving while you page through it. With ?offset=200, a supplier created during your run shifts every later row down by one — so you read one record twice and skip another entirely. That failure is silent: the totals look right.
Cursors encode the sort key of the last row you saw, so the next page resumes from that exact point regardless of what was inserted meanwhile. They also stay fast at depth, where OFFSET gets linearly slower.
Limits
limit defaults to 50 and accepts 1–200. A value above 200 is rejected, not silently clamped — clamping would let you believe you received everything you asked for.
Empty collections
An empty collection is never an error. meta.feature_status says why:
| Value | Meaning |
|---|---|
available | The feature is active. You have no rows. |
not_configured | The feature exists but this workspace has not set it up. meta.message explains. |
coming_soon | Not released yet. |
This is the difference between an integrator filing a bug and reading one sentence. A 404 would say the endpoint does not exist; a 500 would say we broke. Neither is true when you simply have not configured webhooks yet.
Collections that are not cursor-paged
Three endpoints return a bounded set in one response and always report has_more: false:
/assessments/{id}/gaps— one assessment holds at most the full control pool./risk-matrix— ordered by risk, not time, over two tables with no shared key.limitapplies per scope./alerts— merges two sources;limitapplies per source, so a page can hold up to twice it.