DocsRaw ffmpeg
Migrating from your own ffmpeg
Send the ffmpeg commands you already run as jobs, by naming files with aliases, and move to JSON one job at a time.
If your service already runs ffmpeg, you do not need to rewrite anything to start. Send each command as a job: the same arguments, with the files named by aliases instead of paths. When a job is easier to say in JSON, switch that one job; both kinds are the same Job resource, with the same webhooks, usage and rate limits.
Turn a command into a job#
Take the command you run today:
ffmpeg \
-i talk.mp4 \
-vf scale=-2:720 \
-c:v libx264 -crf 23 \
-c:a copy \
talk-720p.mp4Copy failed. The text is selected; copy it with your keyboard.
- Drop
ffmpegitself, and-y,-nostdinand-hide_banner, which are not supported in a command. - Give each input an alias
in_<name>ininput_files, mapped to anhttporhttpsURL or a completed upload, and write{{in_<name>}}where the path was. - Give each output an alias
out_<name>inoutput_files, mapped to its file name, and write{{out_<name>}}where the path was. The extension picks the format.
curl "$VIDEXO_API_URL/v1/jobs" \
-H "Authorization: Bearer $VIDEXO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input_files": {
"in_video": "https://videxo.io/samples/talk.mp4"
},
"output_files": {
"out_main": "talk-720p.mp4"
},
"command": "-i {{in_video}} -vf scale=-2:720 -c:v libx264 -crf 23 -c:a copy {{out_main}}",
"metadata": {
"order": "1234"
}
}'Copy failed. The text is selected; copy it with your keyboard.
const response = await fetch(`${process.env.VIDEXO_API_URL}/v1/jobs`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.VIDEXO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"input_files": {
"in_video": "https://videxo.io/samples/talk.mp4"
},
"output_files": {
"out_main": "talk-720p.mp4"
},
"command": "-i {{in_video}} -vf scale=-2:720 -c:v libx264 -crf 23 -c:a copy {{out_main}}",
"metadata": {
"order": "1234"
}
}),
});
const job = await response.json();Copy failed. The text is selected; copy it with your keyboard.
The API returns the job like any other. ffmpeg_command puts the URLs and file names back, so you
can run the same command on your own machine to compare:
{
"id": "job_3k9x0c2mfq8w1n7r5t4b6v2z",
"state": "queued",
"kind": "command",
"operation": null,
"spec": null,
"command": "-i {{in_video}} -vf scale=-2:720 -c:v libx264 -crf 23 -c:a copy {{out_main}}",
"input_files": {
"in_video": "https://videxo.io/samples/talk.mp4"
},
"output_files": {
"out_main": "talk-720p.mp4"
},
"ffmpeg_command": "ffmpeg -i https://videxo.io/samples/talk.mp4 -vf scale=-2:720 -c:v libx264 -crf 23 -c:a copy talk-720p.mp4",
"steps": [
{
"tool": "ffmpeg",
"inputs": [
"video"
],
"outputs": [
"main"
],
"command": "ffmpeg -i https://videxo.io/samples/talk.mp4 -vf scale=-2:720 -c:v libx264 -crf 23 -c:a copy talk-720p.mp4"
}
],
"webhook_url": null,
"metadata": {
"order": "1234"
},
"progress": null,
"error": null,
"outputs": [],
"usage": null,
"created_at": "2026-11-04T12:00:00.000Z",
"started_at": null,
"finished_at": null
}Copy failed. The text is selected; copy it with your keyboard.
What changes#
- One ffmpeg run. Pipes, redirects, variables and globs are not supported.
- Files by alias. Paths, URLs and protocols inside the command are not supported. Every alias in the maps must appear in the command.
- Supported options. Check that the command uses supported options, codecs, formats and filters
(command rules). Otherwise
POST /v1/jobsreturns400 command_invalidwith a code for each problem, and no job is created. - Limits. Your plan sets the longest job, the size of inputs and outputs and how many run at once (Limits per plan).
The MCP tool check_command validates a command without creating a job, and needs no API key
(MCP server).
Moving a job to JSON#
Most jobs that resize, compress, cut, watermark or grab frames say the same thing shorter in JSON, and the API keeps the ffmpeg details, such as even sizes for H.264 and seeking each part of an input, for you. The command above as JSON:
curl "$VIDEXO_API_URL/v1/transcode" \
-H "Authorization: Bearer $VIDEXO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": "https://example.com/talk.mp4",
"filename": "talk-720p.mp4",
"resize": {
"height": 720
},
"audio": {
"codec": "copy"
}
}'Copy failed. The text is selected; copy it with your keyboard.
const response = await fetch(`${process.env.VIDEXO_API_URL}/v1/transcode`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.VIDEXO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"input": "https://example.com/talk.mp4",
"filename": "talk-720p.mp4",
"resize": {
"height": 720
},
"audio": {
"codec": "copy"
}
}),
});
const job = await response.json();Copy failed. The text is selected; copy it with your keyboard.
ffmpeg \
-i https://example.com/talk.mp4 \
-map 0:v:0 \
-vf scale=7680:720:force_original_aspect_ratio=decrease:force_divisible_by=2 \
-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p \
-map "0:a:0?" \
-c:a copy -movflags +faststart \
talk-720p.mp4Copy failed. The text is selected; copy it with your keyboard.
The ffmpeg tab shows the command the API runs for it. It is not character for character your own:
the API writes every choice out, such as the codec, the pixel format and +faststart for MP4.
Operations lists every option and the guides show
them by task.
Keep a command for what JSON does not cover; the command rules are the limit of both.