Sume status vocab: a job is completed, a resource is ready
Sume lists three status vocabularies: job, resource, webhook delivery. Only jobs say completed; resources say ready, so a check on the wrong one never matches.

A Sume job is completed. A Sume resource is ready. They are separate vocabularies in the errors and rate limits page, and a poll loop that waits for ready on a job, or completed on a resource, spins until its own timeout and never sees the value it wants.
The docs list three status vocabularies side by side. Keep them as three enums in your code, not one string union that everything is compared against.
The three vocabularies
These values come from the status vocabulary table in the Sume docs.
| Object | Values |
|---|---|
| Job status | queued, processing, completed, failed, canceled |
| Resource status | processing, ready, failed, canceled, archived |
| Webhook delivery status | pending, delivering, delivered, retrying, failed, exhausted |
Where each one shows up
Jobs are the generation records behind /v1/jobs/:id. The jobs page marks completed, failed and canceled as terminal, and queued and processing as normal non-terminal states. The status route also returns the booleans terminal and result_ready, plus a queue-shaped status field (IN_QUEUE, IN_PROGRESS, COMPLETED, FAILED, CANCELED) that maps one to one onto sume_status. Poll on the booleans or on sume_status, and do not mix the two fields.
Webhook delivery is the third vocabulary. It describes whether Sume reached your endpoint, not whether the work finished. A job can be completed while its delivery is exhausted, because a failed delivery never changes the job. Format run receipts add their own webhook_delivery block with not_armed as an extra value before delivery is scheduled, so a status switch written for jobs needs a default branch. That is also why one shared helper such as isDone(status) is a trap. It would have to know three vocabularies, and the day it guesses wrong the symptom is a worker that waits for a value that can never arrive, with no error to point at.
Spelling is part of the contract
Sume returns these strings in JSON, so they are compared byte for byte in most client code.
The docs spell it canceled with one L in both lists that contain it (jobs and resources). A branch for cancelled never matches. If you keep a database column of statuses, store the exact strings Sume sends and map them to your own labels at display time.
Validate before you branch
The sample keeps each vocabulary as its own list and throws when a status is not in the list for that kind. That turns "my job never finished" into an immediate, named error in tests. Only the job set marks terminal values here, since that is the one the docs state explicitly. It runs on Node 22 with no dependencies.
const VOCAB = {
job: ["queued", "processing", "completed", "failed", "canceled"],
resource: ["processing", "ready", "failed", "canceled", "archived"],
delivery: ["pending", "delivering", "delivered", "retrying", "failed", "exhausted"],
};
const JOB_TERMINAL = new Set(["completed", "failed", "canceled"]);
export function check(kind, status) {
const allowed = VOCAB[kind];
if (!allowed) throw new Error(`unknown kind ${kind}`);
if (!allowed.includes(status)) throw new Error(`${status} is not a ${kind} status`);
return status;
}
export const jobIsTerminal = (s) => JOB_TERMINAL.has(check("job", s));
console.log(jobIsTerminal("completed"));
console.log(check("resource", "ready"));
try { jobIsTerminal("ready"); } catch (e) { console.log(e.message); }Rules worth keeping
- Switch on the job vocabulary for
/v1/jobsobjects, and never onready. - Add a
defaultbranch that logs the unknown value. A new value then shows up in your logs instead of a silent hang. - Stop polling on
terminal: true, not on a hand-written list of strings. - Log the object kind with every status you store, for example
job:completedorresource:ready, so a dashboard that mixes them stays readable. - Treat delivery status as a separate signal. Reconcile by polling
status_urlwhen a delivery isfailedorexhausted.
Sources
Related posts
More in Developers
- Sume timeouts in one table: 30 s, 55 s, 10 s, 90 minutes
Every wait in the Sume API has its own number: sync 30 s, jobs_wait 55 s, webhook attempts 10 s, SDK helpers 10 and 20 minutes, Format runs 90 minutes.
- Sume /v1/usage summary.final is false: a hold is open, not spent
Read GET /v1/usage?job_id= and book cost only when summary.final is true. held_usd_micros and refunded_usd_micros are not spend. Code to poll it.
- Sume webhooks: 10 attempts 30 seconds apart for a video receiver
A Sume job webhook is tried up to 10 times, 30 seconds apart by default, with a 10 s timeout each. What that means for a video receiver, plus a Python verifier.
- Can a Sume webhook arrive twice? Build an idempotent receiver
Sume retries failed webhook deliveries up to 10 times and Redeliver replays a real event, so one terminal event can reach you twice. Dedupe on job_id or run_id.
Written by Sume