Which episode finished? Map a Sume run id to your episode record
A Sume Format run does not echo your episode number. Keep a ledger keyed by queue index and run id, and match webhooks on request_id.

Sume will not hand your episode number back to you, so store it yourself: after a bulk create, save the queue id and, for each item, its index and run_id against your episode row. A webhook then carries the same run id as run_id and request_id, and that is the key to look up.
The docs say it plainly in two places. On the structured-output page, an order_id you sent in input can be invisible to the projection pass, so keep identifiers on your side keyed by run.id or by the Idempotency-Key you sent. On the call page, the same advice is that an identifier cannot be echoed back unless the run repeats it.
The two places an id shows up
The queue order is the order you submitted, so item 0 is your first episode. A failed item that never started keeps its index with run_id: null and an error set.
| Where | Field | Use it for |
|---|---|---|
| Queue receipt | items[i].index and items[i].run_id | Joining your episode list to child runs; run_id is null while an item is queued |
| Webhook envelope | run_id and request_id (equal) | Dedupe key, stable across retries |
Build the ledger at create time
Write the queue id and your index-to-episode map before you start polling, then fill in run ids as they appear.
def link_queue(queue: dict, episodes: list[str]) -> dict[str, str | None]:
items = queue["data"]["items"]
if len(items) != len(episodes):
raise ValueError("queue and episode list differ in length")
return {episodes[i["index"]]: i["run_id"] for i in items}
queue = {"data": {"items": [
{"index": 0, "status": "running", "run_id": "arun_a", "error": None},
{"index": 1, "status": "queued", "run_id": None, "error": None},
]}}
print(link_queue(queue, ["s01e01", "s01e02"]))Matching a webhook
- Look up the episode by
run_idfrom the envelope, not from the nested receipt id. - Dedupe on
request_id; it is the same on every retry of the same run. Order deliveries bycreated_at. - Switch on
outcome:ok,degraded(real artifacts but no structured output) orerror. - A canceled or skipped run never delivers a webhook, so close those episodes from the response you got when you canceled or created.
- A receipt over 1 MiB arrives with
payload: nullanderror.result_url; fetch it from there.
What the queue will not do for you
There is no public list-queues or cancel-queue endpoint, so the queue id you saved is the only handle on that batch. Per-item webhooks are set with communication.webhook_url on each item, and the queue itself has none. Keep the mapping in your database; the receipts are the evidence, your table is the index.
Sources
Related posts
More in Developers
- Which limit stopped my agent: 402, queue_full or spend cap
Six different walls look alike from an agent loop. A diagnosis table that tells wallet, run spend cap, queue, request rate, scope and provider credits apart.
- Which Sume API errors should page an engineer: route by category
Route Sume API failures by category: fix-the-input errors go to the caller, quota to finance, queue to a retry, and only internal or unexpected 5xx to on-call.
- Which Sume image models accept the quality parameter?
Only five Sume image rows list quality: ChatGPT Image 2, both ChatGPT Image 2.5 ids, Ideogram V3 and Ideogram 4.5. Every other row returns 400 if you send it.
- Windmill run_wait_result vs a Sume async submit: pick one per job
Windmill advises async mode and offers run_wait_result for short jobs. Sume mirrors that split: async submit plus polling for long work, sync only for short.
Written by Sume