waitForJob resolves for failed jobs: read job.status, not catch (TS)
In the Sume SDK on main, waitForJob returns the job for completed, failed and canceled alike. Branch on job.status and job.error, and keep catch for timeouts.

A common first draft of a job wrapper puts waitForJob in a try block and treats a throw as "the video failed". In the Sume SDK source on main that reading is wrong. waitForJob resolves with the final job record for any terminal status: completed, failed and canceled. A failed generation is a result you asked for, so it arrives as a return value with status and error fields, exactly as a webhook handler would see it.
The helper is on main only. The published @sume-com/sdk 0.2.0 package, dated 2026-08-02, does not export it, so use a build from main for this sample, or use a hand-written poll loop on 0.2.0.
What throws and what returns
| Situation | Result |
|---|---|
| Job completed | Resolves with the job, status completed |
| Job failed or canceled | Resolves with the job, error filled in for a failure |
| Client timeout elapses first | Throws SumeJobTimeoutError with jobId and lastStatus |
| Status or job read returns an error | Throws SumeJobRequestError with the HTTP status and body |
| Your AbortSignal fires | The signal's reason is thrown and the request is aborted |
A wait with progress and a deadline
The sample reads the job id from the command line, polls at least every 3 seconds and logs each status snapshot through onStatus. The snapshot is the whole status payload. next_poll_after_seconds from the server can only raise the gap between polls, never shorten it. Two deadlines are set: timeout makes the SDK throw SumeJobTimeoutError, and the AbortSignal is a backstop that also cancels the request in flight.
import { createSumeClient, waitForJob } from "@sume-com/sdk";
const client = createSumeClient({ apiKey: process.env.SUME_API_KEY ?? "" });
const job = await waitForJob(process.argv[2] ?? "job_f", {
client,
timeout: 10 * 60_000,
pollInterval: 3_000,
signal: AbortSignal.timeout(11 * 60_000),
onStatus: (status, snapshot) =>
console.log(status, snapshot.next_action ?? "-", snapshot.queue?.state ?? "-"),
});
if (job.status === "completed") {
console.log("done", job.id);
} else {
// A failed or canceled job resolves here. It is not an exception.
console.log(job.status, job.error?.code, job.error?.retryable, job.error?.next_action);
}Reading the result
- Branch on
job.status. Onlycompletedhas a usable result. For anything else readjob.error, which carriescode,retryableandnext_action. - The final hop reads the job record, not the result route, because
GET /v1/jobs/{id}/resultanswers409 job_not_completedfor failed and canceled jobs. - A timeout does not stop the job. It keeps running and billing, so keep the id and poll again, or cancel it if it has not started.
- Do not resubmit on a timeout. A resubmit with the same
Idempotency-Keyreturns the original job instead of a second charge.
The polling contract is described in the jobs guide, and client options are in the SDK guide. Check the published package on npm before relying on a helper.
Sources
Related posts
More in Developers
- Fetch a Sume video output with the content endpoint index query
GET /v1/videos/{jobId}/content takes an index that defaults to 0. When it matters, how it lines up with unsigned_urls, and the curl line that saves a file.
- 768p on seedance-2.5 returns 400: use 720p, or pin a MiniMax id
Sume's resolution list is per model. seedance-2.5 takes 480p, 720p and 1080p; 768p exists on the MiniMax H3 ids and H3 Max Recast, and kling-3 has no 480p.
- Seedance 2.5 price calculator in Python: tokens to dollars
A 20-line Python function that turns Seedance 2.5 resolution, aspect ratio and seconds into the Sume billed price, checked against two known amounts.
- Sentry Vercel AI integration records inputs and outputs by default
Sentry's Vercel AI integration records inputs and outputs by default when genAI collection is on. Check what Sume tool calls and results put in spans.
Written by Sume