waitForJob threw SumeJobTimeoutError: resume polling with the job id
The Sume SDK stops waiting after 20 minutes by default, but the job keeps running. Catch SumeJobTimeoutError, read jobId and wait again. TypeScript example.

What do I do when waitForJob throws SumeJobTimeoutError?
Catch it, read error.jobId, and call waitForJob again for the same job. The error means your wait ran out, not that the job failed. The SDK's default timeout is 20 minutes, and a client-side timeout does not cancel the job: it keeps running and still bills.
Never resubmit on this error. A second submit creates a second paid job. If you lost the id as well, retry the original submit with the same Idempotency-Key: the replay returns the job you already created.
The 20 minute default exists because video and avatar jobs often run for minutes. If you know your clips are short, lower timeout so a stuck job surfaces sooner. If you submit long 30 second generations in a queue behind other work, raise it, or keep it and use the resume loop below, which gives you a log line and a decision point at each round.
What each error and result means
waitForJob resolves for any terminal status, including failed and canceled jobs. It throws only when the wait or the request itself fails.
| Outcome | Meaning | Next step |
|---|---|---|
| Resolves, status completed | Result ready | Read job.result.artifacts |
| Resolves, status failed or canceled | Terminal, not a throw | Read the error, decide on a retry |
| Throws SumeJobTimeoutError | Wait ran out, job still running | Wait again with error.jobId |
| Throws SumeJobRequestError | The API rejected a poll (401, 404, 5xx) | Fix the key or id; it carries jobId |
Resume the wait with a cap
Bound the number of resumes so a stuck job does not loop forever, and let next_poll_after_seconds set the pace: the SDK's pollInterval of 2 seconds is only a floor.
import { createSumeClient, waitForJob, SumeJobTimeoutError } from "@sume-com/sdk";
const client = createSumeClient({ apiKey: process.env.SUME_API_KEY! });
export async function finish(jobId: string, maxRounds = 3) {
for (let round = 1; round <= maxRounds; round++) {
try {
return await waitForJob(jobId, { client, timeout: 20 * 60_000 });
} catch (err) {
if (!(err instanceof SumeJobTimeoutError)) throw err;
console.log(`round ${round}: job ${err.jobId} still running`);
}
}
throw new Error(`job ${jobId} still running; keep the id and check later`);
}
const job = await finish(process.argv[2]!);
console.log(job.status, job.result?.artifacts);Store the id before you wait
Write the job id to your database as soon as the submit returns, before you call waitForJob. A process that restarts mid-wait can then resume instead of starting over. For work that can take many minutes, a webhook is a better primary path, with this loop as the fallback.
One more habit: pass an AbortSignal when your own process is shutting down. The SDK aborts the in-flight request and rejects with your reason, and the job is untouched on the Sume side, so the next process can resume it with the id you saved.
Sources
Related posts
More in Developers
- SDK waitForJob after a 202 from createImage: TypeScript sample
When createImage returns 202 on a slow gpt-image-2.5 render, pass the job id to waitForJob from @sume-com/sdk and read the terminal job instead of hand-polling.
- Rotate the Sume webhook secret twice in 24 hours: the oldest one dies
One rotation keeps the old secret valid for 24 hours. A second rotation inside that window retires the secret from two rotations ago. Verifier in Python.
- Seedance 2.5 job failed: refund, new idempotency key, and rerun cost
A failed Seedance 2.5 job is refunded on Sume. Retry with a new Idempotency-Key; the old one replays the failed job. A Python handler and the rerun cost.
- Low-latency TTS without streaming: one job per sentence
Sume TTS has no streaming. To start playback early, split the script by sentence, submit the jobs in parallel and play each file as it finishes, in order.
Written by Sume