Developers

Wait for a Sume job or run to finish in the TypeScript SDK

waitForJob, waitForRun, and subscribeFormatRun poll a Sume job or run until it ends. Here are their default timeouts, the errors they throw, and retries.

6 min readSume
All posts

To wait for a Sume job or run to finish in TypeScript, call waitForJob(jobId, { client }) for a generation job, waitForRun(runId, { client, family }) for a Format, Action, or Agent run, or subscribeFormatRun to create a Format run and wait in one call. All three poll, because there is no SSE stream, and they resolve on terminal statuses, failures included, so check status on what they return.

Defaults and errors below come from Sume's Waiting for runs and jobs page and run deadlines from Runs and results, read on 2026-09-26. The Sume TypeScript SDK quickstart covers installing the package.

Which wait helper should I use?

Pick by the id you hold. A job id and a run id are not interchangeable: generation endpoints such as /v1/videos and the Avatar routes create jobs at /v1/jobs/:id, while Formats, Actions, and Agent Completions create runs. Sume jobs vs runs compares the two.

  • waitForRun requires family: "format", "action", or "agent", because a run id does not say which of three URL prefixes it lives under.
  • waitForJob treats pollInterval as a floor: the status payload's next_poll_after_seconds wins when it asks for a longer gap.
  • All three take a signal that aborts the wait and the in-flight request, and an onStatus(status, snapshot) callback for every status read.
Defaults from Waiting for runs and jobs, read 2026-09-26.
HelperWaits onDefault timeoutThrows
subscribeFormatRunA Format run it creates20 minutesSumeRunRequestError, SumeRunTimeoutError
waitForRunAn existing Format, Action, or Agent run10 minutesSumeRunRequestError, SumeRunTimeoutError
waitForJobA generation job at /v1/jobs/:id20 minutesSumeJobRequestError, SumeJobTimeoutError

How do I wait for a video generation job?

Submit, check error, then pass the job id to waitForJob. Per the Sume API reference, POST /v1/videos returns a bare object, not Sume's { data } envelope; its id is the job id, also readable at GET /v1/jobs/{id}/status.

waitForJob resolves with the job record from /v1/jobs/:id, because /result answers 409 job_not_completed for failed and canceled jobs; read status, result, and error off it. Where a submit route takes mode, send async or omit it: sync and subscribe are one server-side wait capped at 30 seconds.

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

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

const { data: video, error } = await createVideoGeneration({
  client,
  headers: { "idempotency-key": "mug-teaser-v1" },
  body: { model: "sume/auto", prompt: "Slow push-in on a ceramic mug" },
});
if (error) throw new Error(JSON.stringify(error));

const job = await waitForJob(video!.id, { client });
if (job.status === "completed") console.log(job.result.artifacts);
else console.error(job.status, job.error);

What if a run takes longer than the default timeout?

subscribeFormatRun waits 20 minutes by default because video Formats routinely run 10 to 20, and waitForRun waits 10. Long-form video is 15 to 30 minutes of work, so a default can elapse while a healthy run is still going.

Pass a longer timeout. The docs advise taking your ceiling from the receipt's expires_at, the deadline past which the run is force-finalized as failed: 90 minutes from created_at, or sooner when the run is older than 25 minutes and has been silent for 10.

  • A timeout throws SumeRunTimeoutError, carrying runId and lastStatus, or SumeJobTimeoutError, carrying jobId.
  • A timeout cancels nothing. The run or job keeps going and still bills. Read it later with getFormatRun or getApiJob, or cancel it with cancelFormatRun, or with cancelApiJob before the job's generation starts.
import { SumeRunTimeoutError, waitForRun } from "@sume-com/sdk";

// created: the 202 receipt from a Format run create
try {
  const run = await waitForRun(created.id, {
    client,
    family: "format",
    timeout: Date.parse(created.expires_at) - Date.now(),
  });
} catch (error) {
  if (!(error instanceof SumeRunTimeoutError)) throw error;
  await markPending(error.runId, error.lastStatus); // still running, still billing
}

Does a terminal status mean the run succeeded?

No. The run helpers resolve for completed, failed, canceled, and skipped; terminal job statuses are completed, failed, and canceled. A failed run is a result you asked for, not an exception, so read status and error as a webhook handler would. skipped means a run was already in flight and you passed on_active_run: "skip"; Format runs allow concurrency by default.

They throw in three cases. SumeRunRequestError means the create was refused, for example 403 workspace_key_required on a team Format called with a personal key, or a status read failed and was not transient; it carries runId, or "(not created)". waitForJob throws SumeJobRequestError instead, carrying jobId. A timeout throws the timeout error, and an abort rejects with your signal's reason.

Will subscribeFormatRun start a second run if I retry?

Not within one call. idempotencyKey defaults to an auto-generated UUID sent as Idempotency-Key, and the client retries a POST only when it carries a key, so its own retries of the create are safe. A replay of a finished run returns immediately. Pass null to send no key.

It does not cover your own code calling again: the Formats docs call a per-request UUID decorative. Pass a key derived from the thing being made, such as your order id plus a version you bump for a deliberate re-run; see idempotency keys for AI video APIs.

What happens on a 429 or 5xx while waiting?

A 429 or 5xx on a status read means the read failed, not the run, which is still executing and spending. The client retries 408, 429, 5xx, and transport failures twice, honoring retry-after, and waitForRun then tolerates six consecutive transient read failures (maxTransientFailures, onTransientError).

  • Polling is one timer and one open request per run in flight. Where you can, take run webhooks and keep the helpers as the fallback.

Sources

Related posts

Written by Sume