Sume API pagination: cursor, starting_after, and page limits
How each Sume list endpoint pages: cursor and has_more on Formats and runs, starting_after on /v1/jobs, and limit-only lists that have no cursor.

Sume API lists page in three ways: Formats, Actions, their run lists, and Agent Completion runs return next_cursor and has_more, and you send the cursor back as cursor, while GET /v1/jobs returns data.next_cursor, which goes back as starting_after. Avatar, event, usage, and search lists take a limit but no cursor, and return a single page.
The limits below come from the live OpenAPI reference and the docs pages for each surface, such as Runs and results and Scheduled runs, read on 2026-09-27. Paging through jobs, including crash recovery, has its own walkthrough in List jobs API.
How does each list endpoint page?
Each row links the post that covers that list. limit is the page size you ask for.
| Endpoint | limit | Next page | Last page |
|---|---|---|---|
Formats: GET /v1/formats | 1–100, default 50 | cursor = next_cursor | has_more: false |
Format runs: GET /v1/formats/{handle}/{slug}/runs | 1–100, default 20 | cursor = next_cursor | has_more: false |
Actions: GET /v1/actions | 1–100, default 50 | cursor = next_cursor | has_more: false |
Action runs: GET /v1/actions/{action_id}/runs | 1–100, default 50 | cursor = next_cursor | has_more: false |
Agent Completion runs: GET /v1/agent-runs | 1–100 | cursor = next_cursor | has_more: false |
Jobs: GET /v1/jobs | 1–100 | starting_after = data.next_cursor | data.next_cursor is absent |
Job events: GET /v1/jobs/{id}/events | 1–100 | No cursor | One page |
Usage ledger: GET /v1/usage | 1–100 newest rows | No cursor | One page |
Avatars and avatar videos: GET /v1/avatars, GET /v1/avatar-videos | 1–100 | No cursor | One page |
Background music: GET /v1/bgm/catalog | 1–200 | No cursor parameter | One page |
Avatar catalog search: POST /v1/avatar-catalog/search | 1–100, default 20 | No cursor | One ranked page |
Trending video search: POST /v1/trending-videos/search | 1–50, default 10 in production | No cursor | One ranked page |
How do I page with cursor and has_more?
Send the first request without cursor. While has_more is true, send next_cursor back as cursor; on the last page next_cursor is null. The same loop works for Formats, Actions, and all three run lists:
- Treat the cursor as opaque: pass it back verbatim and never parse or build one. On Format and Action run lists, a cursor Sume did not mint is
400 invalid_request. - Format run pages are keyset over
(created_at, id), newest first, so runs created while you page do not shift rows. - A Format that has never been run over the API returns an empty list, not a
404.
URL="https://api.sume.com/v1/formats/acme/product-promo/runs?limit=100"
NEXT=""
while :; do
PAGE=$(curl -sS "$URL$NEXT" -H "Authorization: Bearer $SUME_API_KEY")
echo "$PAGE" | jq -r '.data[] | [.id, .status] | @tsv'
[ "$(echo "$PAGE" | jq -r '.has_more')" = "true" ] || break
NEXT="&cursor=$(echo "$PAGE" | jq -r '.next_cursor | @uri')"
doneWhy does GET /v1/jobs use starting_after instead?
Jobs page differently: rows arrive in data.jobs, newest first, data.next_cursor is present only while more jobs remain, and it goes back as starting_after. There is no has_more, and cursor is not a jobs parameter, so sending it is a 400 unknown_parameter. List jobs API walks through the loop and how to recover lost job ids.
Which lists return a single page?
These take a limit, and some take filters, but their responses carry no cursor:
GET /v1/avatars,GET /v1/avatar-1.0/avatars, andGET /v1/avatar-videosfilter bystatus, wherereadyis an alias for completed jobs.GET /v1/jobs/{id}/eventsreturns one job's timeline.GET /v1/usagelists the newest ledger rows. Withrun_id,thread_id, orjob_id, itssummaryfolds every row of that scope, andlimitonly caps the rows listed.GET /v1/formats/{handle}/{slug}/contents?recursive=1returns every file of a Format package, with its body, in one call.
Which lists do not exist?
Callers look for these and do not find them:
GET /v1/format-runs: there is no cross-Format run list. List per Format, or keep your own index keyed by the run id you stored at create.- A list of bulk queues: there is no public list-queues endpoint, so store each queue id from its create response.
- One list of your personal and team Formats together: visibility follows the key, so a personal key lists your personal Formats, a team key lists that workspace's, and neither lists the other's.
Does paging count against my rate limit?
Yes. Every GET page is a read, and with an API key, reads get forty times the plan's write number in their own bucket, so a paging loop cannot starve your creates. The two POST searches spend the write budget instead. The per-plan budgets are in Sume API errors and rate limits.
Sources
Related posts
More in Developers
- Sume API status values: jobs, runs, queues, and webhooks
Sume API status values in one place: jobs, /v1/videos, Format and Agent runs, bulk queues, webhook deliveries, usage rows, grants, and balance.
- Sume job types and concurrency: which calls take a slot
Each Sume endpoint's job type and slot use: every generation job, trims and Timeline included, takes a concurrency slot; frames and inspect don't.
- Sume API media URL rules: which URLs each endpoint accepts
Sume generation endpoints fetch public HTTPS media URLs. Trim, filter, frames, inspect, and Timeline take only your workspace's media.sume.com URLs.
- Webhook URL rejected as invalid? Sume's webhook URL rules
Sume answers 400 invalid_request when a webhook URL is not public HTTPS. The rules for scheme, host, port, and credentials, and the check at delivery.
Written by Sume