Best practices
One worked example — create a live cut, read a clip receipt, retry one scene on the same thread.
This page gives one worked example for Mobidoo backend integrators. It shows a possible create → receipt → clip-retry loop. This example is not a contract.
Team workspace API key: Team Formats need a team key.
API surface: Live Commerce API.
Full details about input and typed receipts:
Call a Format — what is carried,
Structured output.
1. Example create
The body has an instruction, a convenient input, and the partner
output_schema. This schema makes the receipt typed (full cut and per-scene
clips). The tabs show the same body in cURL / JavaScript / Python.
Example create — AHC 30 s live cut
POST /v1/formats/{handle}/{slug}/runs
What the create body has to get right
Four items in that body are critical. The JSON below is the same body that the tab above sends. The run of this body returned a 34.72 s cut with 7 scenes. We made the long strings shorter:
output_schema is the whole object: { name, strict, schema } with the
JSON Schema inline. The registered name alone is not sufficient. Send it
together with primary_output_key. To make sure that the API accepted it, read
output_schema.source in the create response. When the API uses your schema,
this field shows request_override.
generation_spend_cap_credits must be non-zero. Set it well above your
estimate. A zero or missing cap does not mean "no limit". The run above
finished well under its cap.
input is flat snake_case: product_url, product_name, on_card_name,
host_image_url, vo_language, price, highlights, script.segments. Do
not put these keys under a wrapper key. Do not use camelCase.
Partner ALL-CAPS segment tags work. You can send INTRO / MID / FIN
without a conversion. The receipt returns them in normalized form, as
Intro / Mid / Fin in act and tags. Match on the letter case of the
receipt, not on the case that you sent.
Put each request that has no recognized input key into instruction. The
run above put these requests there: "use the segments verbatim", "do not
lengthen the script", "no BGM, no captions, 9:16".
Script length is the duration control
The API has no duration parameter. The length of the finished cut is the time
that it takes to read the script. Thus, the only control on length is the
quantity of text in script.segments. For Korean copy in this pack, the ratio
was approximately 8.2 characters per second of finished video (283
characters gave a 34.72 s cut). Use this ratio to set the size of a slot before
you write the script. It is a number for plans, not an SLA.
The API returns a run:
Wait for a terminal status. Poll status_url / result_url,
or use the run webhook (format.run.terminal). The API
sends one webhook for each turn, not one for each scene.
When the receipt arrives, make sure that it has the script that you sent. Do
this before you show the receipt to an operator. middle: [] and closing: []
satisfy the schema. Thus, a run that read only part of the script can still
return completed with no error. Compare the returned script text with your
segments. This comparison is a cheap guard.
2. Example receipt
The snippet above is a template for you to complete. All the content from this
point down comes verbatim from a real production run. The run is
arun_42ed6a48b3d44092 on Format v5: a Korean sunscreen cut, 11 scenes,
73.36 s. Thus, each media.sume.com URL below is a live file that you can
open.
We show only 3 of the 11 scene rows. We removed sc_1–sc_3,
sc_5, sc_6, and sc_8–sc_10 to make the receipt shorter. Schema details:
Structured output.
For a “retry this clip” control, store four items from this turn:
| Store | From | Used for |
|---|---|---|
run.id | create response | becomes previous_run_id on the retry |
thread_id | create response | groups all the turns of one production. It is read-only. Never send it in a body |
scene.id | each receipt row | identifies the clip to retry. It does not change during the production. Do not use the array index as the key |
scene.status | each receipt row | stand-in / failed enables the retry button |
Store scene.id as an opaque string. This run returned sc_0 … sc_10.
Older productions on earlier Format versions returned other shapes. Store the
value that the receipt gives you. Do not parse the value, and do not
reconstruct it.
All the scenes in this run returned succeeded. Thus, the retry below is an
operator request for a different take, not a repair. The other trigger is a row
with a retryable status. A dashboard enables “retry this clip” for each of
these two statuses:
These statuses are only examples. This production returned 11/11 succeeded.
3. Example retry
Dashboard loop: an operator selects a clip → your backend already has
previous_run_id (create run id) and scene_id (from the receipt row). Post
to the same Format endpoint. Put that scene_id in input, and send the same
output_schema as create. Then the typed receipt keeps the same shape.
Do not put thread_id in the body. It is a response field. If you send it,
the API rejects the request with unknown_parameter. previous_run_id is the
only continuation key.
The remaining field is instruction. This field controls the result that you
get.
Prefer concrete per-scene feedback
If the operator clicks “retry this clip” because they do not like the take, ask
them why. Put their answer in instruction: which scene, what is wrong with
it, and how to change it. We recommend a retry that has a real note. The fixed
template below is the fallback, not the default.
Write four parts in the instruction. Use the language of the production:
- the scene —
scene_idand role. Write them again in prose to make the note clear. - the observed defect — what the operator saw in this take
- the concrete fix — what to do instead. Write instructions, not adjectives.
- the guardrails — keep the VO spine, script text, host, wardrobe, and set. Keep vertical 9:16, no BGM, no captions.
The two instruction examples below come from a second production on this
recipe. That production is on a different thread from the receipts above. Thus,
their previous_run_id is not the one in §2. We show them as bodies, not
receipts.
The first example is a broll scene. The scene came back as a packshot that
floats in an empty room. The example has a banner rule. On-frame card and banner
typography must be fully in the top 40 % of the frame, because the live UI
covers the bottom.
Example retry — scene note in the instruction
POST /v1/formats/{handle}/{slug}/runs
A talk scene uses the same structure: the same four parts, with a different
defect. In this scene, the host says hello. On some frames, her gaze dropped and
her eyes closed:
The note is important. A retry of a talk scene with the bare template gave a
byte-identical copy of the take that the operator rejected. A retry of the
same scene with a note like the one above re-rendered the scene and fixed the
gaze. This is guidance for integrators, not an API guarantee. The API does not
promise that a bare retry is identical, or that an instructed retry changes.
You control how much the instruction tells the run. §5 measures one of these retries from start to end: eight named fixes on a price card, all of them applied, and six other clips with no change.
The bare template is the mechanical fallback
For a stand-in or failed row, the operator has no defect to describe. The
operator did not see a take to reject. A fixed template is correct
there. It is also the honest default for a plain “roll it again”:
Example retry — selected scene on the same thread
POST /v1/formats/{handle}/{slug}/runs
New run, same thread:
To retry several clips at the same time, use the same call with scene_ids and
the plural instruction. No other part changes:
Clip retry re-renders how a scene looks. A change to what the scene says (words, price read, line length) is a new production with new scene ids. Refer to What clip retry can and cannot change.
Two caveats before you wire the button
Serialize retries on a thread. Do not post two retries from the same
previous_run_id while the first retry still runs. One production thread can
have only one turn in flight. Hold the second retry in a queue until the first
retry reaches a terminal status. To retry two scenes in the same turn, use
scene_ids.
previous_run_id picks the thread, not a snapshot. If you name an older
run, the retry does not branch from that older receipt. The API assembles the
new receipt from the latest scene state of the thread. Thus, if an earlier turn
changed a scene, that scene stays changed. To diff a retry, compare it with the
previous turn's receipt, not with the create.
4. Example retry receipt
This is also a full receipt with the whole opening / middle / closing
list. It is not a patch and not a delta. sc_7 has a new media URL. All the other
scenes keep the exact URL that they had before. The API re-assembles
full_video. We show the same three rows as above, so you can diff them by eye:
This table shows what the two receipts prove, as we measured on this pair:
Create arun_42ed6a48b3d44092 | Retry arun_808e4eb3e6c04cf7 | |
|---|---|---|
thread_id | thr_96012fce-… | same |
| scene rows returned | 11 | 11 — full list, not a patch |
| sibling scene URLs | — | 10 of 11 byte-identical |
sc_7 URL | artf_nlQMSVkb… | new — artf_Hy18xtqP… |
sc_7 duration | 17.76 s | 17.76 s — VO spine did not move |
full_video | 73.360 s, 12,049,030 B | re-assembled, 73.360 s, 12,080,322 B |
| credits used | the full production | about a twentieth of the create |
| wall | 11m08s | 2m53s |
After the retry, the old sc_7 URL still resolves 200. Artifacts from
earlier turns are immutable. Thus, a create receipt that you stored stays
valid. You can keep the two takes and let the operator select one.
A regenerated clip is a new generation, not a re-encode. Each generative part in the frame can drift between takes. In the pair above, the structured overlay facts stayed exactly the same: price, discount label, SPF grade, and set composition. But the decorative on-pack copy in the product art changed its words. Do not promise your operators a pixel-identical frame with one detail fixed. Promise them another take.
5. A shorter pair, measured
§2–§4 show a 73 s, 11-scene production. The same loop on the 34.72 s cut from §1 is also useful. It gives the credits used by a targeted retry next to the credits used by a new run of the full production:
Create arun_206837aa88d34c8d | Instructed retry arun_0c315b373e24433a | |
|---|---|---|
| thread | thr_45c64858-… | same |
previous_run_id | null | arun_206837aa88d34c8d |
| script sent | 283 KO chars, INTRO / MID / FIN | — |
| result | 34.72 s, 7 scenes, all succeeded | 34.72 s, 7 scenes, one moved |
| credits used | the full production | about a tenth of the create |
| wall | 10m 08s | 2m 24s |
The retry body is only the §3 shape. It has previous_run_id,
input: { "scene_id": "sc_5" }, the same output_schema as create, and an
instruction that names the scene, the defect, and the fix. sc_5 was the
final price card. It was an under_banner scene with the product name, the
price, and very little more. Shortened text:
고칠 씬: sc_5 (under_banner / Fin, 클로징 가격 카드, 9.44초).
지금 무엇이 잘못됐는지: 카드에 제품명과 가격만 있고, 이 상품의 핵심 소구점인 자외선 차단 지수(SPF50+ PA++++)가 빠져 있습니다. … "30% 할인"이 카드에서 가장 크게 잡혀 있고, 정작 소비자가 결제하는 금액인 23,030원이 그보다 작게 보입니다. … 제품 이미지 위쪽에 빈 여백이 크게 남아 카드 상단이 비어 보입니다.
어떻게 바꿀지: 제품명 바로 아래에 "SPF50+ PA++++" 한 줄을 배지 형태로 추가해 주세요. … 판매가 23,030원을 카드에서 가장 큰 요소로 만들어 주세요. "30% 할인"은 그보다 작게, 보조 배지로 내려 주세요. 정가 32,900원은 취소선을 유지해 주세요. … 배경 톤, 카드 색감, 제품 사진 구도(박스 1개 + 튜브 2개)는 지금 그대로 두세요.
The note has eight specific requests. Each request is an edit, not an
adjective. After them, the note has a list of items that must not change. The
run applied all eight edits, and no part outside sc_5 changed:
| Row | Changed |
|---|---|
sc_0–sc_4, sc_6 | no — same artifact URL on all six |
sc_5 | yes — artf_psot5zRM…/images.png → artf_GWQTB6n2…/timeline.mp4 |
full_video | yes — re-concat, still 34,720 ms, new bytes |
Diff per-scene URLs, not full_video. The API re-assembles full_video on
each turn. Thus, its URL changes on each turn, also when no clip re-rendered.
That URL alone proves nothing. The per-scene URL is the signal. For talk
scenes, also hash the bytes, because one talk retry returned a byte-identical
clip on a fresh URL.
scene.video is not always a video. In the create receipt, sc_5.video
was a PNG still: "type": "image", "content_type": "image/png". Its width,
height, and duration_ms were all null. Only the
duration_seconds: 9.44 of the row gave the length. After the retry, the same
scene id had an MP4: "type": "video", 720×1280, duration_ms: 9440.
Read type and content_type from each row. Do not assume that a video element
can play the file. Take the length from duration_seconds. Expect that the type
can change across turns on one scene id.
A targeted retry used approximately a tenth of the credits of a re-create here, and took 2m 24s against 10m 08s. When an operator wants a change to one clip, retry that clip. A new run of the full production is the expensive way to get the same fix.
That is the loop. For details, refer to Live Commerce API and Structured output.

