Page through GET /v1/jobs with next_cursor: newest first (Python)
GET /v1/jobs returns newest jobs first and a next_cursor you pass back as starting_after. A Python loop pages safely and maps your idempotency keys to job ids.

After a crash you often hold a list of order numbers and no job ids. GET /v1/jobs is how you rebuild the mapping, but its paging has three behaviours that a first draft of a loop usually gets wrong. This post covers them with a loop that runs.
The route returns data.jobs, an array of job records, and adds data.next_cursor only when another page exists. Each record carries id, type, status, model, idempotency_key, communication_mode and timestamps, so the idempotency key you sent at submit time comes back on the row.
Three behaviours to design for
- Order is newest first. The first page holds your latest submissions, not your oldest, so do not stop on the first page and assume you saw the earliest.
- The cursor is opaque. It is base64url of a timestamp and a job id, and the source comments keep the format private so it can change. Pass
next_cursorback asstarting_afterand nothing else. - A cursor that cannot be decoded does not return an error. The route treats it as no cursor and returns the first page again. A truncated or stale cursor therefore restarts your loop from the newest job instead of failing, and a naive
while next_cursorloop can run forever.
The loop
The function below asks for 100 jobs per page, remembers every job id it has seen and stops when a page adds nothing new, when no next_cursor comes back or after max_pages. The last line prints the mapping from your keys to job ids. The sample uses only the standard library.
import json, os, urllib.parse, urllib.request
def page(**params) -> dict:
url = "https://api.sume.com/v1/jobs?" + urllib.parse.urlencode(params)
req = urllib.request.Request(url, headers={"x-api-key": os.environ["SUME_API_KEY"]})
with urllib.request.urlopen(req, timeout=30) as r:
return json.load(r)["data"]
def by_idempotency_key(max_pages: int = 20) -> dict:
found, seen, cursor = {}, set(), None
for _ in range(max_pages):
data = page(limit=100, **({"starting_after": cursor} if cursor else {}))
fresh = [j for j in data["jobs"] if j["id"] not in seen]
if not fresh: # a stale cursor restarts at the newest row
break
for job in fresh:
seen.add(job["id"])
if job.get("idempotency_key"):
found.setdefault(job["idempotency_key"], job)
cursor = data.get("next_cursor")
if not cursor:
break
return found
jobs = by_idempotency_key()
print({k: j["id"] for k, j in jobs.items()})
assert jobs["order-2"]["id"] == "job_b"Filters and limits
| Parameter | Use |
|---|---|
| limit | Page size, clamped to 1-100 |
| starting_after | The next_cursor from the previous page |
| status | Narrow to one job status |
| type | Narrow to one job type |
| scope, run_id, thread_id | Thread and run narrowing for agent-bound credentials |
Any other query parameter returns 400 unknown_parameter, so a misspelled cursor= fails loudly rather than silently returning page one. Note the name is starting_after.
A plain API key lists only its own member's jobs. A job another member created is not in the list, and fetching it by id is a 404. That rule is covered in the jobs guide, and the full route list is in the public API reference.
Once you have the id, stop paging and poll GET /v1/jobs/{id}/status, which tells you when the job is terminal.
Sources
Related posts
More in Developers
- Poison Sume webhook events: park them in a table, return 2xx
A webhook your handler can never process will be retried up to 10 times. Store the raw body in a dead-letter table, return 2xx, and replay it later.
- Per-customer spend caps on Sume: what Sume caps, what you log
A Sume key belongs to a workspace, not your end customer. Cap each run with generation_spend_cap_usd and keep a per-customer ledger yourself.
- Perplexity Decisions API as a publish gate for Sume output
Check a finished Sume Format image with Perplexity's Decisions API before it ships: base64 data URL, one yes/no question, a threshold, and a human-review lane.
- Pick the cheapest Sume image model for an aspect ratio (Python)
A short Python script that reads GET /v1/images/models, keeps models that list your ratio, reference count and n, then prices them from the endpoint records.
Written by Sume