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:
| Kind | Made by | Read at | Wait with |
|---|---|---|---|
| Job | The Image Router, the Video Router and the other generation routes | /v1/jobs/{id} | subscribe in @sume-com/client or sume-client |
| Run | Formats, 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:
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);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}/statusevery 2 seconds and read/v1/jobs/{id}/resultwhen the job completes. - The TypeScript client gives up after
maxPollTime(5 minutes unless you set it) and throwsTimeoutError. ItsrequestIdis the job id, and the job keeps running and still bills. SetmaxPollTimeto the longest job you expect. Video usually takes minutes. - The Python client waits until the job ends.
Already have a job id
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:
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);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-Keyon 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
429or5xxon a read is the read failing, not the run. The run still executes and still spends. Wait and read again. - Use
expires_atas your ceiling. A non-terminal receipt names the deadline after which Sume finalizes the run asfailed. - 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:
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:
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