Pagination
Cursor-based pagination for stable, consistent list traversal.
List endpoints use cursor pagination. The default ordering is
(created_at desc, id desc), which is stable: results don't shift as new
records are added while you page.
Request
| Parameter | Description |
|---|---|
limit | Page size, 1-100 (default 25) |
cursor | Opaque cursor from the previous response's next_cursor |
Cursors are opaque
Pass next_cursor back exactly as you received it. Do not parse it, construct
one, or persist it across sessions - the encoding is an implementation detail
and differs between endpoints.
A cursor is also tied to the query that produced it. Changing limit, any
filter, or sort_by mid-traversal invalidates it, and you may see skipped or
repeated rows. When the query changes, start again from the first page.
Response envelope
{
"object": "list",
"data": [ /* resources */ ],
"has_more": true,
"next_cursor": "MTcxOTg0..."
}Paging through all results
cursor=""
while :; do
resp=$(curl -s "https://studio.zentag.ai/api/v1/partner/clips?limit=100&cursor=$cursor" \
-H "Authorization: Bearer $ZENTAG_SECRET_KEY")
echo "$resp" | jq -c '.data[]'
more=$(echo "$resp" | jq -r '.has_more')
cursor=$(echo "$resp" | jq -r '.next_cursor // empty')
[ "$more" = "true" ] && [ -n "$cursor" ] || break
doneStop when has_more is false. Do not stop on an empty data array alone, and
do not keep calling with the same cursor - that loops forever.
Sorting
GET /highlights and GET /media accept sort_by. Anything other than the
default latest sorts on a non-unique column, so the cursor additionally
carries the timestamp and id as tiebreakers; rows with equal values still page
correctly.
sort_by | Ordering |
|---|---|
latest | Newest first (default) |
oldest | Oldest first |
rating | Highest rated first |
duration | Longest first |
Endpoints that don't paginate
A few list responses return everything in one page and always report
has_more: false with a null next_cursor, because the collections are
inherently small: GET /storage/buckets, GET /webhook-endpoints,
GET /webhook-endpoints/{id}/deliveries and GET /clips/{id}/jobs. They still
use the standard list envelope, so the loop above works unchanged.
Updated about 2 months ago
