subscribeFormatRun on a failed run: it resolves, it does not throw
subscribeFormatRun resolves with status failed instead of throwing; it only throws when create is refused or the wait times out. How to branch in TypeScript.

subscribeFormatRun from @sume-com/sdk resolves for every terminal status, including failed, canceled and skipped. A failed run is a result you asked for, not an exception, so a try/catch around the call will not catch it. The helper throws only when the create call is refused, when a status read fails and is not transient, or when its own timeout elapses.
This page follows Waiting for runs and jobs for @sume-com/sdk@0.2.0, read 2026-10-02, with failure codes from Errors and spend.
What does it throw, and when?
Unlike the generated operations, which resolve with { data, error }, these helpers throw because a poll loop has nowhere to put a non-result.
| Error | When | Carries |
|---|---|---|
| SumeRunTimeoutError | The timeout elapsed first | runId and lastStatus |
| SumeRunRequestError | The create was refused, or a status read failed and was not transient | runId (or "(not created)") plus the SumeApiError fields |
| The signal's reason | You aborted the wait | Whatever you passed to abort() |
How do I branch on the result?
Read status and error off the receipt exactly as a webhook handler would. completed carries the output, failed carries error (with codes such as unattended_blocked whose message is written to be shown), and canceled and skipped need no output. skipped means a run was already in flight and you passed on_active_run: "skip"; Format runs allow concurrency by default.
import { createSumeClient, subscribeFormatRun } from "@sume-com/sdk";
const client = createSumeClient({ apiKey: process.env.SUME_API_KEY! });
const run = await subscribeFormatRun({
client,
path: { handle: "acme", slug: "product-promo" },
body: { input: { product_url: "https://example.com/p" } },
});
switch (run.status) {
case "completed":
console.log(run.primary_output_url);
break;
case "failed":
console.error(run.error?.code, run.error?.message);
break;
default:
console.log("no output:", run.status);
}Why is a create refusal a throw?
There is no run to wait for. A 403 workspace_key_required on a team Format called with a personal key, a 402 insufficient_credits, or a 400 invalid_request all mean nothing ran and nothing was charged. The helper throws SumeRunRequestError, which extends SumeApiError, so the envelope is available as typed fields: status, code, requestId, retryable, retryAfterSeconds, nextAction and details.
The run helpers always throw SumeRunRequestError itself, never the subclasses such as SumeInsufficientCreditsError, so branch on its status or code rather than instanceof against a subclass.
What should I do after a timeout?
SumeRunTimeoutError does not mean the run failed. The run is still going and still spending, and nothing was lost: store error.runId and error.lastStatus, then read the run later from its result_url. The default timeout for subscribeFormatRun is 20 minutes, longer than waitForRun's 10, because video Formats routinely run 10 to 20 minutes.
For a failed run, remember what the error page says: retry with a new Idempotency-Key, since the old one is bound to the receipt you already hold, and prefer continuing the run with previous_run_id when it left clips behind so finished work is not regenerated.
Common mistakes
Most bugs here come from treating the helper like a promise that rejects on any bad outcome.
- Wrapping the call in
try/catchand assuming thecatchcovers failed runs. - Reading
outputwithout checkingstatusandoutput_errorfirst. - Reusing the same
Idempotency-Keyto retry a failed run, which replays the old receipt. - Treating
SumeRunTimeoutErroras a failure and starting a second paid run.
Sources
Related posts
More in Developers
- subscribeFormatRun timeline: true doubles your status reads
Setting timeline: true on subscribeFormatRun reads the phase timeline on every poll, doubling requests. Read budget math, 429 behavior and when to turn it on.
- Sume 503: provider_capacity_exceeded vs provider_not_configured
Sume 503s differ: provider_capacity_exceeded: retry later, same key; provider_not_configured is no hard retries, job_ledger_not_configured is an outage
- Sume API errors: a 13-line function that says retry or fix
Map a failed Sume response to out-of-credit, wait, back off, fix the key, retry with the same key, or fix the request, using the documented error envelope.
- GET /v1/jobs/{id} returns 404 for a job your teammate created
A Sume API key reads only jobs its own member created. Jobs made by a teammate or in another workspace return 404 not_found. Why, and how to read them.
Written by Sume