result_ready vs terminal vs completed: gate the Sume result fetch
Poll a Sume job until terminal, fetch the result only when result_ready is true. Failed and canceled jobs answer 409 job_not_completed on /result.

Stop polling on terminal, and fetch the result only when result_ready is true. A failed or canceled Sume job is terminal, but GET /v1/jobs/{id}/result returns 409 job_not_completed for it, so a loop that fetches the result on every terminal state will treat a failure as an error in the wrong place. Read status, result and error from the job record instead.
The three flags
A Sume job moves through queued, processing, then completed, failed or canceled. The envelope adds terminal and result_ready, and a queue-shaped status (IN_QUEUE, IN_PROGRESS, COMPLETED, FAILED, CANCELED) that mirrors sume_status.
The OpenRouter video guide, read today, has four statuses for its job: pending, in_progress, completed, failed. A client written for it that waits for only completed or failed should also handle canceled on Sume.
| Sume status | Terminal | Result fetch | OpenRouter equivalent |
|---|---|---|---|
queued | No | 409 | pending |
processing | No | 409 | in_progress |
completed | Yes | 200 | completed |
failed | Yes | 409 job_not_completed | failed |
canceled | Yes | 409 job_not_completed | Not listed on this page |
A loop that branches once
The helper waitForJob resolves with the job record from /v1/jobs/:id, not with /result, for this reason. It throws SumeJobTimeoutError after 20 minutes by default; the timeout does not cancel the job.
import { createSumeClient, waitForJob } from "@sume-com/sdk";
const client = createSumeClient({ apiKey: process.env.SUME_API_KEY! });
export async function finish(jobId: string) {
const job = await waitForJob(jobId, { client });
if (job.status === "completed") return job.result;
throw new Error(`job ${jobId} ended ${job.status}: ${JSON.stringify(job.error)}`);
}Where the media lives
A completed job returns artifacts on media.sume.com. Those URLs are durable, so you can store the URL and not copy the file in a hurry.
- Do not poll faster than
next_poll_after_seconds. - A job is readable only by the member whose key created it.
- Cancel works only before generation starts; otherwise
409 job_generation_already_started.
Webhook consumers get the same split
The webhook event names the outcome already: job.completed, job.failed or job.canceled. Only the first has artifacts in its payload. The second and third carry an error object and status: "ERROR". So a receiver does not need to call /result at all for a completed job; the artifact list is in the event.
For polling code the same rule is status === "completed". Use result_ready when you want one flag that is true exactly when the result can be read, and use terminal only to stop the loop.
Edge cases
Cancel is possible only before generation starts. A cancel call after that returns 409 job_generation_already_started, and the job continues to its own end, so do not mark it canceled in your database until the record says so. A job is readable only by the member whose key created it, so read it with that key.
Sources
Related posts
More in Developers
- Sume TTS word timestamps to caption cues for a narrated 60-second clip
Ask Sume TTS for timestamps.words, group them into cues and send them to video-captions so no recognition runs. About 25 cents for a 60-second narration.
- Sume TypeScript SDK waitForJob: 20-minute timeout, job keeps billing
How @sume-com/sdk waitForJob polls a generation job, what SumeJobTimeoutError means, and why a client timeout does not cancel or refund the job.
- Webhook endpoint down: redeliver a Sume video job after the retries
Sume retries a job webhook 10 times, 30 seconds apart. If your receiver was down longer, POST /v1/jobs/{id}/webhook/redeliver re-sends the terminal payload.
- waitForJob times out at 20 minutes: why it does not fit a Vercel route
Sume SDK waitForJob waits 20 minutes by default and the job keeps billing if it throws. Vercel functions default to 300 s, so wait in a worker.
Written by Sume