API reference
EDL

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/json

Request 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 volume
    • 0 = 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:

PlanMax ClipsMax AssetsMax DurationMax Tracks
Free5510 minutes2
Pro202030 minutes5
Business1001002 hours20
Enterprise100050010 hours50

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.json

Watch 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?

Technical Details

Processing Flow

  1. Validation: EDL validated against schema and business rules
  2. Download: All assets downloaded from provided URLs
  3. Processing: Individual clips processed with effects
  4. Assembly: Clips concatenated and merged
  5. Encoding: Final output encoded with specified settings
  6. Upload: Result uploaded to storage
  7. 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.mp4

Code 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.