Cookbook

Every recipe here is the shape a production integration actually sends, with the customer's data swapped for placeholders. Replace acme/live-commerce, the URLs and the copy with yours; keep the structure.

Set up once:

A live-commerce run with typed output and a webhook

The most common production call: a product page, a host image, a tagged script, a schema that names the assembled cut and each scene, a per-run cap, and a webhook. Keep the body in a file. Real scripts are too long for a shell heredoc.

run-body.json:

What each key is doing:

KeyWhy it is there
instructionDecisions: use the script as written, framing, what not to add. Prose, well under 4000 characters.
inputData: the recipe reads the keys it knows (product_url, host_image_url, vo_language, script, price); the rest rides along as context. Your own bookkeeping (sheet_no) is fine here, but it will not come back in output.
output_schemafull_video is the deliverable; scenes[] gives you every clip with a stable id you can retry. SumeMediaFile# is the built-in media shape. Every property is required; optional means nullable.
primary_output_keyMakes primary_output_url the assembled cut, and turns a run that filled scenes but not full_video into a failed receipt rather than a false success.
generation_spend_cap_usdThe ceiling for this run. Production live-commerce runs sit around $120.
communication.webhook_urlOne signed POST when the run ends, so nothing polls. Keep result_url as the backup.

Store data.id and data.thread_id from the 202 against your row: the first for the receipt, the second to group retries.

When the webhook arrives, payload.output.full_video.url is the show and payload.output.scenes[] the clips. A scene with status: "stand-in" or "failed" is what the retry recipe below fixes.

A webhook receiver

The receiver does four things in order: verify the signature against the raw bytes, answer 2xx fast, dedupe on request_id, and only then act on outcome. Both versions below handle the oversized-receipt case (payload: null).

Node

Framework-agnostic on the Web Request API: a Next.js route handler, Hono, Workers or Deno. Uses verifyWebhook from @sume-com/sdk.

If you cannot use the SDK, the check is a dozen lines: HMAC-SHA256 of ${timestamp}.${raw} with your secret, hex-encoded, compared in constant time against the value after sume-v1= in x-sume-webhook-signature, after rejecting a timestamp more than five minutes off. The full function is on Run webhooks.

Python

FastAPI, reading the raw body before any JSON parsing.

Two things bite every first receiver. Frameworks that parse JSON for you have already destroyed the bytes that were signed, so read the raw body on this route. And a receiver that renders video before responding burns the 10-second attempt budget and gets retried while it works, so record, answer, then process.

Test it without a real run: POST /v1/webhooks/test-deliveries (or Send test on the dashboard) fires a webhook.test payload at your URL. Replay a real one with POST /v1/format-runs/{run_id}/webhook/redeliver.

Retry one scene on the same thread

A run is one turn of a conversation. To redo a clip, continue that conversation with previous_run_id and name the scene. The voice track, the other clips and the script stay as they were, and the whole scene list comes back re-assembled.

For an operator's note, put it in instruction (which scene, what is wrong, how it should change) and keep input.scene_id as the machine-readable pointer. Two scenes at once is "scene_ids": ["sc_7", "sc_9"].

What comes back is a new run (arun_…, new receipt, its own webhook) on the same thread_id. output.scenes[] is the full list again: the retried scene has a new URL, the others keep theirs, and full_video is re-assembled at a new URL. Budget a single-scene retry at a fraction of the create (measured production retries ran at roughly a twentieth of the first run's spend) and always send a cap.

A retry is a new take, not a re-encode: everything generative in that scene is re-rolled. Looks change → retry the scene. Words, host or product change → new production, new scene ids.

The continuation is refused with 400 previous_run_not_resumable when the earlier run left nothing behind, 409 previous_run_not_terminal while it is still running, and 400 previous_run_format_mismatch if you address a different Format. See Continue a run.

Batch a sheet with bulk runs

One row of a broadcast sheet becomes one item; the sheet becomes one POST …/bulk-runs. The queue keeps concurrency runs in flight and starts the next as a slot frees.

bulk-body.json is two keys; each item is exactly the body of a single run, the first recipe on this page, once per row. Skip rows with no finished script client-side; there is no empty item.

202 returns a queue (frq_…) with the first concurrency items already running. Keep your own sheet-row ↔ index map: items[i].index is the position you submitted.

Poll the queue, not the children, for progress:

Three things the batch shape makes obvious. The queue has no webhook: communication.webhook_url is per item. Queue completed means every item is terminal, not that every item succeeded, so branch on counts.failed and each child's output_error. And a spent Idempotency-Key returns 202 with the old queue, so mint a fresh one per batch. Full contract: Bulk runs.

Wait for a run without a webhook

When you cannot expose an endpoint (a script, a CI job, a one-off) poll with backoff and stop on the terminal status.

In TypeScript, subscribeFormatRun is the create and the loop in one call. It resolves on any terminal status, so a failed run is a result to branch on, not an exception:

Its default timeout is 20 minutes; raise it for long-form video, and use expires_at on the receipt as the honest ceiling. A timeout does not cancel the run. It keeps running and billing, so keep the run id and read it back later.

Read a Format before you call it

Useful in a settings screen or a preflight: confirm the address resolves for the key you hold, what the Format takes, and its cap.

A 404 format_not_found here with a key you believe is right almost always means the other key: team Formats answer only to keys created in the team workspace. A 403 workspace_key_required means the same thing, said more helpfully, when your key's owner is a member of that team.

Next