How to use Sume's Timeline compose and Timeline audio APIs
Timeline compose puts one still and one video in the same frame as a new MP4. Timeline audio joins or splits Sume-hosted audio into reusable files.

Timeline compose (POST /v1/timeline-1.0/compose) puts one Sume-hosted still and one Sume-hosted video on screen at the same time and returns one MP4 shot. Timeline audio (POST /v1/timeline-1.0/audio) joins or slices Sume-hosted audio into durable media.sume.com files. Both prepare material for a Timeline 1.0 render; neither sequences clips.
The facts below are from the Timeline compose and Timeline audio docs, read on 2026-09-25.
When do I need compose or audio instead of the render?
- Compose builds one shot. Sequential image-then-video is not a compose mode: adjacent
video[]slots on the render already do that. - Timeline audio mints a reusable file. A join needed only inside one render belongs on the render's
audio.parts[]; skip the job. - Both take only URLs already on this workspace's
media.sume.com. Off-host URLs are rejected at admit, so import first withPOST /v1/media-imports(see media inputs and outputs). - Both require
Idempotency-Key. The defaultmodeisasync;mode: "sync"waits up to 30 seconds for a200finished job, otherwise you get202and poll.
How do I put a still and a video in one frame?
Send operation (stack or overlay), image.url, and video.url. The stack-or-overlay field is operation, not mode, which stays the usual async / sync / webhook option. The image must probe as a still (compose_image_not_still) and the video as a video (compose_video_not_video).
Output length always comes from the video layer: video.duration, else the rest of the file from source_in. The still is held for the whole clip and can never lengthen it. The ceiling is 300 s, and a video.duration past the source clamps with the warning compose_duration_clamped_to_source.
Default output is a 1080×1920 MP4 at the video layer's own frame rate. Set output.width / output.height to the timeline you will assemble into, so the shot is not rescaled twice. Audio passes through from the video; a mute video only warns compose_video_has_no_audio, because the Timeline 1.0 spine supplies the audio at assemble time.
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: timeline-compose-001" \
-d '{
"operation": "stack",
"image": { "url": "https://media.sume.com/artifacts/artf_demo/banner.png" },
"video": { "url": "https://media.sume.com/artifacts/artf_demo/talk.mp4" },
"layout": { "split": "horizontal", "image_region": "top", "ratio": 0.5 },
"output": { "width": 720, "height": 1280, "fps": 25 }
}'Which layout keys go with stack and overlay?
stack tiles two regions of one frame. Its defaults, horizontal / top / 0.5, give a half-banner: still on top, video underneath, with the video taking the exact remainder. Mixing stack keys with overlay keys is a 400 (compose_stack_takes_no_overlay_layout / compose_overlay_takes_no_stack_layout).
| `layout` key | `stack` | `overlay` |
|---|---|---|
split | horizontal or vertical | Illegal |
image_region | top / bottom on a horizontal split; left / right on a vertical split | Illegal |
ratio | The still's share of the frame, 0.1–0.9 | Illegal |
image_fit / video_fit | cover, contain, or stretch (blur is not a compose fit) | video_fit only |
position | Illegal | top, center, or bottom |
width_ratio | Illegal | 0.05–1 of width (default 0.9); the plate keeps its aspect |
margin_ratio | Illegal | 0–0.45 of height (default 0.05) |
How do I join or split audio files?
operation: "concat" takes parts[]: 1–20 ordered parts, each { url, source_in?, duration? }. The join is sample-domain, with no re-TTS and no silence at the seams, and parts must share one channel layout. The result (kind: timeline_audio) has one audio_url, duration_seconds, and segments[] (index, start, duration_seconds): the offsets to re-base Timeline 1.0 video[].start against.
operation: "split" takes a top-level url and ranges[]: 1–20 ranges, each { start, end? }, where an omitted end means the rest of the file. Ranges may overlap, and each returned segment has its own audio_url. For many ranges off a talking-head MP4, run audio detach once, then split.
output.format is wav by default (pcm_s16le, sample-exact) or mp3 (smaller; re-adds priming padding at every edge). Keep wav when the file will be joined again or drives lip-sync. Produced audio is ≤ 1800 s.
curl -X POST https://api.sume.com/v1/timeline-1.0/audio \
-H "Authorization: Bearer $SUME_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: timeline-audio-concat-001" \
-d '{
"operation": "concat",
"parts": [
{ "url": "https://media.sume.com/artifacts/artf_demo/line1.wav" },
{ "url": "https://media.sume.com/artifacts/artf_demo/line2.wav", "source_in": 0.1, "duration": 1.8 }
]
}'How do I get the results, and how are they billed?
Neither tool has its own GET route. Poll GET /v1/jobs/:id/status and GET /v1/jobs/:id/result. A compose result is kind: timeline_compose with video_url (a new artf_) and duration_seconds; drop that MP4 into Timeline 1.0 video[]. On the hosted MCP server the flows are timeline_compose → jobs_wait → jobs_result and timeline_audio → jobs_wait → jobs_result. Writes need idempotency_key, and mcp:write under OAuth.
Both are billed flat per job; compose is flat because video.duration may be omitted until the worker probes. There is no provider inference, only worker ffmpeg. Rates are on API pricing; the docs say to confirm them live in GET /v1/catalog.
Why was my compose or audio job refused?
compose_image_region_wrong_axis:left/righton a horizontal split, ortop/bottomon a vertical one.audio_concat_requires_parts,audio_concat_takes_no_url,audio_concat_takes_no_ranges: a concat withoutparts, or with split fields.audio_split_requires_url,audio_split_requires_ranges,audio_split_takes_no_parts: a split missingurlorranges, or carryingparts.audio_range_end_before_start: a rangeend≤start.audio_parts_channel_mismatch: concat parts do not share a channel layout.unsupported_media_source/source_not_found: an off-host or dead URL.400for provider or ffmpeg keys such asfiltergraph,ffmpeg_args,codec, orcrf.
Sources
Related posts
Written by Sume