PricingAPI keys
  • Overview
  • Quickstart
  • Authentication
  • Overview
  • Format API
  • Create a run
  • Runs and results
  • Bulk runs
  • Editing a Format package
  • Structured output
  • Format catalog
  • Errors and spend
  • Cookbook
  • Embed a Format in your product
  • Waiting for runs and jobs
  • Agents
  • Agent Completions
  • Scheduled
  • Create a schedule
  • Advanced: run a schedule via API
  • Runs and results
  • Run webhooks
  • Safe automation
  • Best practices

Enterprise

Waiting for runs and jobs

The SDKs wait for generation jobs for you. Format, Action and Agent runs are waited on over HTTP, with the loop on this page. Prefer webhooks in production.

Sume has two kinds of asynchronous work, behind two URL families:

KindMade byRead atWait with
JobThe Image Router, the Video Router and the other generation routes/v1/jobs/{id}subscribe in @sume-com/client or sume-client
RunFormats, Actions and Agent Completions/v1/format-runs/{id}, /v1/action-runs/{id}, /v1/agent-runs/{id}The HTTP loop below, or a run webhook

A job id is never a run id, and a run id is never a job id. There is no SSE event stream today: waiting means reading the status again until it is terminal.

Generation jobs: subscribe

subscribe submits the job and waits for it:

ts
import { createSumeClient } from "@sume-com/client";

const sume = createSumeClient({
  baseURL: "https://api.sume.com",
  maxPollTime: 20 * 60_000,
});

const response = await sume.subscribe("/v1/video-router/generate", {
  input: {
    model: "seedance-2.5",
    prompt: "Slow push-in on a ceramic mug",
    duration: 5,
    resolution: "480p",
  },
});

console.log(response.status, response.video?.url);
python
import sume_client

sume = sume_client.SyncClient(base_url="https://api.sume.com")

result = sume.subscribe(
    "seedance-2.5",
    arguments={
        "prompt": "Slow push-in on a ceramic mug",
        "duration": 5,
        "resolution": "480p",
    },
)
  • Both poll /v1/jobs/{id}/status every 2 seconds and read /v1/jobs/{id}/result when the job completes.
  • The TypeScript client gives up after maxPollTime (5 minutes unless you set it) and throws TimeoutError. Its requestId is the job id, and the job keeps running and still bills. Set maxPollTime to the longest job you expect. Video usually takes minutes.
  • The Python client waits until the job ends.

Already have a job id

python
status = sume.status("job_7513b52d-3669-4ffe-b317-8b02000715eb")
result = sume.result("job_7513b52d-3669-4ffe-b317-8b02000715eb")
sume.cancel("job_7513b52d-3669-4ffe-b317-8b02000715eb")

result() waits until the job ends, like subscribe. In TypeScript, read the job over HTTP: GET /v1/jobs/{id}/status until terminal is true, then GET /v1/jobs/{id}/result. Jobs and results has the fields and the loop.

For a failed or canceled job, /result answers 409 job_not_completed. Read status and error from /v1/jobs/{id} instead.

Format, Action and Agent runs

The SDKs have no run calls. Create the run with one POST, then read its receipt until the status is terminal:

ts
const API = "https://api.sume.com";
const headers = {
  Authorization: `Bearer ${process.env.SUME_API_KEY}`,
  "Content-Type": "application/json",
};

const created = await fetch(`${API}/v1/formats/acme/product-promo/runs`, {
  method: "POST",
  headers: { ...headers, "Idempotency-Key": "order-8823-promo-v1" },
  body: JSON.stringify({
    input: { product_url: "https://shop.example.com/p/8823" },
    generation_spend_cap_credits: 300,
  }),
});
if (!created.ok) throw new Error(JSON.stringify(await created.json()));
let run = (await created.json()).data;

let wait = 5_000;
while (run.status === "queued" || run.status === "processing") {
  await new Promise((resolve) => setTimeout(resolve, wait));
  wait = Math.min(wait * 2, 60_000);
  const read = await fetch(`${API}/v1/format-runs/${run.id}`, { headers });
  if (read.status === 429 || read.status >= 500) continue;
  if (!read.ok) throw new Error(JSON.stringify(await read.json()));
  run = (await read.json()).data;
}

console.log(run.status, run.primary_output_url, run.error);
python
import os
import time

import httpx

API = "https://api.sume.com"
headers = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}

created = httpx.post(
    f"{API}/v1/formats/acme/product-promo/runs",
    headers={**headers, "Idempotency-Key": "order-8823-promo-v1"},
    json={
        "input": {"product_url": "https://shop.example.com/p/8823"},
        "generation_spend_cap_credits": 300,
    },
)
created.raise_for_status()
run = created.json()["data"]

wait = 5
while run["status"] in ("queued", "processing"):
    time.sleep(wait)
    wait = min(wait * 2, 60)
    read = httpx.get(f"{API}/v1/format-runs/{run['id']}", headers=headers)
    if read.status_code == 429 or read.status_code >= 500:
        continue
    read.raise_for_status()
    run = read.json()["data"]

print(run["status"], run.get("primary_output_url"), run.get("error"))

Action and Agent Completion runs use the same loop at /v1/action-runs/{id} and /v1/agent-runs/{id}. Runs and results documents each field of the receipt.

Keep the loop correct:

  • Send an Idempotency-Key on the create. A retried create with the same key returns the same run. Without one, a retry starts and bills a second run.
  • Back off. Long-form video is 15 to 30 minutes of work. Double the gap, up to one minute.
  • A 429 or 5xx on a read is the read failing, not the run. The run still executes and still spends. Wait and read again.
  • Use expires_at as your ceiling. A non-terminal receipt names the deadline after which Sume finalizes the run as failed.
  • Stopping your loop does not cancel the run. Store the run id. Read it later, or cancel it with POST /v1/format-runs/{id}/cancel.

Watching phases while you wait

status tells you that a Format run is processing. The phase timeline tells you what it is doing: preparing, running, finalizing, each with a timestamp and a status. Read events_url from the receipt:

ts
const events = await fetch(run.events_url, { headers });
const { data } = await events.json();

It is a phase timeline, not a log stream: Sume never publishes agent output, tool calls or sandbox internals. Refer to Watch a run progress. Action and Agent runs report events_url: null.

Terminal is not the same as successful

A run is terminal at completed, failed, canceled or skipped. A failed run is a result that you asked for, not an exception. Read status and error from the receipt, the same as a webhook handler does:

ts
switch (run.status) {
  case "completed":
    return attachOutputs(run);
  case "failed":
    return showError(run.error);
  case "canceled":
  case "skipped":
    return noop();
}

skipped means that another run was in flight and you sent on_active_run: "skip". Format runs allow concurrency by default.

Prefer a webhook where you can

A poll loop holds one timer and one open request for each run in flight, and runs usually take minutes. Run webhooks deliver the same receipt without that cost, and Verifying webhooks is the receiver's half. Keep the loop as the fallback.

Next

  • Verifying webhooks — the receiver check for the push path
  • Runs and results — each field of the receipt
  • Bulk runs — queue many runs, then poll GET /v1/format-run-queues/{id}
  • Embed a Format in your product — the whole partner integration

On this page

  • Generation jobs: subscribe
  • Format, Action and Agent runs
  • Watching phases while you wait
  • Terminal is not the same as successful
  • Prefer a webhook where you can
  • Next