How it works
Submit a JSON job with a source bucket/object and destination bucket/object. The worker downloads the source object, converts it with FFmpeg, uploads the result to storage, and reports progress through WebSocket events. The browser never uploads the original video to this API.
Create an MP4 job
curl -X POST https://api.example.com/api/v1/jobs \
-H 'content-type: application/json' \
-d '{
"sourceBucket": "original-media",
"sourceKey": "uploads/movie.mp4",
"sourceRegion": "us-east-1",
"sourceAccessKeyId": "SOURCE_KEY",
"sourceSecretAccessKey": "SOURCE_SECRET",
"destinationBucket": "converted-media",
"destinationKey": "converted/movie.mp4",
"destinationRegion": "us-east-1",
"destinationAccessKeyId": "DESTINATION_KEY",
"destinationSecretAccessKey": "DESTINATION_SECRET",
"outputFormat": "mp4",
"resolution": "1080p",
"videoBitrate": "4000k",
"maxVideoBitrate": "5000k",
"videoBufferSize": "8000k",
"fps": 30,
"audioCodec": "aac",
"audioBitrate": "128k",
"preset": "veryfast",
"normalizeHdr": true
}'The response is 202 Accepted with a job ID and status URL. Jobs are created before source download, so storage and conversion errors stay visible.
Create an HLS/M3U8 job
curl -X POST https://api.example.com/api/v1/jobs \
-H 'content-type: application/json' \
-d '{
"sourceBucket": "original-media",
"sourceKey": "uploads/movie.mp4",
"sourceRegion": "us-east-1",
"sourceAccessKeyId": "SOURCE_KEY",
"sourceSecretAccessKey": "SOURCE_SECRET",
"destinationBucket": "streaming-media",
"destinationKey": "hls/movie/index.m3u8",
"destinationRegion": "us-east-1",
"destinationAccessKeyId": "DESTINATION_KEY",
"destinationSecretAccessKey": "DESTINATION_SECRET",
"outputFormat": "hls",
"segmentDuration": 6,
"videoBitrate": "3000k",
"audioBitrate": "128k"
}'HLS creates the requested playlist key and MPEG-TS segments beside it, for example hls/movie/index.m3u8 and hls/movie/segment_00000.ts. The playlist uses VOD mode and independent segments.
Trim only without re-encoding
curl -X POST https://api.example.com/api/v1/jobs \\
-H 'content-type: application/json' \\
-d '{
"sourceBucket": "original-media",
"sourceKey": "uploads/movie.mp4",
"sourceRegion": "us-east-1",
"sourceAccessKeyId": "SOURCE_KEY",
"sourceSecretAccessKey": "SOURCE_SECRET",
"destinationBucket": "converted-media",
"destinationKey": "converted/movie-trim.mp4",
"destinationRegion": "us-east-1",
"destinationAccessKeyId": "DESTINATION_KEY",
"destinationSecretAccessKey": "DESTINATION_SECRET",
"outputFormat": "trim",
"trimStart": 10,
"trimEnd": 50
}'Trim mode accepts numeric seconds or HH:MM:SS/MM:SS timecodes and uses FFmpeg stream copy (-c copy) for the requested 10–50 second range. It does not decode or re-encode the audio or video, so CPU usage and quality loss are minimal. The destination key must use the same extension as the source. Stream-copy cuts can land on the nearest keyframe and may not be frame-accurate; use a normal transcode when exact frame precision, resizing, filters, or codec changes are required.
Trim only without re-encoding
outputFormat: "trim" trimStart: "00:00:10" trimEnd: "00:00:50" sourceKey: "uploads/movie.mp4" destinationKey: "converted/movie-trim.mp4"
Trim mode uses stream copy and requires the destination extension to match the source extension. The test console automatically omits transcode-only controls when trim is selected.
Endpoints
| Method | Path | Purpose |
|---|---|---|
| GET | /health | Service and queue health. |
| POST | /api/v1/jobs | Create a bucket-to-bucket JSON conversion job. |
| GET | /api/v1/jobs | List jobs. |
| GET | /api/v1/jobs/:id | Read one job and progress. |
| DELETE | /api/v1/jobs/:id | Remove a job record. |
| WebSocket | /ws | Receive snapshots, health, and job progress events. |
Encoding options
outputFormat accepts mp4, webm, hls, or trim. MP4 uses H.264/AAC and -movflags +faststart; WebM uses VP9/Opus; HLS uses H.264 with an M3U8 playlist and MPEG-TS segments. HLS requires videoCodec=h264 and audioCodec=aac or none.
Use videoCodec, videoBitrate, maxVideoBitrate, videoBufferSize, fps, crf, and preset (ultrafast through slow) to balance speed, quality, and compression. Use audioCodec (aac, opus, or none), audioBitrate, and audioSampleRate to control audio or remove it completely.
Use segmentDuration from 1 to 30 seconds for HLS. Shorter segments reduce startup and seek latency but create more objects and requests. Longer segments reduce object count and overhead.
Video processing
resolution accepts original, 360p, 480p, 720p, 1080p, 1440p, 2160p, or custom with width and height. fit accepts contain, cover, or stretch. Set cropEnabled=true with crop width, height, and x/y offsets. Set thumbnail=true to create a JPEG. HDR normalization is enabled by default and converts HDR/Apple HDR to 8-bit SDR BT.709 for Android compatibility.
Live updates and webhooks
Connect to wss://api.example.com/ws. The server sends snapshot, health, job.created, job.updated, and job.deleted. Optional webhooks receive signed job.completed or job.failed events using the x-video-convert-signature HMAC-SHA256 header.
Coolify
Use the included Dockerfile, expose port 3000, mount /app/data, and provide enough /app/temp storage for source and output work files. Start with WORKER_CONCURRENCY=1 and optionally set FFMPEG_THREADS=2 on a small VPS.