Skip to content

Preview Videxo runs jobs, accepts uploads up to 100 MB, serves output downloads and sends signed webhooks. Sign in with email, GitHub or a passkey. Payments are not connected yet, so credit packs and paid plans cannot be bought. Read the quickstart

ffmpeg as a service

The ffmpeg API. Your command over HTTPS.

Hosted ffmpeg as a REST API: send the command you already run with your input URLs, and get the files and a signed webhook back, or send the same job as JSON.

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"
  }
}'

The request

Five fields. Two of them optional.

input_files
Aliases such as in_video, each to a public http or https URL, or the input_url of a completed private upload.
output_files
Aliases such as out_main, each to the file name you want back. The extension picks the format.
command
ffmpeg’s arguments without “ffmpeg”, with each file named by its {{alias}}.
webhook_url
Optional. An https URL that gets the job, signed, when it ends.
metadata
Optional. Your own strings, returned with the job and its webhook.
POST /v1/jobs in the API reference
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"
  }
}'

The answer is the job, queued, with its id. Ask for it with GET /v1/jobs/{id} or wait for the webhook.

Or JSON

Or skip the command. Say what you want in JSON.

Name an input and a preset or a few options, with curl or any HTTP client. The API writes the ffmpeg command and returns it with the job, so you can see and keep exactly what ran.

Operations reference
curl "$VIDEXO_API_URL/v1/transcode" \
  -H "Authorization: Bearer $VIDEXO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "input": "https://videxo.io/samples/talk.mp4",
  "preset": "web-720p",
  "filename": "talk-720p.mp4",
  "metadata": {
    "order": "1234"
  }
}'

The ffmpeg tab is the command this request runs, as the job returns it in ffmpeg_command.

Compatibility

What your commands can use. ffmpeg 6.1.1.

Options go where ffmpeg reads them: before an input, before an output or globally, with stream suffixes such as -c:v and -b:a. Values, filter graphs and expressions are written as you write them today.

Output formats the file name's extension picks the muxer
  • .mp4 mp4
  • .mov mov
  • .m4a ipod
  • .mkv matroska
  • .webm webm
  • .ts mpegts
  • .mp3 mp3
  • .aac adts
  • .wav wav
  • .flac flac
  • .ogg ogg
  • .opus opus
  • .gif gif
  • .png image2pipe
  • .jpg image2pipe
  • .jpeg image2pipe
Input formats ffmpeg demuxers that open inputs
  • mov
  • matroska
  • webm
  • avi
  • mpegts
  • mp3
  • aac
  • wav
  • flac
  • ogg
  • gif
  • png_pipe
  • jpeg_pipe
  • webp_pipe
  • bmp_pipe
  • rawvideo
  • s16le
Codecs for -c, -codec and their stream forms, and copy
  • libx264
  • libx265
  • libvpx
  • libvpx-vp9
  • libaom-av1
  • mpeg4
  • mjpeg
  • png
  • gif
  • ffv1
  • aac
  • libmp3lame
  • libopus
  • libvorbis
  • flac
  • pcm_s16le
  • pcm_s24le
  • pcm_f32le
  • alac
Encoder presets and pixel formats -preset, -pix_fmt and the format filter
  • ultrafast
  • superfast
  • veryfast
  • faster
  • fast
  • medium
  • slow
  • yuv420p
  • yuv422p
  • yuv444p
  • yuv420p10le
  • yuv422p10le
  • yuv444p10le
  • yuva420p
  • rgb24
  • rgba
  • bgra
  • gray
  • pal8
  • nv12
53 options each where ffmpeg reads it
  • -loglevel
  • -v
  • -filter_complex
  • -filter_complex_threads
  • -filter_threads
  • -f
  • -ss
  • -t
  • -to
  • -itsoffset
  • -skip_frame
  • -stream_loop
  • -r
  • -s
  • -pix_fmt
  • -ar
  • -ac
  • -threads
  • -c
  • -codec
  • -b
  • -maxrate
  • -minrate
  • -bufsize
  • -crf
  • -q
  • -qscale
  • -preset
  • -tune
  • -profile
  • -level
  • -g
  • -keyint_min
  • -bf
  • -tag
  • -cpu-used
  • -frames
  • -vframes
  • -aframes
  • -map
  • -map_metadata
  • -map_chapters
  • -metadata
  • -movflags
  • -fps_mode
  • -vn
  • -an
  • -sn
  • -dn
  • -shortest
  • -vf
  • -af
  • -filter
48 filters in -vf, -af, -filter and -filter_complex
  • acrossfade
  • afade
  • aformat
  • amix
  • apad
  • aresample
  • asetpts
  • asplit
  • atempo
  • atrim
  • boxblur
  • colorchannelmixer
  • concat
  • crop
  • drawtext
  • eq
  • fade
  • format
  • fps
  • hflip
  • hue
  • loudnorm
  • overlay
  • pad
  • palettegen
  • paletteuse
  • rotate
  • scale
  • scale2ref
  • scdet
  • select
  • setdar
  • setpts
  • setsar
  • showspectrum
  • showwaves
  • split
  • subtitles
  • thumbnail
  • tile
  • tpad
  • transpose
  • trim
  • unsharp
  • vflip
  • volume
  • xfade
  • zoompan

A command holds up to 1,024 arguments and 65,536 characters, names up to 16 input and 16 output files, and uses up to 128 filters in its graphs.

Migrate

From your server. To one POST.

  1. 1 Drop ffmpeg itself and the options for a terminal, such as -y and -hide_banner.
  2. 2 Give each input an alias in input_files, mapped to its URL or upload, and write {{in_name}} where its path was.
  3. 3 Give each output an alias in output_files, mapped to its file name, and write {{out_name}} where its path was.
Migrating from your own ffmpeg
ffmpeg \
  -i talk.mp4 \
  -vf scale=-2:720 \
  -c:v libx264 -crf 23 \
  -c:a copy \
  talk-720p.mp4

The ffmpeg tab is the job's ffmpeg_command: your command with the URLs and file names put back.

Webhooks

Signed when it ends. Retried for about a day.

When a job succeeds, fails or is cancelled, the API posts the whole job to your webhook in the Standard Webhooks format, signed with your account’s secret. Verify it with the standardwebhooks library or with a few lines of node:crypto.

Webhooks and signatures
import crypto from 'node:crypto';

// The event when the headers prove the body came from us, null otherwise.
function verifyWebhook(secret, headers, body, now = Date.now()) {
  const id = headers['webhook-id'];
  const timestamp = headers['webhook-timestamp'];
  const signatures = headers['webhook-signature'];
  if (!id || !timestamp || !signatures) return null;
  // Refuse a timestamp more than five minutes away: an old delivery replayed.
  if (Math.abs(now / 1000 - Number(timestamp)) > 300) return null;
  const key = Buffer.from(secret.slice('whsec_'.length), 'base64');
  const expected = crypto.createHmac('sha256', key).update(`${id}.${timestamp}.${body}`).digest();
  // After a rotation the header carries two signatures, space separated: either may match.
  for (const signature of signatures.split(' ')) {
    const [version, value = ''] = signature.split(',');
    const given = Buffer.from(value, 'base64');
    if (version === 'v1' && given.length === expected.length && crypto.timingSafeEqual(given, expected)) {
      return JSON.parse(body);
    }
  }
  return null;
}

Retries

Retry without a second job.

A timeout can hide whether a job was created. Send an Idempotency-Key and retry with the same key: for 24 hours you get the same job back, never a second one.

Idempotency
curl "$VIDEXO_API_URL/v1/transcode" \
  -H "Authorization: Bearer $VIDEXO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1234-transcode" \
  -d '{
  "input": "https://videxo.io/samples/talk.mp4",
  "preset": "web-720p",
  "filename": "talk-720p.mp4",
  "metadata": {
    "order": "1234"
  }
}'

Examples

Start from a command that runs. Then change it.

  • Compress and transcode -i {{in_video}} -c:v libx264 -crf 28 -preset veryfast -c:a aac -b:a 128k -movflags +faststart {{out_main}}
  • Trim and clip -ss 00:01:30 -i {{in_video}} -t 30 -c:v libx264 -crf 23 -c:a aac {{out_main}}
  • Vertical 9:16 -i {{in_video}} -vf "crop=ih*9/16:ih,scale=1080:1920" -c:v libx264 -crf 23 -c:a copy {{out_main}}
  • Watermark or logo -i {{in_video}} -i {{in_logo}} -filter_complex "[1:v]scale=160:-1[logo];[0:v][logo]overlay=x=W-w-24:y=H-h-24[v]" -map "[v]" -map 0:a? -c:v libx264 -crf 23 -c:a copy {{out_main}}
  • GIF previews -ss 5 -t 4 -i {{in_video}} -filter_complex "[0:v]fps=12,scale=480:-1:flags=lanczos,split[a][b];[a]palettegen[p];[b][p]paletteuse[v]" -map "[v]" {{out_main}}
  • Loudness normalisation -i {{in_video}} -c:v copy -af "loudnorm=I=-16:LRA=11:TP=-1.5" -c:a aac -b:a 192k {{out_main}}

Questions, answered.

Is this ffmpeg as a service?

Yes. Hosted ffmpeg behind a REST API: POST /v1/jobs takes your ffmpeg arguments, input URLs or uploads and output names, runs the job and stores the files, and GET /v1/jobs/{id} or a signed webhook returns them. There is no ffmpeg server, queue or storage for you to run.

Which ffmpeg features can my commands use?

ffmpeg 6.1.1 with 48 filters, 53 options, the codecs libx264, libx265, libvpx, libvpx-vp9, libaom-av1, mpeg4, mjpeg, png, gif, ffv1, aac, libmp3lame, libopus, libvorbis, flac, pcm_s16le, pcm_s24le, pcm_f32le, alac and the output formats .mp4 .mov .m4a .mkv .webm .ts .mp3 .aac .wav .flac .ogg .opus .gif .png .jpg .jpeg. The list above and the supported commands page name every one.

Can I describe a job in JSON instead of a command?

Yes. POST /v1/transcode, /v1/thumbnails, /v1/gif and the other operations take an input and a preset or a few options, and POST /v1/jobs takes a list of outputs. The job returns the ffmpeg command it compiled to, so you can move one job at a time and run the same command yourself.

Which languages can call it?

Any language that can send an HTTPS request. The docs show curl and JavaScript and have complete example projects for common frameworks, and the OpenAPI 3.1 document at /docs/openapi.json can generate a client for the rest.

How do I get the output files?

Each output includes a signed download_url valid for up to one hour, bounded by its retention. Read the job again for a fresh link, or use GET /v1/jobs/{id}/outputs/{alias} with your API key.

What does a job cost?

6 credits a minute of 720p output and 12 at 1080p, plus 10 credits for each GB fetched and stored. Pay as you go needs no card, and your personal organization gets 500 free credits once.

What happens when a command needs a change?

The API answers 400 before a job exists, with each argument to change and a sentence that says how; nothing is billed. If ffmpeg stops while running, the job ends failed with an error code, and its webhook says so.

Run your first job. Free, no card.