Sume /v1/videos says cancelled, /v1/jobs says canceled: a status map

Two spellings and two vocabularies for the same Sume job: pending to cancelled on /v1/videos, queued to canceled on /v1/jobs. A table and a TypeScript mapping.

5 min readSume
All posts

The same Sume video job reads differently on its two surfaces. GET /v1/videos/{jobId} uses pending, in_progress, completed, failed, and cancelled, with two l's. GET /v1/jobs/{id}/status uses queued, processing, completed, failed, and canceled, with one. A client that checks status === "canceled" against the videos endpoint never matches.

The Video generation page says you can also see the same job at /v1/jobs/{id}/status and /v1/jobs/{id}/result. Mixing the two is where the spelling bites.

The map

The first two columns come from the Video generation page and the Jobs and results page, read 2026-10-09.

Status vocabularies for one video job, as of 2026-10-09.
/v1/videos/{jobId}/v1/jobs/{id}/status (`sume_status`)Queue-shaped `status`
pendingqueuedIN_QUEUE
in_progressprocessingIN_PROGRESS
completedcompletedCOMPLETED
failedfailedFAILED
cancelledcanceledCANCELED

Normalize once

Pick one vocabulary for your own code and convert at the edge. The snippet maps the videos spelling to the jobs one, and treats anything unknown as an error so a new value does not pass silently.

const fromVideos = {
  pending: "queued",
  in_progress: "processing",
  completed: "completed",
  failed: "failed",
  cancelled: "canceled",
} as const;

export type JobStatus = (typeof fromVideos)[keyof typeof fromVideos];

export function normalize(videoStatus: string): JobStatus {
  const mapped = (fromVideos as Record<string, JobStatus>)[videoStatus];
  if (!mapped) throw new Error(`unknown video status: ${videoStatus}`);
  return mapped;
}

export const isTerminal = (s: JobStatus) =>
  s === "completed" || s === "failed" || s === "canceled";

Downloading the result

On /v1/videos, a completed job lists unsigned_urls, and GET /v1/videos/{jobId}/content?index=0 downloads the output. The OpenAPI description says the content endpoint redirects to the generated video, and that a job that terminally failed answers 409 job_failed with the public reason, not the retryable job_not_completed. So a 409 job_not_completed there means keep polling, and a 409 job_failed means stop and read the error.

If the job id is stored, either surface works for status. On the jobs side the envelope also gives terminal, result_ready, and next_action, so a client that only needs to know when to stop can use those and ignore the spelling entirely.

A note on the third column

The status endpoint also returns a queue-shaped status for clients that come from other queue APIs: IN_QUEUE, IN_PROGRESS, COMPLETED, FAILED, and CANCELED. The docs say it maps one to one to sume_status and that you should not mix the two in one check. These follow the one-l spelling. The two-l spelling belongs only to /v1/videos.

A failed job is the same job on every surface. Only the label differs. The error object and the request_id are what you should log, not the status string.

When you write tests, add one case per row of the table, including the unknown-value case, so a future status does not turn into a silent hang in your poll loop. The mapping function above already throws on a value it does not know, so the test for that case is one line.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume