EDL (Edit Decision List)
Overview
The EDL (Edit Decision List) system allows you to create complex video editing projects using a JSON-based format. This powerful feature enables professional video editing workflows directly through the API.
What is an EDL?
An EDL is a JSON document that describes how to edit and combine multiple video clips into a final output. Think of it as a programmatic video editor - you define what clips to use, how to trim them, what order to place them, and various effects to apply.
Core Features
Video Operations
- Trim: Cut specific sections from videos using start and end timestamps
- Split: Create multiple clips from a single source video
- Reorder: Arrange clips in any order on the timeline
- Join: Seamlessly concatenate multiple clips together
- Speed: Change playback speed from 0.1x to 100x
Audio Operations
- Mute: Completely silence audio tracks
- Volume: Adjust volume from -20dB to +20dB
- Replace: Swap video audio with an external audio file
Overlay & Text Support
The EDL system now supports adding overlays (logos, watermarks, images) and dynamic text over your videos!
Overlays (Logos & Watermarks)
- Image Overlays: Add PNG/JPG images as overlays
- Position Control: Place overlays anywhere (x/y coordinates or keywords like "center", "top-right")
- Size Control: Set overlay size in pixels or percentages
- Opacity: Control transparency (0.0 to 1.0)
- Fade Effects: Fade in/out for smooth transitions
- Timing: Control when overlays appear and how long they stay
Dynamic Text
- Custom Text: Add any text overlay to your videos
- Positioning: Center, corners, or exact pixel coordinates
- Font Control: Size, color, family, weight
- Styling: Background boxes, outlines, shadows
- Fade Effects: Smooth text fade in/out
- Timing: Control when text appears and disappears
Accepted but not yet applied
The schema accepts five clip properties that the encoder does not act on yet:
transform, crop, filters, transition and freeze.
A timeline using any of them still validates and still runs, but the property has no effect on the output. Validation flags it so you are not left guessing:
{
"valid": true,
"warnings": ["Using experimental features: filters, transition"]
}Treat a warning naming one of those five as "this part of my edit will be ignored". Everything under Video operations, Audio operations, overlays and text is applied.
API Endpoints
Create EDL Job
Create a new video processing job from an EDL.
Endpoint: POST /api/v1/edl
Headers:
Authorization: Bearer YOUR_API_KEY
Content-Type: application/jsonRequest Body:
{
"edl": {
"version": "1.0",
"output": {
"resolution": "1920x1080",
"fps": 30,
"container": "mp4",
"videoCodec": "libx264",
"audioCodec": "aac"
},
"timeline": {
"tracks": [
{
"type": "video",
"clips": [
{
"id": "clip1",
"src": "https://example.com/video.mp4",
"in": 0,
"out": 10,
"startTime": 0
}
]
}
]
}
},
"metadata": {
"name": "My Project",
"description": "Optional project description"
}
}Response: 201 Created
{
"id": "9d1f5c0a-4e7b-4a61-9b2e-8f1c3d5a7e42",
"status": "queued",
"type": "edl",
"webhookUrl": "https://yourapp.example.com/hooks/video",
"createdAt": "2026-09-10T14:08:11.204Z",
"stats": {
"clips": 3,
"duration": 30.5,
"tracks": 1,
"complexity": "simple"
},
"estimate": {
"processingTime": "5 minutes",
"complexity": "simple"
},
"links": {
"self": "/api/v1/jobs/9d1f5c0a-4e7b-4a61-9b2e-8f1c3d5a7e42",
"cancel": "/api/v1/jobs/9d1f5c0a-4e7b-4a61-9b2e-8f1c3d5a7e42"
},
"user": {
"remainingJobs": 97,
"plan": "Indie/Pro"
},
"warnings": ["Track 0 has 2 overlapping clips"]
}An EDL job counts as one job against your monthly quota, however many clips it
contains. It is stored with the preset name edl_custom, and from then on you
read it, cancel it and download it through the ordinary
job endpoints.
warnings is absent when there is nothing to warn about.
Validate EDL
Validate an EDL without processing it.
Endpoint: PUT /api/v1/edl
There is no /api/v1/edl/validate path. Validation is the PUT verb on the
same resource as creation.
Request Body:
{
"edl": { /* Your EDL JSON */ }
}Response: 200 OK, whether or not the timeline is valid. Read valid
rather than the status code.
{
"valid": true,
"stats": {
"clips": 2,
"duration": 15,
"tracks": 1,
"assets": 2,
"complexity": "simple",
"features": []
},
"estimate": {
"processingTime": "3 minutes",
"complexity": "simple"
},
"plan": {
"current": "free",
"canProcess": true
}
}errors and warnings are omitted entirely when empty rather than returned as
empty arrays. stats.features lists any of the five accepted-but-not-applied
properties your timeline uses.
An invalid timeline comes back with the reasons spelled out:
{
"valid": false,
"errors": [
"Too many clips: 8 (max 5 for free plan)",
"timeline.tracks.0.clips.1.out: 'out' must be greater than 'in'"
],
"stats": { "clips": 8, "duration": 92.5, "tracks": 1, "assets": 3, "complexity": "moderate", "features": [] },
"estimate": { "processingTime": "4 minutes", "complexity": "moderate" },
"plan": { "current": "free", "canProcess": false }
}Validation costs nothing and does not touch your quota, so validate before creating whenever the timeline is built by a user.
Creating a job with an invalid timeline returns 400:
{
"error": "EDL validation failed",
"message": "The EDL has validation errors",
"errors": ["Too many clips: 8 (max 5 for free plan)"]
}EDL Structure
Basic Structure
Every EDL must have this basic structure:
{
"version": "1.0",
"output": { /* Optional output settings */ },
"timeline": {
"tracks": [ /* Array of tracks */ ]
},
"metadata": { /* Optional metadata */ }
}Clip Properties
Required Properties
- id (string): Unique identifier for the clip
- src (string): URL to the source video/audio file
- in (number): Start time in source file (seconds)
- out (number): End time in source file (seconds)
- startTime (number): When clip starts on timeline (seconds)
Optional Properties
-
speed (number): Playback speed multiplier
0.5= half speed (slow motion)1.0= normal speed (default)2.0= double speed (fast forward)- Range: 0.1 to 100
-
mute (boolean): Mute audio completely
-
volume (number): Volume adjustment in dB
-6= approximately half volume0= no change (default)+6= approximately double volume- Range: -20 to +20
-
replaceAudio (string): URL to replacement audio file
Examples
Example 1: Simple Trim and Concatenate
Create a 28-second video from three clips:
{
"version": "1.0",
"output": {
"container": "mp4",
"resolution": "1920x1080"
},
"timeline": {
"tracks": [
{
"type": "video",
"clips": [
{
"id": "intro",
"src": "https://example.com/intro.mp4",
"in": 0,
"out": 5,
"startTime": 0
},
{
"id": "main",
"src": "https://example.com/main.mp4",
"in": 10,
"out": 30,
"startTime": 5
},
{
"id": "outro",
"src": "https://example.com/outro.mp4",
"in": 0,
"out": 3,
"startTime": 25
}
]
}
]
}
}Result: 5 sec intro + 20 sec main + 3 sec outro = 28 seconds
Example 2: Speed Control
Slow motion, normal, and fast forward in one video:
{
"version": "1.0",
"timeline": {
"tracks": [
{
"type": "video",
"clips": [
{
"id": "slow_motion",
"src": "https://example.com/action.mp4",
"in": 5,
"out": 10,
"startTime": 0,
"speed": 0.5
},
{
"id": "normal",
"src": "https://example.com/action.mp4",
"in": 10,
"out": 20,
"startTime": 10
},
{
"id": "fast",
"src": "https://example.com/action.mp4",
"in": 20,
"out": 30,
"startTime": 20,
"speed": 2.0
}
]
}
]
}
}Timeline:
- 0-10s: Slow motion (5 seconds at 0.5x = 10 seconds output)
- 10-20s: Normal speed (10 seconds)
- 20-25s: Fast forward (10 seconds at 2x = 5 seconds output)
- Total: 25 seconds
Example 3: Audio Replacement
Replace video audio with background music:
{
"version": "1.0",
"timeline": {
"tracks": [
{
"type": "video",
"clips": [
{
"id": "video_with_music",
"src": "https://example.com/video.mp4",
"in": 0,
"out": 30,
"startTime": 0,
"replaceAudio": "https://example.com/music.mp3",
"volume": -5
}
]
}
]
}
}Example 4: Reordering Scenes
Play scenes out of order:
{
"version": "1.0",
"timeline": {
"tracks": [
{
"type": "video",
"clips": [
{
"id": "scene3",
"src": "https://example.com/movie.mp4",
"in": 60,
"out": 90,
"startTime": 0
},
{
"id": "scene1",
"src": "https://example.com/movie.mp4",
"in": 0,
"out": 30,
"startTime": 30
},
{
"id": "scene2",
"src": "https://example.com/movie.mp4",
"in": 30,
"out": 60,
"startTime": 60
}
]
}
]
}
}Result: Scene 3 → Scene 1 → Scene 2 (90 seconds total)
Example 5: Volume Control
Mix clips with different volume levels:
{
"version": "1.0",
"timeline": {
"tracks": [
{
"type": "video",
"clips": [
{
"id": "loud_intro",
"src": "https://example.com/intro.mp4",
"in": 0,
"out": 5,
"startTime": 0,
"volume": 3
},
{
"id": "quiet_main",
"src": "https://example.com/main.mp4",
"in": 0,
"out": 20,
"startTime": 5,
"volume": -6
},
{
"id": "muted_outro",
"src": "https://example.com/outro.mp4",
"in": 0,
"out": 5,
"startTime": 25,
"mute": true
}
]
}
]
}
}Example 6: Logo Watermark Overlay
Add a logo watermark in the bottom-right corner:
{
"version": "1.0",
"timeline": {
"tracks": [
{
"type": "video",
"clips": [
{
"id": "main_video",
"src": "https://example.com/video.mp4",
"in": 0,
"out": 30,
"startTime": 0
}
]
},
{
"type": "overlay",
"clips": [
{
"id": "logo",
"src": "https://example.com/logo.png",
"startTime": 0,
"duration": 30,
"position": {
"x": "right",
"y": "bottom"
},
"size": {
"width": "10%",
"height": "10%"
},
"opacity": 0.8,
"fadeIn": 1,
"fadeOut": 1
}
]
}
]
}
}Result: 30-second video with logo watermark in bottom-right corner, with smooth fade in/out
Example 7: Title Card with Text
Add animated title text that fades in and out:
{
"version": "1.0",
"timeline": {
"tracks": [
{
"type": "video",
"clips": [
{
"id": "main",
"src": "https://example.com/video.mp4",
"in": 0,
"out": 60,
"startTime": 0
}
]
},
{
"type": "text",
"clips": [
{
"id": "title",
"text": "Welcome to My Video",
"startTime": 2,
"duration": 5,
"fontSize": 72,
"fontColor": "#ffffff",
"position": {
"x": "center",
"y": "center"
},
"background": {
"color": "#000000",
"opacity": 0.7,
"padding": 20
},
"outline": {
"color": "#ff0000",
"width": 2
},
"fadeIn": 1,
"fadeOut": 1
}
]
}
]
}
}Result: Video with centered title text appearing from 2-7 seconds with fade effects
Example 8: Multiple Overlays and Text
Combine logo, banner, and text overlays:
{
"version": "1.0",
"output": {
"resolution": "1920x1080",
"container": "mp4"
},
"timeline": {
"tracks": [
{
"type": "video",
"clips": [
{
"id": "video",
"src": "https://example.com/content.mp4",
"in": 0,
"out": 45,
"startTime": 0
}
]
},
{
"type": "overlay",
"clips": [
{
"id": "logo",
"src": "https://example.com/logo.png",
"startTime": 0,
"duration": 45,
"position": {
"x": 20,
"y": 20
},
"size": {
"width": 120,
"height": 120
},
"opacity": 0.9
},
{
"id": "banner",
"src": "https://example.com/banner.png",
"startTime": 5,
"duration": 10,
"position": {
"x": "center",
"y": "bottom"
},
"fadeIn": 0.5,
"fadeOut": 0.5
}
]
},
{
"type": "text",
"clips": [
{
"id": "intro",
"text": "Episode 1: Getting Started",
"startTime": 1,
"duration": 4,
"fontSize": 48,
"fontColor": "#ffffff",
"position": {
"x": "center",
"y": 100
},
"shadow": {
"color": "#000000",
"offsetX": 2,
"offsetY": 2,
"blur": 4
},
"fadeIn": 0.5,
"fadeOut": 0.5
},
{
"id": "subscribe",
"text": "Subscribe for more!",
"startTime": 40,
"duration": 5,
"fontSize": 36,
"fontColor": "#ff0000",
"fontWeight": "bold",
"position": {
"x": "center",
"y": "bottom"
},
"background": {
"color": "#ffffff",
"opacity": 0.9,
"padding": 15
},
"fadeIn": 0.3,
"fadeOut": 0.3
}
]
}
]
}
}Result: 45-second video with:
- Logo in top-left corner throughout
- Banner overlay from 5-15 seconds
- Title text from 1-5 seconds
- Subscribe call-to-action from 40-45 seconds
Example 9: Lower Third Text
Add professional lower third text overlay:
{
"version": "1.0",
"timeline": {
"tracks": [
{
"type": "video",
"clips": [
{
"id": "interview",
"src": "https://example.com/interview.mp4",
"in": 0,
"out": 120,
"startTime": 0
}
]
},
{
"type": "text",
"clips": [
{
"id": "name",
"text": "John Doe",
"startTime": 5,
"duration": 10,
"fontSize": 42,
"fontColor": "#ffffff",
"fontWeight": "bold",
"position": {
"x": 50,
"y": 900
},
"background": {
"color": "#0066cc",
"opacity": 0.85,
"padding": 12
},
"fadeIn": 0.5,
"fadeOut": 0.5
},
{
"id": "title",
"text": "CEO, Company Name",
"startTime": 5,
"duration": 10,
"fontSize": 28,
"fontColor": "#e0e0e0",
"position": {
"x": 50,
"y": 955
},
"background": {
"color": "#003366",
"opacity": 0.85,
"padding": 8
},
"fadeIn": 0.5,
"fadeOut": 0.5
}
]
}
]
}
}Result: Interview video with professional lower third showing name and title for 10 seconds
Overlay & Text Properties
Overlay Clip Properties
Required:
- id (string): Unique identifier
- src (string): URL to image file (PNG, JPG)
- startTime (number): When overlay appears (seconds)
- duration (number): How long overlay stays (seconds)
Optional:
- position: Object with x and y (numbers or "center", "left", "right", "top", "bottom")
- size: Object with width and height (numbers or percentages like "10%")
- opacity: Number 0.0 to 1.0 (default: 1.0)
- fadeIn: Fade in duration in seconds
- fadeOut: Fade out duration in seconds
- blendMode: "normal", "multiply", "screen", "overlay", "darken", "lighten"
Text Clip Properties
Required:
- id (string): Unique identifier
- text (string): The text content to display
- startTime (number): When text appears (seconds)
- duration (number): How long text stays (seconds)
Optional:
- fontSize: Number in pixels (default: 48)
- fontColor: Hex color like "#ffffff" (default: white)
- fontFamily: Font name (default: "Arial")
- fontWeight: "normal" or "bold"
- textAlign: "left", "center", "right"
- position: Object with x and y (numbers or keywords)
- background: Object with color, opacity, padding
- outline: Object with color and width
- shadow: Object with color, offsetX, offsetY, blur
- fadeIn: Fade in duration in seconds
- fadeOut: Fade out duration in seconds
Plan Limits
Different subscription plans have different limits:
| Plan | Max Clips | Max Assets | Max Duration | Max Tracks |
|---|---|---|---|---|
| Free | 5 | 5 | 10 minutes | 2 |
| Pro | 20 | 20 | 30 minutes | 5 |
| Business | 100 | 100 | 2 hours | 20 |
| Enterprise | 1000 | 500 | 10 hours | 50 |
Output Settings
You can customize the output format:
{
"output": {
"resolution": "1920x1080", // Width x Height
"fps": 30, // Frames per second
"container": "mp4", // mp4, webm, mov, mkv
"videoCodec": "libx264", // libx264, libx265, libvpx-vp9
"audioCodec": "aac", // aac, mp3, opus
"videoBitrate": "5000k", // Video bitrate
"audioBitrate": "192k", // Audio bitrate
"preset": "medium", // ultrafast to veryslow
"crf": 23 // Quality (lower = better)
}
}Practical notes
Every asset URL must be publicly reachable
Each src and each replaceAudio is downloaded by the worker, from the public
internet, at the moment the job runs. URLs behind a login, short-lived signed
URLs, and private addresses all fail. If your sources are private, sign them
yourself with a lifetime that outlasts the queue.
Every distinct URL counts towards your asset limit, so reusing one source for several clips costs one asset, not several.
Build up one feature at a time
Trim and concatenation first, then speed, then audio, then overlays and text. A timeline that fails is easier to diagnose when you know which addition broke it.
Validate before you create
curl -X PUT https://videotranscode.cloud/api/v1/edl \
-H "Authorization: Bearer $VT_API_KEY" \
-H "Content-Type: application/json" \
-d @your-edl.jsonWatch for the experimental warning
If validation returns a warning naming transform, crop, filters,
transition or freeze, that part of your edit will be ignored by the
encoder. The job still runs and still costs a job.
Monitor progress
EDL jobs can take longer to process. Use webhooks or poll the job status:
curl https://videotranscode.cloud/api/v1/jobs/JOB_ID \
-H "Authorization: Bearer $VT_API_KEY"Troubleshooting
Common Errors
"EDL validation failed: out must be greater than in"
Make sure out time is always greater than in time:
{
"in": 5,
"out": 10 // Must be > 5
}"Too many clips"
You've exceeded your plan limit. Either:
- Reduce the number of clips
- Upgrade your plan
"Asset not found"
Check that:
- URLs are publicly accessible
- URLs return valid video files
- There are no CORS restrictions
"Duration exceeded"
The total output duration exceeds your plan limit:
- Trim longer clips
- Remove some clips
- Upgrade your plan
Getting Help
Need assistance?
- Check our FAQ
- Join our Discord community (opens in a new tab)
- Email: [email protected]
- Documentation: https://videotranscode.cloud/docs (opens in a new tab)
Technical Details
Processing Flow
- Validation: EDL validated against schema and business rules
- Download: All assets downloaded from provided URLs
- Processing: Individual clips processed with effects
- Assembly: Clips concatenated and merged
- Encoding: Final output encoded with specified settings
- Upload: Result uploaded to storage
- Notification: Webhook sent with download URL
FFmpeg Integration
The system generates optimized FFmpeg commands. For example, a simple 2-clip concatenation:
ffmpeg -i video1.mp4 -i video2.mp4 \
-filter_complex "\
[0:v]trim=0:10,setpts=PTS-STARTPTS[v0]; \
[1:v]trim=5:15,setpts=PTS-STARTPTS,setpts=0.667*PTS[v1]; \
[v0][v1]concat=n=2:v=1:a=1[out]" \
-map "[out]" output.mp4Code Examples
JavaScript/TypeScript
const edl = {
version: "1.0",
timeline: {
tracks: [{
type: "video",
clips: [{
id: "clip1",
src: "https://example.com/video.mp4",
in: 0,
out: 10,
startTime: 0,
speed: 1.5
}]
}]
}
};
const response = await fetch('https://videotranscode.cloud/api/v1/edl', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({ edl })
});
const job = await response.json();
console.log(`Job created: ${job.id}`);Python
import requests
import json
edl = {
"version": "1.0",
"timeline": {
"tracks": [{
"type": "video",
"clips": [{
"id": "clip1",
"src": "https://example.com/video.mp4",
"in": 0,
"out": 10,
"startTime": 0
}]
}]
}
}
response = requests.post(
'https://videotranscode.cloud/api/v1/edl',
headers={
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
json={'edl': edl}
)
job = response.json()
print(f"Job created: {job['id']}")cURL
curl -X POST https://videotranscode.cloud/api/v1/edl \
-H "Authorization: Bearer $VT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"edl": {
"version": "1.0",
"timeline": {
"tracks": [{
"type": "video",
"clips": [{
"id": "clip1",
"src": "https://example.com/video.mp4",
"in": 0,
"out": 10,
"startTime": 0
}]
}]
}
}
}'Next steps
An EDL job is an ordinary job once it is created, so everything else you need is in the general reference.
- Jobs covers reading, cancelling and downloading it.
- Webhooks describes the notification you get when it finishes.
- Plans and limits lists the clip, asset, duration and track limits.
- Preset catalogue is the alternative when you are transcoding one file rather than assembling a timeline.
If this is your first timeline, start with the trim and concatenate example
above, validate it with PUT /api/v1/edl, and add one feature at a time.