Wait for an avatar video job with the SDK: waitForJob and its timeout

Avatar jobs need waitForJob, not waitForRun. Defaults, the 20-minute timeout, and why a SumeJobTimeoutError neither cancels nor refunds the render.

5 min readSume
All posts

To wait for a Sume avatar video in TypeScript, submit with mode: "async" and hand the returned job id to waitForJob, not waitForRun. Avatar routes create jobs at /v1/jobs/:id, and the SDK keeps jobs and Format runs apart: a job id and a run id are not interchangeable.

This post follows the TypeScript SDK page and Jobs and results, read 2026-10-02. It covers the talking-video route, POST /v1/avatar-1.0/talking-video, which the avatar video guide describes.

Why waitForJob and not waitForRun?

POST /v1/image-1.0/generate, /v1/video-1.0/generate, the Avatar routes and the /v1/models/sume/.../runs aliases all create jobs. Formats, Actions and Agent Completions create runs. waitForRun reads a run receipt; waitForJob reads the job record from /v1/jobs/:id.

It reads the job record rather than /result on purpose. /result answers 409 job_not_completed for failed and canceled jobs, so a wait built on it would have nothing to hand back. You read status, result and error off the record instead.

What are the defaults?

The options below come straight from the SDK page. Note that the wait is client-side, so it can outlast the 30-second HTTP budget of the server-side sync mode.

waitForJob options (read 2026-10-02)
OptionDefaultNotes
clientmodule defaultPass your own: the module default has no base URL or key.
timeout20 minutesExceeding it throws SumeJobTimeoutError.
pollInterval2 secondsA floor; next_poll_after_seconds wins when it asks for a longer gap.
signalnoneAborts the wait and the in-flight request.
onStatusnoneCalled on every status read, including the terminal one.

What does the code look like?

Submit with plain fetch, then wait. The body is the same one the avatar video guide shows; the avatar handle must already be ready.

import { createSumeClient, waitForJob, SumeJobTimeoutError } from "@sume-com/sdk";

const client = createSumeClient({ apiKey: process.env.SUME_API_KEY! });

const res = await fetch("https://api.sume.com/v1/avatar-1.0/talking-video", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.SUME_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "avatar-video-launch-001",
  },
  body: JSON.stringify({
    avatar_handle: "sume_clawra",
    script: "Say hello to the Sume developer platform.",
    mode: "async",
  }),
});
const submitted = await res.json();
const jobId = submitted.data.request_id;

try {
  const job = await waitForJob(jobId, {
    client,
    onStatus: (status) => console.log(status),
  });
  console.log(job.status, job.result);
} catch (err) {
  if (err instanceof SumeJobTimeoutError) console.log("still running:", err.jobId);
  else throw err;
}

What happens when the wait times out?

A SumeJobTimeoutError carries the jobId and nothing else changes: the job keeps running and keeps billing. The SDK page says to store the job id and read it back later with getApiJob, or cancel it with cancelApiJob.

Cancel is not a refund button. Cancellation succeeds only before generation work starts; after that the API returns 409 job_generation_already_started with details.cancelable: false and the job runs to completion.

Never answer a timeout by submitting a second avatar video for the same intent. If you must retry the submit itself, reuse the same Idempotency-Key so the retry returns the original job instead of a second charge.

Terminal is not the same as successful

waitForJob resolves for any terminal status: completed, failed or canceled. Check job.status before you read job.result, and read job.error for the other two.

If you would rather not hold a process open for minutes, use mode: "webhook" and keep polling status_url as a backup. See the sync-mode post for why the server-side wait is the wrong tool for video.

Which errors can waitForJob throw?

The SDK page names two: SumeJobTimeoutError when the client-side timeout passes, and SumeJobRequestError when a request in the wait fails. Both carry jobId, which is the one thing you must persist before anything else goes wrong.

Treat the two differently. A timeout says nothing about the render; the job is still queued or running. A request error says your read failed, which can be a transient 429 or 503; the docs call those transient inside a poll loop and say abandoning the loop does not stop the run or its spend. Back off and read again with the stored id.

If you want the wait to stop for your own reasons, such as a user closing a tab, pass an AbortSignal. It aborts the wait and the in-flight request, and it does not touch the job.

How do I size the timeout for a render?

Sume does not publish a fixed render time for avatar videos, and the quality tier changes it: standard is the fastest execution path, plus is the default balanced path, and max is the highest quality with slower turnaround. Use the 20 minute default as your starting point and widen it only if you routinely submit max at the top of the 60 second window.

Short avatar jobs, such as creating an avatar from a prompt, are job-backed too, so the same helper waits on them. Pick one wait helper per job family and put it in one module, so a timeout policy lives in one place.

Finally, log onStatus snapshots at debug level. The snapshot carries next_action, which is the quickest way to see what Sume wants a client to do next when a job looks stuck.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume