List Sume jobs: next_cursor, starting_after, and idempotency_key
Page through GET /v1/jobs with limit, next_cursor and starting_after, then recover a lost wave by joining your own key to each job's idempotency_key.

GET /v1/jobs lists jobs newest first, at most 100 per page. When the response has data.next_cursor, pass it back as starting_after to read the next page; the last page has no next_cursor, and that absence is the loop terminator. To recover a lost wave, join on idempotency_key: each job row carries the key it was created with, and array position is not your submission order.
Everything here is from the listApiJobs operation in the API reference OpenAPI file, plus the idempotency rules on Jobs and results, read 2026-10-02.
Which query parameters does GET /v1/jobs take?
Seven parameters, all optional. An unrecognized parameter is a 400 unknown_parameter rather than a silently unfiltered page, so a typo cannot make a filter disappear.
| Parameter | Values | Use |
|---|---|---|
limit | 1 to 100 | Page size; pages are capped at 100. |
status | queued, processing, completed, failed, canceled | Narrow to one job status. |
type | string | Narrow to one job type. |
starting_after | opaque cursor | Pass back the previous data.next_cursor. |
scope | thread or workspace | An ordinary API key defaults to workspace. |
run_id | string | Narrow to one automation run. |
thread_id | string | Only ever your own thread; never widens a bound credential. |
How do I loop over every page?
Do not build a cursor from the final job; the schema says not to synthesize one. Pass next_cursor back unchanged and stop when it is absent. This generator also builds the key-to-job map used in the next section:
import os
import httpx
def all_jobs(status="processing"):
params = {"limit": 100, "status": status}
headers = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}
with httpx.Client(base_url="https://api.sume.com", headers=headers) as c:
while True:
r = c.get("/v1/jobs", params=params)
r.raise_for_status()
data = r.json()["data"]
yield from data["jobs"]
if "next_cursor" not in data: # absent on the last page
return
params["starting_after"] = data["next_cursor"]
by_key = {j["idempotency_key"]: j["id"] for j in all_jobs() if j["idempotency_key"]}
print(by_key)Why join on idempotency_key instead of position?
Newest-first is not the order you submitted in once a wave retries. The schema describes idempotency_key on each job as the join column for recovery: it is the Idempotency-Key the job was created with, or null when none was sent. If your process died mid-wave, map each of your own business keys to a job id, resubmit only the keys with no job, and reuse the same key on any retry so it returns the original job instead of billing a second one.
Reuse a key only for the same operation and payload. The same key on a different payload returns 409 idempotency_conflict.
What can I not see in this list?
The list is a status view, with limits worth knowing before you build on it.
- Per-job queue position or ETA; Sume exposes queue counts, not ranks.
- Raw provider ids or URLs; public job records carry Sume-owned fields only.
- Jobs from other workspaces: a key sees its own workspace.
- Jobs created without an
Idempotency-Key: theiridempotency_keyis null, so you cannot join on them.
Sources
Related posts
More in Developers
- Sume SumeMediaFile duration_ms: the 10 percent check and null
In a Sume structured output, a duration_ms must agree with the artifact's recorded length within 10 percent, or the projection fails. A null means not measured.
- Sume media tools: which answer 200 and which answer 202 by default
video-inspect defaults to sync, trim, filter, compose and detach to async, and video-frames always returns 202. Defaults, the 30 s wait, and how to poll each.
- next_poll_after_seconds vs recommended_poll_interval_seconds
Which delay a Sume poller should sleep: next_poll_after_seconds, recommended_poll_interval_seconds, or retry-after. Null rules, a fallback, and read budgets.
- Sume queue capacity: max(3, concurrency x 5), and 7 jobs on Free
Sume's default queue capacity is max(3, concurrency_limit x 5). On Free that is 5 queued plus 1 processing, so a seventh live generation job gets queue_full.
Written by Sume