Python match on a Sume run status: terminal is not success
A Format run can be terminal as completed, failed, canceled or skipped. A structural match that never treats done as success, with a null-output case.

Match on the whole run object, not on a boolean like is_done. A Sume Format run has six statuses: queued, processing, completed, failed, canceled and skipped, and four of them are terminal without being a success. Python's match statement lets you write each shape once, put the surprising one (completed with a null output) before the happy path, and fail loudly on a status you have never seen.
The six statuses
The runs docs list the statuses and the SDK warns that terminal is not success: the helpers resolve when a run stops moving and leave it to you to read status and error. skipped only appears when you sent on_active_run: "skip" and another run for the same Format was already active; it is a normal outcome, not an error, and it never produces a webhook. canceled is also silent on the webhook side.
The one that trips people is completed with no output. When a Format has an output schema and the agent could not satisfy it, the run can finish with output null and an output_error describing why, and the webhook envelope reads status: OK with outcome: degraded. A check for status == completed alone will happily pass that along. There is already a dedicated post on the degraded case; here the point is where it lives in your control flow.
A runnable match
The function below returns a string per branch so you can run it with no dependencies. It needs Python 3.10 or newer. Order matters: the null-output pattern is above the plain completed pattern, because match takes the first case that fits.
In production each branch would call into your own code: store the output, route the failure code, log the skip.
def handle(run: dict) -> str:
match run:
case {"status": "completed", "output": None}:
return "completed but output missing: read output_error"
case {"status": "completed"}:
return "success: use output"
case {"status": "failed", "error": {"code": code}}:
return f"failed: route {code}"
case {"status": "skipped"}:
return "skipped: a run was already active"
case {"status": "canceled"}:
return "canceled: nothing to deliver"
case {"status": "queued" | "processing"}:
return "keep polling"
case _:
raise ValueError("unknown status")
for r in (
{"status": "completed", "output": {"url": "x"}},
{"status": "completed", "output": None, "output_error": {"code": "output_schema_unsatisfied"}},
{"status": "failed", "error": {"code": "provider_credits_exhausted"}},
{"status": "skipped"},
):
print(handle(r))Route failures by code
A failed run carries error.code, and you should branch on it, not on the message. provider_credits_exhausted is not your wallet and is not retryable; output_schema_unsatisfied, primary_output_missing and incomplete_assembly say the work ran but the deliverable did not materialise, so a continuation with previous_run_id is usually the right move; unattended_blocked means the agent needed a human. Retrying a failed run needs a new Idempotency-Key, because the old key replays the old run.
| Status | Meaning | Your action |
|---|---|---|
| completed, output present | Success | Use output |
| completed, output null | Degraded | Read output_error; fix schema or continue |
| failed | Error | Route on error.code |
| skipped | No run started | Wait for the active run, then re-trigger |
| canceled | Stopped by you | No webhook arrives |
| queued / processing | Not terminal | Keep polling |
Keep the wildcard branch
The last case raises. A new status or an unexpected shape should be an alert in your logs, not a quiet pass-through into billing or publishing code. If you use the TypeScript SDK, the same discipline is an exhaustive switch with a never check, as in the job status post.
Wiring it to the SDK
In Python you will usually have the run as a decoded JSON object from a receipt or a webhook payload, which is exactly the shape the match patterns expect. Add a case for the webhook envelope if you route stored events through the same function: status: OK with outcome: degraded is the receipt's completed-with-null-output case in another spelling. Test the function with one fixture per status plus a deliberately unknown one, and keep the fixtures next to the code so a new status shows up as a failing test.
Sources
Related posts
More in Developers
- Log x-sume-request-id and Idempotency-Key on every call (Python)
A requests response hook that writes one JSON log line per Sume call: x-sume-request-id, idempotency key, error code and rate-limit headers. Tested.
- Does a queue_full 429 charge me? Sume's reservation rules
A Sume 429 queue_full means the workspace has no accepted-job capacity left. The failed admission releases its reservation; retry with the same Idempotency-Key.
- IN_QUEUE or queued? Two status fields on a Sume job, do not mix
GET /v1/jobs/{id}/status returns sume_status and a queue-shaped status that map one to one. Which to poll, and how /v1/videos values differ.
- Quota job error vs 402 insufficient_credits: where each appears
A 402 insufficient_credits means the submit was refused; a quota job category means an accepted job later failed. How to tell them apart and what to do next.
Written by Sume