Sume waitForJob in TypeScript: ms timeout, failed jobs resolve
waitForJob in @sume-com/sdk takes milliseconds, resolves for failed and canceled jobs, and throws only on timeout or a failed read. 25-line sample.

waitForJob from @sume-com/sdk resolves with the job whenever it reaches any terminal status, including failed and canceled, so you must check job.status yourself. It throws only for two reasons: SumeJobTimeoutError when the deadline passes, and SumeJobRequestError when reading the job fails.
Its timeout and pollInterval are in milliseconds. The defaults I read in the SDK source (version 0.2.0) are 20 minutes and 2,000 ms, and the SDK runs page documents the helper.
A sample that handles every outcome
The program submits a video in async mode with an idempotency key, then waits. It typechecked against the SDK source in the Sume repository with only the process type missing, which the Node types add.
import { createSumeClient, generateVideoV1, waitForJob,
SumeJobRequestError, SumeJobTimeoutError } from "@sume-com/sdk";
const client = createSumeClient({ apiKey: process.env.SUME_API_KEY! });
const { data: sub, error } = await generateVideoV1({
client,
headers: { "idempotency-key": "promo-clip-order-8823-v1" },
body: { prompt: "Slow push-in on a ceramic mug", mode: "async" },
});
if (error) throw new Error(JSON.stringify(error));
const jobId = sub!.data.request_id;
try {
const job = await waitForJob(jobId, {
client,
timeout: 10 * 60_000, // milliseconds
pollInterval: 3_000, // milliseconds, a floor
});
if (job.status === "completed") console.log(job.result);
else console.error(job.status, job.error); // failed or canceled resolves too
} catch (e) {
if (e instanceof SumeJobTimeoutError) console.warn("still running:", e.jobId, e.lastStatus);
else if (e instanceof SumeJobRequestError) console.error("read failed:", e.jobId, e.status);
else throw e;
}How the loop behaves
| Situation | Result |
|---|---|
| Job completes | Resolves with the job; result holds the artifacts |
| Job fails or is canceled | Resolves too; read status and error |
| Deadline passes | Throws SumeJobTimeoutError with jobId and lastStatus; the job is not canceled |
| A read returns a non-success response | Throws SumeJobRequestError with jobId, status and body |
Server sends next_poll_after_seconds | Raises the gap; it never makes it shorter than pollInterval |
Two details that save a day
The deadline is checked before each sleep, so a short timeout cannot be overshot by a long server hint. And the generated operations such as generateVideoV1 do not throw: they resolve to { data, error, response }, which is why the sample checks error right after submitting.
When a timeout fires, log the job id and lastStatus, then decide whether to keep waiting. Because the job still runs, resubmitting the same prompt would pay twice. See the timeouts comparison for the other waits.
Choosing timeout and pollInterval
Pick the timeout from the work, not from habit. A short image job fits a one or two minute budget, while video work deserves the ten minutes in the sample. Keep pollInterval at or above the 2,000 ms default unless you have a reason; the server hint will raise it when it wants you to slow down, and each poll counts against your read bucket.
If you run many jobs at once, share one client and wait on each id in its own promise. Concurrency is limited by your plan, so submit in waves that respect it, as the admission docs describe, rather than starting fifty waits for jobs that sit queued.
- Timeout and interval are milliseconds; write them as
10 * 60_000and3_000. - Branch on
job.statusafter the call returns. - Catch the two error classes by
instanceof, and rethrow anything else.
Sources
Related posts
More in Developers
- Sume Send test vs Redeliver: which to use for avatar video jobs
Send test posts a dummy webhook.test payload to a URL you type; Redeliver re-sends a real job's terminal event with a fresh signature. When to use each.
- Sume webhook URL localhost returns 400: test with a tunnel
Sume refuses localhost, private ranges, and plain HTTP webhook URLs with a 400 at create. Use a public HTTPS tunnel and verify the signature.
- Suno v6-wild is less predictable: how to keep a track on Sume
Suno describes v6-wild as less predictable. Sume Music has no seed, so you cannot rerun a track; keep the job result and its URL as the only master.
- Tailscale Funnel for Sume webhooks: a public HTTPS URL for localhost
Sume webhook URLs must be public HTTPS, so localhost is rejected. Tailscale Funnel gives your dev machine one, plus a receiver that refuses an empty secret.
Written by Sume