Seedance job status: pending vs queued, canceled vs cancelled
Sume has two status vocabularies for one video job: pending/in_progress on /v1/videos and queued/processing on /v1/jobs. A poller must handle both.

The same Seedance job reads as pending or in_progress on /v1/videos/{id} but as queued or processing on /v1/jobs/{id}/status. Terminal states are spelled cancelled on the videos route and canceled on the jobs route. A poller that only knows one vocabulary can spin forever on the other.
Mixing the two is a classic source of an endless polling loop, so it is worth a few lines of care.
What does each route call the states?
Both lists come from the Sume docs: the video guide for the first column and the API reference for the second.
| Meaning | /v1/videos/{id} | /v1/jobs/{id}/status |
|---|---|---|
| Waiting | pending | queued |
| Running | in_progress | processing |
| Done | completed | completed |
| Failed | failed | failed |
| Stopped by you | cancelled | canceled |
Which route should I poll?
Either works. The jobs route is described as a lightweight status read for polling, and the result payload is at GET /v1/jobs/:id/result. Pick one per integration and normalize.
Which route you read depends on what you want back. The videos route gives the OpenRouter-shaped response with unsigned_urls once finished. The jobs routes give Sume's own envelope and a separate result call. Both describe the same job, so you can start on one and check on the other while debugging.
How do I normalize in code?
Map every spelling to a small set of your own. This helper treats anything not terminal as still running.
Notice that the helper treats unknown strings as pass-through. That is intentional: if Sume adds a state later, your code should not crash, and is_done will return false so you keep waiting until a timeout you set yourself.
Set that timeout. A reasonable ceiling is a few times the longest generation you expect; the docs say video typically takes 30 seconds to several minutes. When it passes, read the events and the status once more, and only then decide whether to cancel.
TERMINAL = {"completed", "failed", "cancelled", "canceled"}
def normalize(status: str) -> str:
s = status.lower()
if s in ("pending", "queued"):
return "waiting"
if s in ("in_progress", "processing"):
return "running"
if s in ("cancelled", "canceled"):
return "cancelled"
return s
def is_done(status: str) -> bool:
return status.lower() in TERMINALWhat about cancelling?
The API reference says a job can be cancelled before generation starts; after that POST /v1/jobs/:id/cancel returns 409 job_generation_already_started, and cancelling an already-cancelled job is idempotent. See cancelling video jobs. To avoid polling entirely, pass callback_url.
One more spelling trap: if you store statuses in a database enum, add both cancelled and canceled or normalize on write. A single misspelled value is enough to leave a row stuck as running forever.
Sources
Related posts
More in Developers
- Batch Seedance and Kling jobs: generation_limits and wave_size_hint
Submitting many Seedance or Kling clips at once? How Sume's generation_limits fields give a safe in-flight budget, and why wave_size_hint is not concurrency.
- Seedance or Kling job failed: could not download an input media URL
A Seedance or Kling job can fail with 'Could not download an input media URL'. Why Sume rejects a frame or reference URL, and a preflight check for it.
- Seedance and Kling submit errors: which to retry and which to fix
A Sume video submit can fail with 402, 429 queue_full, 429 rate_limited or 400. Which to retry with the same key and which need a changed request.
- Seedance or Kling video 409: job_not_completed vs job_failed
A 409 from GET /v1/videos/{id}/content means two opposite things on Sume: job_not_completed is retryable, job_failed is not. How to tell them apart.
Written by Sume