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:
| Key | Why it is there |
|---|---|
instruction | Decisions: use the script as written, framing, what not to add. Prose, well under 4000 characters. |
input | Data: 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_schema | full_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_key | Makes 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_usd | The ceiling for this run. Production live-commerce runs sit around $120. |
communication.webhook_url | One 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
- Create a run: every field on the body
- Runs and results: the receipt, polling and webhook rules
- Errors and spend: what each code means and what to do
- Embed a Format in your product: key custody, spend tiers and artifact handling for a multi-tenant product