Pagination
Every list endpoint returns the same envelope and uses opaque, cursor-based paging. You request a page size with limit, and follow next_cursor until the API tells you there's nothing more. Cursors are encrypted tokens tied to the exact query that produced them.
The list envelope
- Name
object- Type
- string
- Description
Always
list.
- Name
data- Type
- array
- Description
The page of resources, newest-first unless the endpoint documents otherwise.
- Name
has_more- Type
- boolean
- Description
Whether more pages exist. Stop walking when this is
false.
- Name
next_cursor- Type
- string
- Description
Pass this back as
cursorto fetch the next page.nullon the last page.
- Name
url- Type
- string
- Description
The path this list was fetched from.
Response
{
"object": "list",
"data": [
{ "object": "template", "id": "template_00a1b2c3d4e5f607" }
// ...
],
"has_more": true,
"next_cursor": "hf8a2c1b9d0e...",
"url": "/v1/templates"
}
Request parameters
- Name
limit- Type
- integer
- Description
Page size,
1–100(default10). A value outside that range is rejected with400parameter_invalid(onlimit) rather than silently clamped. A few endpoints fix their own size — see below.
- Name
cursor- Type
- string
- Description
The previous response's
next_cursor, passed back verbatim.
Walking a collection
Fetch the first page, then keep passing next_cursor back as cursor until has_more is false.
# First page
curl -s -G https://api.synexcloud.com/v1/templates \
-H "Authorization: Bearer $SYNEX_API_KEY" \
-d limit=50
# Next page
curl -s -G https://api.synexcloud.com/v1/templates \
-H "Authorization: Bearer $SYNEX_API_KEY" \
-d limit=50 \
-d cursor=hf8a2c1b9d0e...
Cursor rules
Cursors are opaque, encrypted tokens. Treat them as a black box — you only
ever read next_cursor and pass it straight back as cursor.
- Never construct or modify a cursor. A malformed or tampered cursor returns
400invalid_cursor. - Don't persist cursors long-term. To resume later, restart from the first page.
- Cursors are tied to their query. Every filter joins the cursor's scope, so a cursor minted under one set of filters and replayed under another is
400invalid_cursor. Keep the filters identical for the whole walk and vary onlylimit.
Creation-time filters
Four list endpoints accept a creation window:
| Endpoint | Filter runs as |
|---|---|
GET /v1/templates | key condition |
GET /v1/ontologies | key condition |
GET /v1/imports | SQL WHERE |
GET /v1/events | bounds on the evt_ id |
- Name
created[gte]- Type
- string
- Description
Only objects created at or after this time.
- Name
created[lte]- Type
- string
- Description
Only objects created at or before this time.
Both bounds are inclusive of the whole second they name and accept unix seconds or an ISO string. An inverted range — a created[gte] later than created[lte] — is rejected with 400 parameter_invalid rather than returning an empty page.
None of them scans, so a narrow window is exact and cheap. Only templates can return a short page, and only because its category filter is applied after the page is read.
The bounds join the cursor's scope like every other filter: a cursor minted under one window is invalid_cursor under a different one, or under none.
Endpoints that page differently
A few lists differ from the standard shape — in page size, order, or where a filter runs. Assume the standard shape and these will surprise you:
| Endpoint | Difference |
|---|---|
GET /v1/ontologies/:ontology/passports | search= is applied after a page is read, so a page can come back short or empty while has_more is true |
GET /v1/templates | The category filter is applied after the page is read, so short pages are normal |
GET /v1/imports/:id/rows | No limit; reads a fixed 100 rows per page. The status filter is applied after the page is read, so a filtered page can come back short |
GET /v1/data_request_sets/:id/children | Pages in row order rather than newest first |
GET /v1/data_request_sets/:id/fulfil/sources | Unpaginated — a fixed list in one response. has_more is always false |
GET /v1/ontologies/:id/versions | Unpaginated — every version in one response. has_more is always false |
GET /v1/team/members | Unpaginated — every member in one response. has_more is always false |
POST /v1/lab/query | Not a list endpoint: pages by a keyset cursor carried inside the AST (ast.cursor, page size ast.limit), and a bad one is 422 bad_cursor |
Where a filter is applied after the page is read, page until has_more is false and never terminate on a short page. An empty page is not an answer about existence: for search= in particular, no rows on a page means nothing matched inside the window that page scanned, not that no such passport exists.