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
- RMBG, image upscale and video upscale request fields side by side
The three Sume media tools take different bodies: image_url, upscale_factor 1-4, video_url, duration_seconds 1-30, enhancement_tier. One table, prices included.
- Sume run webhook: 10 s timeout, so queue before you call a slow model
Sume gives your webhook 10 seconds per attempt, up to 10 attempts. Verify the signature, store the event, return 2xx, then call any slow model. Python verifier.
- Sume run webhook retries: ten attempts span about 3 hours
Ten delivery attempts with 30-second doubling backoff capped at one hour add up to 11,010 seconds before jitter. The timeline, and what to do after exhaustion.
- Sume run webhook says OK but output is null: handle degraded
A Sume run can complete, bill you, and still have output null. Branch on outcome, not status, and read artifacts and output_error before you retry.
Written by Sume