Cookbook

Each recipe on this page has the shape of a real production request. Placeholders replace the data of the customer. Replace acme/live-commerce, the URLs, and the copy with your values. Keep the structure.

Do this setup one time:

A live-commerce run with typed output and a webhook

This is the most frequent production call. It has a product page, a host image, a tagged script, and a schema that names the assembled cut and each scene. It also has 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 does:

KeyWhy it is there
instructionDecisions: use the script as written, the framing, and what not to add. It is prose, much shorter than 4000 characters.
inputData: the recipe reads the keys that it knows (product_url, host_image_url, vo_language, script, price). The other keys go to the run as context. You can put your own reference data (sheet_no) here, but it will not come back in output.
output_schemafull_video is the deliverable. scenes[] gives you each clip with a stable id that you can retry. SumeMediaFile# is the built-in media shape. Each property is necessary. An optional property is a nullable property.
primary_output_keyMakes primary_output_url the assembled cut. If a run fills scenes but not full_video, the receipt is failed, not a false success.
generation_spend_cap_creditsThe ceiling for this run, in credits. A production live-commerce run needs a much larger cap than a single-scene retry.
communication.webhook_urlThe API sends one signed POST when the run ends, thus no poll is necessary. Keep result_url as the backup.

From the 202, store data.id and data.thread_id with your row. Use the first value to get the receipt. Use the second value to group retries.

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

A webhook receiver

The receiver does four steps in this sequence. First, it verifies the signature against the raw bytes. Then it answers 2xx quickly. Next, it dedupes on request_id. Only after these three steps, it acts on outcome. The two versions below handle the oversized-receipt case (payload: null).

Node

This version uses only the Web Request API. Thus, it works in a Next.js route handler, Hono, Workers, or Deno. It uses verifyWebhook from @sume-com/sdk.

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

Python

This version uses FastAPI. It reads the raw body before it parses the JSON.

Two problems occur in each first receiver. First, a framework that parses JSON for you destroys the signed bytes. Thus, read the raw body on this route. Second, a receiver that renders video before it answers uses all of the 10-second attempt budget. Then the API retries the delivery while the receiver works. Thus, record and then answer before you process the event.

You can do a test of the receiver without a real run. POST /v1/webhooks/test-deliveries (or Send test on the dashboard) sends a webhook.test payload to your URL. To replay a real delivery, use POST /v1/format-runs/{run_id}/webhook/redeliver.

Retry one scene on the same thread

A run is one turn of a conversation. To do a clip again, continue that conversation with previous_run_id. Name the scene in the request. The voice track, the other clips, and the script do not change. The API returns the full scene list, re-assembled.

Put the note of an operator in instruction (which scene, what is wrong, how it must change). Keep input.scene_id as the machine-readable pointer. For two scenes at the same time, use "scene_ids": ["sc_7", "sc_9"].

The API returns a new run (arun_…, a 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 other scenes keep their URLs, and the API re-assembles full_video at a new URL. Plan the cap for a single-scene retry as a fraction of the credits of the create. Measured production retries used approximately one twentieth of the credits of the first run. Always send a cap.

A retry is a new take, not a re-encode. The API generates all the generative parts of that scene again. If the look changes, retry the scene. If the words, the host, or the product change, start a new production with new scene ids.

The API refuses the continuation with 400 previous_run_not_resumable when the earlier run left nothing. It refuses with 409 previous_run_not_terminal while the earlier run is still in progress. It refuses with 400 previous_run_format_mismatch if you address a different Format. Refer to 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 progress. When a slot becomes free, the queue starts the next run.

bulk-body.json has two keys. Each item is the same as the body of a single run (the first recipe on this page), one time for each row. On the client side, skip rows that do not have a finished script. There is no empty item.

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

To get the progress, poll the queue, not the children:

The batch shape makes three things clear. The queue has no webhook, because communication.webhook_url is per item. Queue completed means that each item is terminal, not that each item succeeded. Thus, branch on counts.failed and on the output_error of each child. A used Idempotency-Key returns 202 with the old queue, thus mint a new key for each batch. For the full contract, refer to Bulk runs.

Wait for a run without a webhook

If you cannot expose an endpoint (a script, a CI job, a one-off), poll with backoff. Stop when the run gets to a terminal status.

In TypeScript, subscribeFormatRun does the create and the loop in one call. It resolves on all terminal statuses. Thus, a failed run is a result that you branch on, not an exception:

Its default timeout is 20 minutes. For long-form video, increase the timeout. Use expires_at on the receipt as the true ceiling. A timeout does not cancel the run. The run continues, and it continues to use credits. Thus, keep the run id and read the run again at a later time.

Read a Format before you call it

This call helps in a settings screen or a preflight. With it, you can make sure that the address resolves for the key that you hold. You can also see what the Format takes and its cap.

You can get a 404 format_not_found here with a key that you think is right. This almost always means that you need the other key. Team Formats answer only to keys created in the team workspace. A 403 workspace_key_required tells you the same thing, with a more helpful message. You get it when the owner of your key is a member of that team.

Next