Typed try-on results: output_schema with a video URL and your SKU
Bind an output_schema to a Sume try-on run to get the video as a typed field, and keep your SKU outside it because identifiers do not round-trip.

Add an output_schema with a SumeMediaFile# field and a primary_output_key to the run, and the receipt returns a typed object with the video URL in it. Do not ask the schema to carry your SKU: on the projection path, identifiers you send in input do not come back, so key the SKU by the run id or your Idempotency-Key on your side.
A try-on button for a catalog turns each tap into a database row. You need the clip URL, a status and the SKU in one place, and this is the shape that gives it to you.
Two paths, and why filled_by matters
A bound schema is filled one of two ways. With filled_by: "agent" the run itself submits your object before finishing and can see your input and instruction. With filled_by: "projection", a separate constrained pass builds the object afterwards from only two facts: the run's generated media and the first 8000 characters of its closing text. It never sees your input, your instruction or the Format body (read 2026-10-03, Sume structured output docs).
Two consequences follow. First, require only what the Format actually makes: a schema that demands a field the recipe never produces will fail on the projection path every time. Second, an empty text field with filled_by: "projection" is a signal that the run stopped early, not that your schema is wrong.
| Field | Type | Keep or move |
|---|---|---|
tryon_video | SumeMediaFile# | Keep: the run makes it |
caption | string | Keep only if the run writes one |
sku | string | Move to your database, keyed by run id |
shopper_id | string | Move: never goes through the schema |
Receipt filled_by | agent or projection | Read it before counting a run delivered |
A schema and a run
strict schemas are limited to a supported subset, and an unsupported shape is 400 output_schema_invalid with nothing run and nothing charged. A single required media field is well inside the subset.
{
"instruction": "Vertical 9:16 try-on clip of image 1 wearing image 2. No captions.",
"attachments": [
{"type": "input_image", "image_url": "https://cdn.example.com/people/77.jpg"},
{"type": "input_image", "image_url": "https://cdn.example.com/sku/1042.jpg"}
],
"output_schema": {
"name": "shop/tryon/v1",
"strict": true,
"schema": {
"type": "object",
"additionalProperties": false,
"required": ["tryon_video"],
"properties": { "tryon_video": { "$ref": "SumeMediaFile#" } }
}
},
"primary_output_key": "tryon_video"
}Keeping the SKU on your side
Post this body to /v1/formats/sume/sume-virtual-try-on/runs with Idempotency-Key: tryon-77-1042-v1, store data.id in a table next to the SKU and shopper, and read the run when the signed format.run.terminal webhook arrives. If the webhook receipt is larger than 1 MiB its payload is null, so fetch by id instead of trusting the body.
Then branch on the receipt. On success, write output.tryon_video.url to your row. On failure, primary_output_url is null and the run carries a code such as output_schema_unsatisfied or deliverable_missing; log it and do not show a result. For continuing a run, see the previous_run_id post, and for a whole catalog the bulk post.
The image attachments are separate from the schema: up to 30 images per run are accepted, as the attachments post explains.
Gates and failure modes to expect
Whatever path fills the object, it is gated before it reaches you, and nothing in output is invented. If the gate fails, the receipt says why in output_error and primary_output_url is null, so your handler should treat a missing URL as a failed try-on, show the shopper a retry button and log the code. Do not retry in a tight loop; each retry is a new run unless you reuse the same Idempotency-Key, which replays the original receipt.
If you do want a retry to produce a fresh run, bump the version in the key, as the double-click post explains. Keep a short mapping from run id to your row and the key you used, and a dashboard of runs by status will fall out of it for free.
A last schema rule
Keep the schema small and the names stable. Version it in its name, such as shop/tryon/v1, and bump the version when a field changes, because a schema is a contract with your database. Bind the same schema on every turn of a thread, since it is per run and not inherited by a continuation.
Sources
Related posts
More in Developers
- TTS from an accepted script: transcript_source instead of pasted text
Sume TTS can read an accepted script by script_revision_id and sentence_ids, not pasted text. MCP tools tts_source_get and tts_source_verify_spine support it.
- TTS volume 0.5 to 2: set narration gain before mixing with music
Sume TTS 1.0 generation_config.volume runs from 0.5 to 2 alongside speed 0.6 to 1.5. How to set narration level before you mix with a music bed.
- TTS mp3 bit_rate vs wav: fit a voiceover under the 10 MB Fabric limit
Sume TTS mp3 bit rates run 32k to 192k. At 128k a 300-second voiceover is about 4.8 MB, under the 10 MB Fabric audio limit. Mono 16 kHz wav is 9.6 MB.
- TTS word timings to burned-in captions: send them as words on Sume
Sume's TTS can return word start and end times; the caption job accepts words with text, start and end and skips transcription. How to wire them together.
Written by Sume