Timeline transition_too_long: 1 s cap and half the shorter clip
transition_too_long means a Timeline fade is over 1 s or over half the shorter neighbour. Sub-frame fades fail too. Limits, snapping, and a check.

A Timeline 1.0 transition must be 1.0 s or shorter and no more than 50% of the shorter of the two slots it joins. Anything longer returns 400 transition_too_long, and the error carries a max_seconds value so you can retry with it. A fade shorter than one output frame returns transition_not_frame_aligned instead of silently turning into a hard cut.
What are the exact limits?
These come from the Timeline 1.0 page and the API source (apps/api/src/timeline.ts). The cap is min(1, 0.5 * min(previous.duration, current.duration)). A 1.2 s slot next to an 8 s slot therefore allows at most 0.6 s, while two 8 s slots allow the full 1 s.
Fades are rendered as a whole number of frames. A duration that is off the frame lattice is snapped, and the job still succeeds with a transition_snapped_to_frame warning that reports the requested and applied seconds. At 24 fps, 0.3 s is 7.2 frames and renders as 7 frames (0.292 s).
| Rule | Value | Result when broken |
|---|---|---|
| Longest transition | 1.0 s | transition_too_long |
| Share of the shorter neighbour | 50% | transition_too_long |
| Shortest transition | at least one output frame | transition_not_frame_aligned |
| Consecutive transitions | 8 without a hard cut | too_many_chained_transitions |
| On the first slot | not allowed | transition_on_first_segment |
| Types | fade, wipeleft, wiperight, slideup, slidedown, dissolve | schema error |
Why is it stricter than FFmpeg's xfade?
The FFmpeg filters documentation lets xfade duration run from 0 to 60 seconds with a default of 1 second. Sume narrows that, because a long fade eats the clips on both sides and a chain of long fades leaves the chunk planner nowhere to split. The source comment on the chained-fade cap says as much: offsets accumulate in one graph.
When the frame rate is omitted from output.fps, the API checks the fade against the slowest allowed rate, 24 fps, because the real rate is only known from the sources at render time. Sending at least 1/fps seconds is the safe floor.
How do I choose a legal duration in code?
Clamp before you build the request. The helper below returns the largest legal fade for a pair of slots and tells you whether a requested fade survives as at least one frame. It runs with the standard library only.
If a fade is too long for a short slot, cut to a hard cut by omitting transition on that slot, or lengthen the slot's duration. A hard cut is always valid; a run of more than 8 fades is not.
def max_transition(prev_len, cur_len):
return min(1.0, 0.5 * min(prev_len, cur_len))
def min_frames_ok(seconds, fps):
return round(seconds * fps) >= 1
print(max_transition(8, 1.2)) # 0.6
print(max_transition(8, 8)) # 1.0
for fps in (24, 25, 30, 60):
print(fps, min_frames_ok(0.02, fps), min_frames_ok(1 / fps, fps))Where do I see the clamp before paying?
Send the document to POST /v1/timeline-1.0/plan first. It is unbilled, runs the same checks, and returns the refusal with its next_action (shorten_transition, use_frame_aligned_transition or insert_hard_cut). Then submit the render with an Idempotency-Key, so a retried request does not queue a second job.
What if I want a longer dissolve?
Lengthen the neighbouring slots, not the fade. Because the cap is half the shorter side, two slots of 2 s or more both allow a full 1 s fade, and 1 s is the ceiling for every type. If a design needs something slower, a still held for a few seconds between two clips gives each fade room, and fade_in_seconds and fade_out_seconds on output (0 to 5 s each) cover the opening and closing edges without touching the slot rules.
When a fade is clamped by the API rather than refused, you will see it in the warnings of the finished render. Treat those warnings as a to-do list: each one names the slot and the applied value, so fixing the source document is a small edit.
How does the plan call help here?
The plan response is the cheapest place to learn a fade is too long. It returns the refusal body, including max_seconds, without a job, without reserving credits and without downloading any media. A common pattern is to run the plan, read the cap, clamp the value in your builder, and only then send the render with an Idempotency-Key.
Because the plan runs the same compiler checks as the render, a document that plans cleanly will not fail on transitions later. The warnings that remain are the ones that depend on the real files, such as a source shorter than its slot.
Sources
Related posts
More in Developers
- Transactional outbox for paid API calls in Python (Sume)
Write the Sume request and its Idempotency-Key in the order's transaction, drain later: a tested Python outbox that survives crashes, 429s and 409s.
- Transcribe a three-hour recording when the API caps at ten minutes
Streaming sessions end at an hour; Sume STT jobs take up to 600 seconds. Split with Timeline audio, transcribe 18 chunks, and stitch word times back together.
- Translate an SRT and burn it in: Sume caption cues, limits, Python
Sume takes no SRT upload, but caption cues take the same text and times. A Python converter, the 200-cue and 60-second limits, and which fonts apply.
- Trigger.dev 4.6.3 error cause chains: log the Sume code and request id
Trigger.dev v4.6.3 shows thrown error cause chains in the dashboard, CLI and alerts. Wrap a failed Sume call so the cause carries error.code and request_id.
Written by Sume