Poll Sume jobs with next_poll_after_seconds, not a fixed sleep
Sume status responses carry terminal, result_ready and next_poll_after_seconds. Use them in your loop instead of a fixed 30-second sleep. JavaScript included.

Poll a Sume job on the booleans it gives you. GET /v1/jobs/{id}/status returns terminal, result_ready and, while the job runs, next_poll_after_seconds. Sleep that long, stop when terminal is true, and fetch the result when result_ready is true. A fixed sleep wastes calls on short jobs and lags on long ones.
The fields to read
Sume's docs say to obey next_poll_after_seconds when it is present and to use exponential backoff when it is not. The status endpoint also returns a queue-shaped status (IN_QUEUE, IN_PROGRESS, COMPLETED, FAILED, CANCELED) for clients ported from other queue APIs. It maps one to one onto sume_status, and the two always agree, so pick one and do not mix them.
| Field | Use it to |
|---|---|
| terminal | Stop polling |
| result_ready | Know when GET result_url will succeed |
| next_poll_after_seconds | Pick the next sleep |
| sume_status | Branch on queued, processing, completed, failed, canceled |
A loop that follows the server
This version falls back to exponential backoff capped at 30 seconds, and throws on a failed or canceled job. It reads the key from the environment and refuses to run without it.
const key = process.env.SUME_API_KEY;
if (!key) throw new Error('set SUME_API_KEY');
const h = { Authorization: `Bearer ${key}` };
const api = 'https://api.sume.com/v1/jobs/';
const sleep = (s) => new Promise((r) => setTimeout(r, s * 1000));
export async function waitForResult(id) {
let back = 2;
for (;;) {
const s = await (await fetch(api + id + '/status', { headers: h })).json();
if (s.terminal) {
if (s.sume_status !== 'completed') throw new Error(s.sume_status);
return (await fetch(api + id + '/result', { headers: h })).json();
}
await sleep(s.next_poll_after_seconds ?? (back = Math.min(back * 2, 30)));
}
}Set the deadline on your side
The wait lives in your client, so its timeout can be minutes. A reasonable ceiling for video is around twenty minutes, according to the jobs guide. If you give up, you have only stopped waiting. The job continues and bills, so keep its id and read it later, or cancel it.
If you use TypeScript
waitForJob in @sume-com/sdk is this same loop, so you can use it instead of rewriting the logic. Either way, never poll tighter than the hint. Sume sends hints so that a fleet of clients does not hammer the status endpoint.
Related posts
More in Developers
- No GET /v1/audio-detach/:id: poll the job instead
Audio detach and timeline audio have no GET resource route. Read /v1/jobs/:id/status and /result. Video captions and avatar video do have resource GETs.
- Node 22 batch runner for a prompt file: four lanes on Sume
Read one prompt per line, render each on Sume POST /v1/videos with four concurrent lanes and a stable idempotency key per line, and save clip-N.mp4. Node 22.
- Node quickstart: your first Wan 3.0 video on Sume and what 30 s costs
Node 18 fetch script: POST /v1/videos with wan-3.0, poll, print the URL. The list-price arithmetic for 30 s at 480p, 720p and 1080p, times the 1.25 margin.
- Node stream.pipeline to save a Sume MP4 and catch truncation
Stream a finished Sume video to disk with stream.pipeline, then compare bytes written with content-length so a cut-off MP4 never reaches your users.
Written by Sume