@sume-com/sdk waitForJob is not exported: a 26-line replacement
The docs show waitForJob, but the npm build of @sume-com/sdk 0.2.0 does not export it. Here is a fetch version with 429 tolerance and a deadline.

If import { waitForJob } from "@sume-com/sdk" fails with "does not provide an export named waitForJob", it is not your setup. The registry lists @sume-com/sdk 0.2.0 as published on 2026-08-02 and its build does not export waitForJob, SumeJobTimeoutError or SumeJobRequestError, although the docs page for the SDK describes all three. Until a newer release lands, poll the job with fetch. The 26-line function below does what the docs describe: deadline, a floor on the poll gap, tolerance for 429 and 5xx on status reads, and a final read of the job record.
What the published 0.2.0 build does export: createSumeClient, the generated operations such as generateImageV1 and getApiJobStatus, uploadFile, verifyWebhook, waitForRun and subscribeFormatRun. The last two wait on Format, Action or Agent runs, which are a different surface from generation jobs at /v1/jobs/:id.
What I compared
I installed 0.2.0 from npm on 2026-10-02 and listed the module's exports, then read the repository's SDK docs at the same date.
| Helper | In npm 0.2.0 | Described in docs |
|---|---|---|
| createSumeClient, generated operations | Yes | Yes |
| uploadFile, verifyWebhook | Yes | Yes |
| waitForRun, subscribeFormatRun | Yes | Yes |
| waitForJob | No | Yes |
| SumeJobTimeoutError, SumeJobRequestError | No | Yes |
The replacement
It returns the job record from GET /v1/jobs/{id}, not from /result, because /result answers 409 job_not_completed for failed and canceled jobs. Read status, result and error off the record. A TimeoutError means you stopped watching; the job keeps running.
const sleep = (ms, signal) => new Promise((res, rej) => {
const t = setTimeout(res, ms);
signal?.addEventListener("abort", () => { clearTimeout(t); rej(signal.reason); }, { once: true });
});
export async function waitForJob(jobId, { base, key, timeoutMs = 20 * 60_000, maxTransient = 6, signal } = {}) {
const stop = AbortSignal.any([AbortSignal.timeout(timeoutMs), ...(signal ? [signal] : [])]);
const get = (path) => fetch(`${base}/v1/jobs/${jobId}${path}`, { headers: { Authorization: `Bearer ${key}` }, signal: stop });
for (let bad = 0; ; ) {
const res = await get("/status");
if (res.ok) {
bad = 0;
const { data } = await res.json();
if (data.terminal) {
const job = await (await get("")).json();
return job.data.job; // read status, result and error off the record
}
await sleep(Math.max(2, data.next_poll_after_seconds ?? 2) * 1000, stop);
} else if ((res.status === 429 || res.status >= 500) && ++bad <= maxTransient) {
const hint = Number(res.headers.get("retry-after")) || 2 ** bad;
await sleep(Math.min(hint, 30) * 1000, stop);
} else {
throw new Error(`job ${jobId}: status read failed with ${res.status}`);
}
}
}How it behaves
Against a status endpoint that answers one 429 with retry-after: 1 and then completes, it makes three status reads and returns the record; when the deadline is shorter than the job, it throws a TimeoutError.
- Poll gap is the larger of 2 seconds and
next_poll_after_seconds. - A 429 or 5xx on a status read waits for
retry-after(or 2, 4, 8 seconds, capped at 30) and retries up to six times in a row, then throws. - Any other non-2xx, such as 401 or 404, throws at once, because retrying will not change it.
- The deadline defaults to 20 minutes, the figure the docs suggest for video.
Limits
Confirm the field names against a real job before relying on them. Do not treat a thrown error as a reason to resubmit: store the job id, and if you must retry the submit, send the same Idempotency-Key. When a newer SDK release exports waitForJob, switch to it; its retry behavior is described on the docs page, not in this function.
Sources
Related posts
More in Developers
- Check an Avatar payload with sume tools schema before --confirm-paid
sume tools schema avatar-videos.create --json prints the exact fields before you pass --confirm-paid. A read-only pre-flight loop for a spending CLI command.
- Cost of one Sume agent thread or turn: /v1/usage thread_id and job_id
Pass thread_id to GET /v1/usage to sum one Studio Agent thread, or a turn's job id as job_id for that turn plus every job it commissioned. Fields and a request.
- Sume /v1/balance: next_expires_at and the expiring-soon fields
GET /v1/balance returns USD micros and cents, a funded or empty state, and an expiration block with the next expiry and the amount expiring soon. Field list.
- Sume /v1/usage limit: it caps rows, not the summary total
On GET /v1/usage, limit (1 to 100) only caps the rows listed. With thread_id, run_id or job_id the summary folds every row, up to 5,000.
Written by Sume