Mobidoo

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:

StoreFromUsed for
run.idcreate responsebecomes previous_run_id on the retry
thread_idcreate responsegroups all the turns of one production. It is read-only. Never send it in a body
scene.ideach receipt rowidentifies the clip to retry. It does not change during the production. Do not use the array index as the key
scene.statuseach receipt rowstand-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:

  1. the scene — scene_id and role. Write them again in prose to make the note clear.
  2. the observed defect — what the operator saw in this take
  3. the concrete fix — what to do instead. Write instructions, not adjectives.
  4. 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_42ed6a48b3d44092Retry arun_808e4eb3e6c04cf7
thread_idthr_96012fce-…same
scene rows returned1111 — full list, not a patch
sibling scene URLs—10 of 11 byte-identical
sc_7 URLartf_nlQMSVkb…new — artf_Hy18xtqP…
sc_7 duration17.76 s17.76 s — VO spine did not move
full_video73.360 s, 12,049,030 Bre-assembled, 73.360 s, 12,080,322 B
credits usedthe full productionabout a twentieth of the create
wall11m08s2m53s

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_206837aa88d34c8dInstructed retry arun_0c315b373e24433a
threadthr_45c64858-…same
previous_run_idnullarun_206837aa88d34c8d
script sent283 KO chars, INTRO / MID / FIN—
result34.72 s, 7 scenes, all succeeded34.72 s, 7 scenes, one moved
credits usedthe full productionabout a tenth of the create
wall10m 08s2m 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:

RowChanged
sc_0–sc_4, sc_6no — same artifact URL on all six
sc_5yes — artf_psot5zRM…/images.png → artf_GWQTB6n2…/timeline.mp4
full_videoyes — 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.