Compose 400: stack and overlay layout keys cannot be mixed
Fix compose_stack_takes_no_overlay_layout and its mirror: pick stack or overlay as operation, then send only the layout keys that belong to it.

Timeline compose returns compose_stack_takes_no_overlay_layout when a stack request carries an overlay key, and compose_overlay_takes_no_stack_layout when an overlay request carries a stack key. Choose the operation first, then send only the layout keys that belong to it; the table below lists which are which.
The rules are from the Timeline compose page, read 2026-10-10. Compose puts one still and one video on screen together and returns one MP4 for $0.02 flat per job (confirm in GET /v1/catalog).
Which layout keys belong to which operation
The field that picks stack or overlay is operation. It is not mode, which is the usual async or sync communication option, and mixing the two up is the other common first mistake.
| `layout` key | `stack` | `overlay` |
|---|---|---|
split | horizontal or vertical | Not permitted |
image_region | top or bottom on a horizontal split; left or right on a vertical split | Not permitted |
ratio | The still's share of the frame, 0.1 to 0.9 | Not permitted |
image_fit and video_fit | cover, contain or stretch | video_fit only |
position | Not permitted | top, center or bottom |
width_ratio | Not permitted | 0.05 to 1 of width, default 0.9 |
margin_ratio | Not permitted | 0 to 0.45 of height, default 0.05 |
Why a copied layout object fails
The usual cause is reuse. A team builds a half-banner with split, image_region and ratio, then flips operation to overlay to get a price card on the clip and keeps the old layout object. The API answers 400 with compose_stack_takes_no_overlay_layout or its mirror image, because the vocabulary is separate on purpose: a stack divides the frame between two regions, while an overlay places a plate on top of a full-frame video.
A related error is compose_image_region_wrong_axis: left or right on a horizontal split, or top or bottom on a vertical one. The region must match the split, so a vertical split pairs with left or right.
- Stack defaults are
horizontal,topand0.5: still on top, video below, the half-banner shape. ratiois the still's share; the video takes the exact remainder.bluris not a compose fit. It is a Timelinevideo[].fitvalue, so a compose request using it is not valid.- Overlay plates keep their own aspect ratio at the chosen
width_ratio.
A valid overlay request
The sample puts a price card near the bottom of a clip, 80% of the width, with a 5% margin. It carries only overlay keys. Both URLs must already be media.sume.com files in your workspace (import them first), image.url must probe as a still and video.url as a video, and Idempotency-Key is required.
curl -X POST https://api.sume.com/v1/timeline-1.0/compose \
-H "Authorization: Bearer $SUME_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: compose-overlay-001" \
-d '{
"operation": "overlay",
"image": {"url": "https://media.sume.com/artifacts/artf_demo/price-card.png"},
"video": {"url": "https://media.sume.com/artifacts/artf_demo/talk.mp4"},
"layout": {"position": "bottom", "width_ratio": 0.8, "margin_ratio": 0.05}
}'What stays the same either way
The output length always comes from the video layer: video.duration, or the rest of the file from source_in. The still stays on screen for the whole clip and can never make it longer. The ceiling is 300 seconds, and a duration past the end of the source is clamped with the warning compose_duration_clamped_to_source, which does not fail the job.
The default output is 1080 by 1920 MP4 at the video's frame rate. A silent video gives the warning compose_video_has_no_audio; it still renders, and a Timeline spine can supply sound at assembly. Put the finished MP4 into Timeline 1.0 video[] as a normal slot.
A quick checklist before you submit
Read the request top to bottom: pick operation, then check every layout key against the table, then confirm the still is a still and the video is a video. Keys such as filtergraph, ffmpeg_args, codec and crf are refused with a 400, so there is no way to hand-place a plate with a raw filter.
When the same price card must go on many clips, keep two small layout objects in your code, one per operation, and build requests from the one that matches. That removes the copy-and-flip mistake, and each job is a flat $0.02 whichever operation you choose, so a batch of 100 overlays costs $2.00 before any retries.
Sources
Related posts
More in Developers
- Timeline 400: first slot must start at 0 and take no transition
Fix timeline_must_start_at_zero and transition_on_first_segment: start video[0] at 0, use source_in to skip, put fades on slot two, then plan for free.
- TTS output_format: choose wav or mp3 for joins, captions and clips
Sume TTS returns mp3 by default. Choose wav when you will join, slice per sentence or feed lip-sync; mp3 is smaller but adds padding at every edge.
- Twenty image jobs, one webhook: mode webhook plus a Python verifier
Submit 20 image requests with mode webhook, receive signed job.completed callbacks, and verify them in Python. Retries, replay window and the poll fallback.
- Unit-test a Sume job poll loop with a fake clock and no network
Inject the status reader and the sleep function to test a Sume poll loop in milliseconds: next_poll_after_seconds, backoff fallback and the client deadline.
Written by Sume