Paginate list endpoints
Page DALP list endpoints with JSON:API sort, page, and filter query parameters, read the response links object, and stay inside the 200-item page-size cap.
Overview
Most V2 collection endpoints return an offset-and-limit page with links to neighboring pages. This page covers sorting, filtering, and walking that result. Some endpoints use cursor pagination or streams instead; consult the route schema and other pagination shapes below.
Offset-based responses contain data, meta.total, and navigation links. The following request is illustrative; replace the host and API key with your deployment values.
curl --globoff "https://your-instance.example.com/api/v2/tokens?sort=-createdAt&page[offset]=0&page[limit]=50" \
-H "X-Api-Key: sm_dalp_..."Sort
Send sort with a field name to sort ascending, or a field name prefixed with - to sort descending. DALP accepts exactly one sort field per request; a comma-separated list of fields fails validation instead of sorting by the first field and ignoring the rest. Each list endpoint documents its own sortable fields on its spec-rendered page; an unlisted field also fails validation, naming the fields that are sortable.
Each collection defines its own default sort, and they differ: some order by newest first, others by a name or a display field ascending. Do not assume -createdAt. If order matters to your integration, send sort explicitly rather than relying on the default, and read the generated contract for the endpoint you are calling.
A request naming more than one sort field fails with COLLECTION_SORT_MULTI_FIELD_UNSUPPORTED; this contract has no multi-column client-specified sort. Deterministic ordering comes from appending tiebreaker columns to whatever field you sorted by, not from the sort field alone. A createdAt sort orders by createdAt first and then by a stable secondary column the route itself declares, so two rows sharing an identical createdAt value still resolve to one deterministic order across repeated identical queries. That guarantee is about tie-breaking within one fixed data state; it says nothing about two requests separated by an insert or a delete.
Page window
page[offset] is the zero-based index of the first item to return. page[limit] is the page size. Omit either to get the default: offset 0, limit 50. page[limit] accepts 1 through 200; a caller that needs more than 200 items per page should follow links.next rather than raise the limit, because 200 is the maximum DALP accepts on this parameter.
page[offset] is bounded 0 through 10,000. A request past that bound fails outright with a 400, COLLECTION_PAGE_OFFSET_OUT_OF_RANGE (DALP-9275); an out-of-range page[limit] returns COLLECTION_PAGE_LIMIT_OUT_OF_RANGE (DALP-9274). Neither error clamps the value for you or returns an empty page; fix the request and resend. Treat both as a stop-and-fix condition in an automated job: blindly retrying the identical request will fail identically every time, since nothing about the request changes between attempts.
The offset ceiling is the one hard stop a full walk of a very large collection will actually hit. An automated job walking a collection large enough to exceed roughly 10,200 rows, the 10,000 offset ceiling plus one final 200-row page, hits it in practice. The contract has no separate mechanism, such as a cursor token, to page further once offset itself runs out of room. Narrow the query with filters, or batch by another dimension such as a date range or an owner id, so no single walk ever needs an offset past the ceiling.
A page walk is not a consistent read
DALP takes no snapshot when you start paging. Every request re-runs the query live against current state, meta.total included, so a page and the total it came with can both shift while a caller is still walking. A row can move between offsets, drop off the page it was on, or appear twice across two pages, purely because a write landed between two of your requests. Nothing in the response marks that it happened.
For a display list refreshed on a schedule, live re-query is what you want: later pages reflect the latest writes. A reconciliation job or an export needs the opposite guarantee, accounting for every row exactly once, and live re-query alone cannot give it that. For that case, page inside a filter range that will not admit new rows while you walk, such as a fixed creation-time window. A fixed window reduces drift but does not stop rows from being updated or deleted. Deduplicate by resource id and reconcile against a later read when completeness matters. DALP does not offer a consistent multi-page cursor for these list endpoints; design for a live view across pages, not for isolation.
meta.total inherits the same caveat: it is recomputed on every single request as a live count on the same query, not carried over from the first page. A walk that needs a defensible row count for reconciliation must reconcile the returned resource identifiers. Recording the first total provides context but cannot prove that the walk saw every row.
Filter
filter[<field>] filters on a field the endpoint declares as filterable. The shorthand filter[status]=active applies the field's default operator, usually equality. Name an explicit operator when you need one: filter[status][inArray]=active,paused matches either value.
Filtering supports these operators: eq, ne, gt, gte, lt, and lte cover equality and ordering. iLike and notILike cover case-insensitive substring matching. inArray and notInArray cover membership. between covers an inclusive two-value range. isEmpty and isNotEmpty take no value at all. inArray and notInArray cap at 100 members; a longer list fails with COLLECTION_FILTER_IN_LIST_TOO_LARGE (DALP-9273) rather than being silently truncated. An unknown field fails with COLLECTION_FILTER_FIELD_UNKNOWN, and an operator the field does not support fails with COLLECTION_FILTER_OPERATOR_UNKNOWN, rather than being ignored; both name the fields the endpoint accepts.
When an endpoint enables global search, filter[q]=<term> searches across that endpoint's default text fields.
No flat aliases
sort, page[offset], page[limit], and filter[<field>] are the only collection parameters. The flat keys sortBy, sortDirection, limit, offset, and filters are refused with a 400 BAD_REQUEST that names the key and the parameter to send instead. DALP refuses them rather than ignoring them, because an ignored limit or filters would return the unfiltered first page and look like a correct answer.
Read the response
{
"data": [],
"meta": { "total": 214 },
"links": {
"self": "/api/v2/tokens?sort=-createdAt&page[offset]=0&page[limit]=50",
"first": "/api/v2/tokens?sort=-createdAt&page[offset]=0&page[limit]=50",
"prev": null,
"next": "/api/v2/tokens?sort=-createdAt&page[offset]=50&page[limit]=50",
"last": "/api/v2/tokens?sort=-createdAt&page[offset]=164&page[limit]=50"
}
}Follow links.next and links.prev instead of computing the next offset by hand. Each link already carries the active sort and filters, so following them retains the query parameters from that response. Changing filters starts a different walk.
links.next follows one rule: it is present exactly when page[offset] + page[limit] is less than meta.total, and null on exactly the last page. That makes it the only correct end-of-walk signal. A short page or an empty data array is not a substitute: data.length and links.next come from separate parts of the response, and treating data.length < page[limit] as your end-of-walk check is a bug that a page walk under concurrent writes, see above, will eventually trigger. Stop when links.next is null, never before.
Other pagination shapes in the API
This offset and limit contract covers list endpoints under /api/v2/<resource>. Two other shapes exist elsewhere in the Platform API, and assuming this one everywhere will be wrong in both places.
The request-log and health-snapshot endpoints use a keyset cursor instead of an offset: send the previous response's nextCursor back as cursor, and stop when nextCursor comes back null. Their envelope is items, nextCursor, and totalCount, not data, links, and meta.total.
Event streams, such as the request-log and health-snapshot SSE feeds, carry no pagination contract at all. Connecting starts a live feed from that point forward. It does not replay history and gives no resume cursor, so a dropped connection loses whatever it missed. Use the matching list endpoint for a historical page, and the stream only for events while it stays connected.
Next steps
- Errors covers what an out-of-range page size or an unknown filter field returns.
- Each resource's spec-rendered page in the API reference lists that endpoint's sortable and filterable fields.
Authentication and API keys
Authenticate Platform API requests with an X-Api-Key header, or a Console session on session-or-api-key routes, and store the secret when you create it.
Errors
Read the DALP-#### id, category, and retryable flag on every error response, branch your integration on those fields, and never parse the message text.