Timeline invalid_transition_type: why xfade is rejected (use fade)
transition.type accepts fade, wipeleft, wiperight, slideup, slidedown and dissolve. xfade is the internal ffmpeg name, so it returns invalid_transition_type.

invalid_transition_type is a 400 from Timeline 1.0 when video[].transition.type is not one of fade, wipeleft, wiperight, slideup, slidedown or dissolve. If you sent xfade, the message says so directly: xfade is the internal ffmpeg filter name, so use fade or another listed value. It is a validation error, retryable only after you change the body.
What the body returns
The error carries next_action: use_supported_transition_type, the field that failed, the value you received and the allowed list, so a client can show the choices to the user.
| Rule | Value |
|---|---|
| Allowed types | fade, wipeleft, wiperight, slideup, slidedown, dissolve |
| Slots that may have one | Only slots after the first |
| Maximum duration | 1 second |
| Relative limit | At most 50% of the shorter neighbor |
| Minimum | At least one output frame |
| Chained fades | More than 8 adjacent fades is too_many_chained_transitions |
Other transition refusals are separate codes
A valid type can still be refused. transition_on_first_segment means video[0] has a transition. transition_too_long and transition_not_frame_aligned compare the duration with the neighbors and the frame rate. too_many_chained_transitions asks for a hard cut. Keep these apart in your error handler, because invalid_transition_type is about the name, while the others are about timing.
Normalize names before you send
If your editor exports ffmpeg names, translate them once at the edge:
ALLOWED = {"fade", "wipeleft", "wiperight", "slideup", "slidedown", "dissolve"}
ALIASES = {"xfade": "fade", "crossfade": "fade"}
def transition_type(name: str) -> str:
name = ALIASES.get(name.lower(), name.lower())
if name not in ALLOWED:
raise ValueError(f"{name!r} not in {sorted(ALLOWED)}")
return name
print(transition_type("xfade"), transition_type("WipeLeft"))Why the allowed list is short
The public type is a small, stable set on purpose. xfade is one filter inside the worker's ffmpeg graph, and the docs keep ffmpeg arguments, filtergraphs and codec settings out of the public contract: keys like filtergraph, ffmpeg_args, codec and crf are rejected with a 400. That keeps your integration independent of the renderer's internals, and it lets the renderer change without breaking your calls.
When you need a look that the six types cannot give, cut instead: a hard cut is always valid, and it avoids the chained-fade limit entirely.
Testing your presets
If you ship transition presets to users, test each preset name through the plan endpoint in CI. It takes one request per preset and costs nothing, and it catches a rename before a user does. Keep the alias map in one module, so that a new editor export format only needs one new line.
Check with the plan call
POST /v1/timeline-1.0/plan validates the schema and compiles the program without a job, credits or media download, so a bad transition fails at no cost. The Timeline docs say the compiler compensates for the overlap that a fade creates and never pre-shifts your declared starts, so you do not need to subtract anything yourself.
The full table is on the Timeline page.
Sources
Related posts
More in Developers
- Is Luma Ray3.2 on Sume? Check the video catalog ids in Python
Ray3.2 is not a Sume model. Rather than trust a blog, list GET /v1/video-router/models and test for an id. A short Python script that prints every id.
- Java 21: poll many Sume jobs with virtual threads and a Semaphore
Poll 50 Sume jobs from one Java 21 file using virtual threads, a Semaphore cap of 8, and java.net.http. No Maven, no dependencies, runs with java Poll.java.
- Java ImageIO.read returns null on a Sume WebP: request PNG
ImageIO.read gives null when no reader claims the stream. Check the reader list, then pin output_format to png or jpeg on a Sume /v1/images call.
- Start the trim step from a job.completed webhook: Python verifier
Let Sume's signed job.completed webhook trigger the next chain step. A stdlib Python verifier that refuses an empty secret, plus dedupe on job_id.
Written by Sume