Sume job statuses: three vocabularies and a Python normalizer
Ported Sora code checks one status word. Sume has pending, queued and IN_QUEUE depending on the route. A table, and a normalizer you can run with no network.

A Sume video job reports its state in up to three vocabularies, and ported code that compares strings will miss one. GET /v1/videos/{id} returns pending, in_progress, completed, failed or cancelled. GET /v1/jobs/{id} returns queued, processing, completed, failed or canceled. GET /v1/jobs/{id}/status also carries a queue-style field, IN_QUEUE, IN_PROGRESS, COMPLETED, FAILED, CANCELED. The docs read 2026-10-06 say these map one to one.
That matters after the OpenAI Sora API ended on 2026-09-24 (Magic Hour tracker, read 2026-10-06), because a rewritten worker often ends up calling both the /v1/videos route and the shared jobs route, and the spelling of the cancelled state differs between them.
The mapping
The video route spells cancelled with two l letters and the jobs route with one, so compare through a normalizer, not a literal. The jobs status response also has booleans, terminal and result_ready, which the docs recommend polling on when you have them (Jobs and results, read 2026-10-06).
| Meaning | /v1/videos poll | /v1/jobs | /v1/jobs/{id}/status queue field | Terminal |
|---|---|---|---|---|
| Accepted, waiting | pending | queued | IN_QUEUE | No |
| Running | in_progress | processing | IN_PROGRESS | No |
| Result ready | completed | completed | COMPLETED | Yes |
| Failed with an error | failed | failed | FAILED | Yes |
| Stopped before running | cancelled | canceled | CANCELED | Yes |
A normalizer
It lowercases, folds the spelling, and returns one of four states. It has no network call, so it runs as is and works as a unit test.
STATE = {
"pending": "waiting", "queued": "waiting", "in_queue": "waiting",
"in_progress": "running", "processing": "running",
"completed": "done",
"failed": "failed",
"cancelled": "stopped", "canceled": "stopped",
}
def normalize(raw: str) -> str:
try:
return STATE[raw.strip().lower()]
except KeyError:
raise ValueError(f"unknown job status: {raw!r}") from None
assert normalize("IN_QUEUE") == "waiting"
assert normalize("cancelled") == normalize("canceled") == "stopped"
assert normalize("COMPLETED") == "done"
print("ok")Why raise on an unknown word
An unknown status should stop the worker, not be treated as still running. A loop that keeps polling on a word it does not recognize can run until its deadline while the job has long since finished or been stopped. Raising gets the new word into your logs the first day it appears.
Keep the terminal set small: done, failed, stopped. Only done means a result exists. For failed, read the error field and decide whether to retry with the same idempotency key; for stopped, the job did not complete and you do not resubmit unless you meant to.
Where each route fits
- Use
/v1/videosfor new video code; its polling URL comes back in the submit response. - Use
/v1/jobs/{id}/statuswhen one monitor watches video, image and audio jobs together, so one parser serves all. - Use
/v1/jobs/{id}/eventsfor a timeline when you debug:job.created,job.queued,job.started,generation.submittedand the terminal events. - Do not mix the spellings in one database column. Store the normalized state next to the raw word, and keep the raw word for support questions.
The earlier posts on the spelling difference and the retry policy by error category go deeper on those two edges.
Tests for the normalizer
A normalizer is a table lookup, so test the table, not the function body. List every word in the mapping above, feed each through the function, and assert the state it returns. Add one case with different capitalization, since the queue-style field is uppercase, and one with the unknown word that must raise.
The test is also documentation. When a new route or a new status word appears, a failing test says exactly where to add it, and the diff shows reviewers what changed in your model of the API. If you store the normalized state in a database, add a migration note too, because an old row with a raw word will otherwise look like a bug.
Share the normalizer as a small module used by every worker and dashboard in the company. Two services with two slightly different mappings are how a job ends up shown as running in one tool and cancelled in another, and one module with one test file removes that whole class of confusion.
- Cover all five states on each of the three vocabularies, fifteen words in total.
- Assert that
cancelledandcanceledmap to the same state. - Assert that an empty string and
Noneraise, so a malformed response fails loudly. - Keep the raw word in a second column; support questions are easier with the exact string the API sent.
Sources
Related posts
More in Developers
- Timeout for AI video jobs: set a deadline in your worker, not HTTP
A Sora-era HTTP timeout of 10 minutes breaks on Sume: sync waits cap at 30 s while jobs run for minutes. Use a client deadline and a poll. Python example.
- Callback or polling for a ported video worker: pick by job count
Replacing a Sora polling worker on Sume: when callback_url beats polling, when polling is enough, and one function that does both.
- Split a long video into Shorts episodes: Python trim ranges
Generate back-to-back video trim ranges for a long vertical video with a tail rule, so no episode is a sliver. Respects the 1800 s source and 900 s output caps.
- Start a song at the chorus in a Short: split, then soundtrack
Timeline's soundtrack has no in-point. Cut the chorus with Timeline audio split, then pass the new audio_url as the soundtrack. Steps and cost: $0.01 + $0.10.
Written by Sume