Five status vocabularies on the Sume and OpenRouter video APIs
Sume job status, the queue-shaped field, /v1/videos status, webhook delivery status and OpenRouter status differ in spelling. One TypeScript map fixes it.

A Sume video integration can see five different status vocabularies: the job status (queued, processing, completed, failed, canceled), the queue-shaped status field (IN_QUEUE and friends), the /v1/videos status (pending, in_progress, completed, failed, cancelled), the webhook delivery status, and, if you also call OpenRouter, its own list. Spelling differs, and so does the double-L in cancelled.
The vocabularies
The values come from Sume's Jobs and results, Errors, and Video generation pages, and from OpenRouter's video guide.
| Surface | Values | Notes |
|---|---|---|
| Sume job (/v1/jobs/:id) | queued, processing, completed, failed, canceled | Terminal: completed, failed, canceled |
| Sume queue-shaped status field | IN_QUEUE, IN_PROGRESS, COMPLETED, FAILED, CANCELED | Maps one-to-one onto the job status; do not mix the two |
| Sume /v1/videos | pending, in_progress, completed, failed, cancelled | Same job also visible at /v1/jobs/{id} |
| Sume webhook delivery | pending, delivering, delivered, retrying, failed, exhausted | About the callback, not the job |
| OpenRouter poll status | pending, in_progress, completed, failed | Webhook payloads also use cancelled and expired |
Normalize once, at the edge
Map every value to your own small set as soon as it enters your code. The function throws on an unknown value, so a new status surfaces in a test and does not become an infinite poll. I ran this with Bun.
type Phase = "waiting" | "running" | "done" | "failed" | "canceled";
const PHASES: Record<string, Phase> = {
queued: "waiting", pending: "waiting", IN_QUEUE: "waiting",
processing: "running", in_progress: "running", IN_PROGRESS: "running",
completed: "done", COMPLETED: "done",
failed: "failed", FAILED: "failed",
canceled: "canceled", cancelled: "canceled", CANCELED: "canceled",
};
export function phase(status: string): Phase {
const mapped = PHASES[status];
if (!mapped) throw new Error(`unknown job status: ${status}`);
return mapped;
}
for (const s of ["queued", "pending", "in_progress", "cancelled", "COMPLETED"]) {
console.log(s, "->", phase(s));
}Two traps
First, do not use the webhook delivery status to decide whether a job finished. A delivery can be exhausted on a job that completed. Second, poll on the booleans terminal and result_ready, or on sume_status, but not on a mix of the queue-shaped status and sume_status. They always agree, but code that reads both invites drift.
A GET /v1/jobs/:id/result on a job that did not complete answers 409 job_not_completed. For failed and canceled jobs, read the failure from the job record.
Mapping rules
Map every vocabulary to a small internal set such as pending, running, done, failed and canceled, and keep the original string for logs. Spelling differs: /v1/videos uses cancelled, while Sume jobs use canceled. Failed and canceled job webhooks both carry the status ERROR, so branch on the event name, not the status.
Do not mix the queue-shaped field with the job status. They describe the same job in different terms, and a client that reads one and expects the other will get stuck.
Sources
Related posts
More in Developers
- FLUX 3 Image rewrites your instruction: result.prompt vs Sume's rows
BFL's FLUX 3 Image expands an edit instruction into a detailed prompt and returns it in result.prompt. What to log, and what Sume's rows return instead.
- FLUX 3 Image has no negative prompt: what to write, and Sume's rows
BFL's FLUX 3 Image has no negative_prompt field; it wants the positive version. The replacement table, a runnable rewriter, and Sume's image rows' fields.
- FLUX.3 not on Sume yet: detect it via /v1/images/models, fall back
Sume does not list FLUX.3. A Python script reads /v1/images/models, uses FLUX.3 if it appears, else Nano Banana 2.1 at 2K for $0.15. BFL 2k is $0.100.
- Format run expires_at: a 90 minute ceiling for your Python poll loop
A Sume Format run receipt has an expires_at deadline: 90 minutes from creation, sooner if the run goes quiet. Use it as your loop ceiling, with Python backoff.
Written by Sume