Pagination
Every list endpoint answers with the same envelope:
Code
| Field | |
|---|---|
data | The rows. |
has_more | Whether more rows exist past this page. |
next_cursor | Pass back as cursor to get the next page. null on the last page. |
has_more is not inferred from the page being full. The API requests one row more than
you asked for and reports whether it came back, so a full page with has_more: false is
an accurate answer rather than an estimate.
Page with a cursor
Send the previous response's next_cursor back as cursor:
Stop when has_more is false:
The cursor is opaque. It is base64url and encodes a position in the sort order, but its contents are not part of the contract, so decoding it will break your client. A cursor the API cannot read is ignored rather than rejected, and you get the first page back.
A cursor belongs to the sort it was issued under. Change sort between pages and the
position no longer means anything. Start the pass again instead.
Why cursors rather than offsets
Cost. Several of the tables you are most likely to page (messages, tasks, threads,
usage) are partitioned by time, and a deep offset has to scan and discard rows across
partitions to reach the requested starting point. It gets slower the further you go, and
it degrades fastest for the workspaces with the most data. A keyset cursor costs the same
on page 900 as on page 2.
Cursors are also stable under writes. With offset, a row inserted while you page shifts
everything down by one and you read a row twice; delete a row and you skip one entirely.
A cursor records a position in the data rather than a count of rows already seen.
offset is still accepted for page-number grids that need to jump to page 12, but it is
not the recommended approach for integrations.
Sorting
Use sort=name for ascending and sort=-created for descending. Sortable columns are
declared per resource. A column that is not sortable falls back to the resource's default
order rather than being sorted on, so check the results rather than assuming.
Limits
| Parameter | Default | Maximum |
|---|---|---|
limit | 50 | 1000 |
limit_extended | — | 10000 |
Where an endpoint accepts limit_extended, it is intended for exports and bulk reads
rather than interactive listing.
Asking for more than the maximum returns the clamped page with has_more: true, so the
loop above terminates correctly regardless of what you requested.
Filtering
Collections filter on their own columns, using the column name directly:
Code
Each operation's page in the reference lists the filters it accepts. They are read from the validators that enforce them, so the list matches the endpoint's actual behavior.
Not every list is paginated yet
The envelope is being adopted resource by resource. A collection that has not been converted returns a bare JSON array, so check the shape rather than assuming:
This is a temporary compatibility check. New resources ship with the envelope, and converted resources do not revert.
