Pagination
Cursor-based pagination with cursor, limit, and has_more, plus the top-level-array shape used for small lists.
Pagination
List endpoints come in two flavors:
- Small, bounded resources return their entire result set as a top-level
dataarray with no pagination metadata. Examples:GET /v2/datasets(metadata index) andGET /v2/datasets/{name}(individual dataset rows). - Larger resources paginate with opaque cursors. The caller passes
cursorandlimit; the response returns an innerdataarray, plushas_moreandnext_cursor.
Either way, there are no page numbers and no offsets.
Why cursors, not offsets
Page-number pagination ("?page=2&per_page=25") computes slices from a running count. Inserts and deletes during the walk shift every later page — you silently see duplicates or miss rows.
Cursors point at a specific position in the index. Inserts and deletes elsewhere don't move the cursor. The walk always completes with the exact rows that existed when the cursor was minted, regardless of concurrent mutations.
Request parameters
| Parameter | Default | Max | Meaning |
|---|---|---|---|
cursor | null | — | Opaque base64 string from the previous response's next_cursor. Omit on the first page. |
limit | 25 | 100 | Number of items to return. The server may return fewer at the tail of the set. |
A few endpoints (like product catalogs) default to limit=250 to return the full set in one response. They're marked x-pagination-eager-default: true in the OpenAPI spec. This is always documented per endpoint — never silent.
Response shape
There are two list-response shapes on the public surface. Which one a
given endpoint uses is documented in the API reference;
the SDKs hide the difference behind a uniform iterator.
Top-level array (no pagination metadata)
Small, bounded resources whose entire result set fits in one response —
GET /v2/datasets (the metadata index) and GET /v2/datasets/{name}
(individual dataset rows) — return data as a top-level array. There
is no has_more, no next_id: the response contains the full
set. The carrier logo endpoint GET /v1/logo/{name} returns image
bytes directly, not JSON, so it isn't strictly a list endpoint.
{
"object": "list",
"livemode": true,
"request_id": "req_v2ds_p7q8r9s0t1",
"data": [
{ "name": "medications", "description": "...", "item_count": 4200 },
{ "name": "conditions", "description": "...", "item_count": 850 }
]
}
Paginated page (cursor + has_more)
For large resources (where row count exceeds the per-page max), the response is a paginated page with cursor metadata. The outer data field contains its own data array (items), has_more, and next_cursor. No public v2 endpoint uses this shape today; the contract is published so SDK iterators are forward-compatible when such endpoints arrive.
{
"object": "list",
"livemode": true,
"request_id": "req_01HZK2N5GQR9T8X4B6FJW3Y1AS",
"data": {
"data": [
{ "name": "diabetes", "category": "metabolic", "aliases": ["DM"] },
{ "name": "hypertension", "category": "cardiovascular", "aliases": ["HBP"] }
],
"has_more": true,
"next_cursor": "Y3Vyc29yX29uZV80NzI="
}
}
The doubled data is intentional. The outer data holds the standard response envelope. The inner data is the page array. By convention, every paginated list endpoint names its array data — so SDK iterators can walk any list without knowing the resource type.
- outer
data— the response envelope payload. - inner
data— the resource page (the array of items). has_more—trueif at least one more row exists pastnext_cursor.falseat the tail.next_cursor— opaque base64. Pass it ascursoron the next request.null(or omitted) iffhas_moreisfalse.
Two invariants:
has_more === falseimpliesnext_cursoris null/absent.data.lengthmay be less thanlimiteven whenhas_more === true(if the server's index shard returned a short page). Always trusthas_more, not array length, to decide whether to continue.
The cursor is opaque
Treat cursors as opaque string tokens. Never decode, construct, or inspect them. The encoding scheme is internal and may rotate without notice.
Cursors are endpoint-bound: a cursor from one list is not valid on another. Cross-endpoint use returns code: validation_error with param: "/cursor".
Walking a list
The example below walks a hypothetical paginated endpoint
(GET /v1/example/widgets) so the loop shape is concrete; the live v2
surface returns the top-level-array shape and needs no loop.
# curl — manual cursor walk against a paginated endpoint
NEXT=""
while :; do
if [ -n "$NEXT" ]; then
URL="https://zyins.isaapi.com/v1/example/widgets?limit=100&cursor=$NEXT"
else
URL="https://zyins.isaapi.com/v1/example/widgets?limit=100"
fi
RES=$(curl -s "$URL" -H "Authorization: Bearer $ISA_TOKEN")
echo "$RES" | jq -r '.data.data[].id'
HAS_MORE=$(echo "$RES" | jq -r '.data.has_more')
[[ "$HAS_MORE" != "true" ]] && break
NEXT=$(echo "$RES" | jq -r '.data.next_cursor')
done
For the datasets surface (GET /v3/datasets), no loop is needed — the
endpoint returns the entire requested set in one response, so read
data directly:
curl -s 'https://zyins.isaapi.com/v3/datasets?include=conditions' \
-H "Authorization: Bearer $ISA_TOKEN" \
| jq -r '.data.datasets.conditions.items[].name'
What you will NOT find on this API
?page=2— rejected withvalidation_error.?offset=50— rejected withvalidation_error.?starting_after=case_01HZK2P...— use?cursor=...instead, passing the previous response'snext_cursor.- A total count. The server does not compute
totalon list responses. Counting rows is O(N); pagination is O(1) per page. If you need a total, use a dedicated count endpoint (where available) or accept the answer is "unknown until you walk the list."
See also
- Errors —
validation_errorshape. - API reference — every list endpoint declares its
cursor+limitparameters by$refto the shared component.
Updated about 8 hours ago