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.

6 min readSume
All posts

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.

Schema choices for a try-on run, read 2026-10-03
FieldTypeKeep or move
tryon_videoSumeMediaFile#Keep: the run makes it
captionstringKeep only if the run writes one
skustringMove to your database, keyed by run id
shopper_idstringMove: never goes through the schema
Receipt filled_byagent or projectionRead 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

All Developers posts

Written by Sume