API reference

Every job takes inputs — an uploaded key or an external http(s) URL — and returns a job id you poll until it lands. Your ffmpeg options pass through verbatim.

Run a job

POST /api/ffmpeg/job/

Queues an ffmpeg operation over one or more inputs (already-uploaded files or external URLs) and produces one or more outputs. Processing runs async on a separate worker — this response only confirms the job was queued, not the final result (see GET /api/ffmpeg/job/{job_id}/ to check on it).

Parameters

global_options optional array of strings
ffmpeg flags applied globally, before the inputs.
inputs required array
Needs at least one item. Each item needs an "input_source" — either a key from a file already uploaded via /api/files/upload/, or an external http(s) URL. The server auto-detects which one it is (anything that parses as an absolute http/https URL is treated as a URL, everything else as a storage key) — plus an optional "options" with flags for that specific input.
outputs required array
Needs at least one item. Each item needs a "filename" (a simple name, no paths) and an optional "options" with the ffmpeg flags for that output (e.g. -vf, -c:v, -crf).

Example request

POST /api/ffmpeg/job/
{
  "global_options": [
    "-y"
  ],
  "inputs": [
    {
      "input_source": "https://example.com/input.mp4",
      "options": [
        "-t",
        "30"
      ]
    },
    {
      "input_source": "uploads/8f3a2b1c-logo.png",
      "options": [
        "-loop",
        "1"
      ]
    }
  ],
  "outputs": [
    {
      "filename": "output.mp4",
      "options": [
        "-filter_complex",
        "[0:v][1:v]overlay=10:10",
        "-c:v",
        "libx264",
        "-crf",
        "23"
      ]
    },
    {
      "filename": "thumbnail.jpg",
      "options": [
        "-vf",
        "scale=320:-1",
        "-frames:v",
        "1"
      ]
    }
  ]
}

Example response

202 Accepted
{
  "job_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "status": "pending"
}

Response fields

job_id string
UUID of the newly created job — used to check its status at GET /api/ffmpeg/job/{job_id}/.
status string
the job's initial status. Always "pending" in this response; once you check on it later it can be "processing", "completed" or "failed".

Assistant

Ask what you want to do — "how do I downscale a video from 1080p to 480p" — and you get the endpoint, the full request, and an explanation of every option used.

Get job status

GET /api/ffmpeg/job/{job_id}/

Checks the status of a job created by POST /api/ffmpeg/job/ — poll this until status reaches a terminal value ("success", "failed" or "timeout"); before that it's "pending", "download", "running" or "upload" depending on which stage of the pipeline it's in. A job id that doesn't exist, or belongs to someone else, gets the same 404 either way — never leaks which one it was.

Example response

200 OK
{
  "job_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "status": "success",
  "created_at": "2026-08-22T10:00:00Z",
  "started_at": "2026-08-22T10:00:05Z",
  "finished_at": "2026-08-22T10:00:42Z",
  "output_bytes": 5242880,
  "outputs": [
    {
      "filename": "output.mp4",
      "size": 5242880,
      "url": "https://s3.example.com/outputs/42/output.mp4?X-Amz-Signature=..."
    }
  ]
}

Response fields

job_id string
UUID of the job.
status string
either "pending", "download", "running", "upload" (still going), or "success", "failed", "timeout" (terminal — stop polling once you see one of these).
created_at string
when the job was created.
started_at string | null
when the worker started processing it, or null if it hasn't yet.
finished_at string | null
when the job reached a terminal status, or null if it hasn't yet.
output_bytes integer | null
total bytes across all produced outputs. Null until terminal, and 0 when the job failed before ffmpeg wrote anything.
outputs array
array of produced files — only present when status is "success". Each item has "filename", "size" and a signed "url" to download it.
detail string
error message — only present when status is "failed" or "timeout".
error_type string
machine-readable error code (e.g. "ffmpeg_run-ffmpeg-execution_error", "download-http-unreachable_url") identifying which stage/cause failed — only present when status is "failed" or "timeout", same condition as "detail".
returncode integer
ffmpeg's process exit code — only present when the job actually ran to completion and failed (absent on "timeout", where it never got that far).

Code example

GET /api/ffmpeg/job/{job_id}/
(no request body — GET request)

Run a job

POST /api/ffprobe/job/

Runs ffprobe over an already-uploaded file or an external URL and returns its metadata (container format, duration, codecs, one entry per stream) — unlike /api/ffmpeg/job/, it doesn't produce any output file, it only inspects the one it's given. Answers in one of two ways, and your client has to handle both: 200 with the result in the body (an ordinary metadata probe, which takes tens of milliseconds), or 202 with a job_id when it's taking longer — then poll GET /api/ffprobe/job/{job_id}/, the same contract as POST /api/ffmpeg/job/. You hit the 202 path when the probe has real work to do: -count_frames over a long video, or an external URL that's slow to download.

Parameters

input_source required string
Either a key from a file already uploaded via /api/files/upload/, or an external http(s) URL — same auto-detection as the "inputs" of /api/ffmpeg/job/.
options optional array of strings
Raw ffprobe flag tokens (e.g. -select_streams, -show_entries, -count_frames, -count_packets), in the order you want them on the command line. Flags the server controls internally (-i, -protocol_whitelist, -tls_verify, -max_redirects, -rw_timeout, -print_format/-of, -v/-loglevel) are rejected with a 400. Pass no -show_* flag and the probe runs with -show_format -show_streams (everything ffprobe knows); pass any of them and you get exactly the sections you asked for, so -show_entries stream=width,height returns those two fields and an empty "format".

Example request

POST /api/ffprobe/job/
{
  "input_source": "https://lorem.video/720p",
  "options": [
    "-select_streams",
    "v:0",
    "-show_entries",
    "stream=width,height,r_frame_rate"
  ]
}

Example response

200 OK
{
  "job_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "status": "success",
  "created_at": "2026-08-21T12:00:00Z",
  "started_at": "2026-08-21T12:00:01Z",
  "finished_at": "2026-08-21T12:00:03Z",
  "format": {},
  "streams": [
    {
      "width": 1280,
      "height": 720,
      "r_frame_rate": "30/1"
    }
  ]
}

Response fields

job_id string
UUID of the probe job that was run.
status string
on a 200, either "success", "failed" or "timeout". On a 202 it's still "pending" or "running" — keep polling the GET below until it turns terminal.
created_at string
when the job was created.
started_at string | null
when ffprobe actually started running, or null if it never got to.
finished_at string | null
when the job reached its final status, or null if it never did.
format object
raw ffprobe "format" block (container/duration/size/bit_rate/tags) — only present when status is "success", and empty if "options" asked for sections that leave it out.
streams array
raw ffprobe "streams" array (one entry per audio/video/subtitle stream) — only present when status is "success", and empty if "options" asked for sections that leave it out.
detail string
error message from ffprobe or the worker — only present when status is "failed" or "timeout".
error_type string
machine-readable error code (e.g. "ffprobe_run-ffprobe-execution_error", "download-http-unreachable_url") identifying which stage/cause failed — only present when status is "failed" or "timeout", same condition as "detail".
returncode integer
exit code ffprobe exited with — same condition as "detail", and only when ffprobe actually got to run.

Code example

POST /api/ffprobe/job/
{
  "input_source": "https://lorem.video/720p",
  "options": [
    "-select_streams",
    "v:0",
    "-show_entries",
    "stream=width,height,r_frame_rate"
  ]
}

Original input

Get job status

GET /api/ffprobe/job/{job_id}/

Checks the status of a probe that came back as 202 — poll this until status reaches a terminal value ("success", "failed" or "timeout"). A probe that answered 200 is already done and needs no polling. A job id that doesn't exist, or belongs to someone else, gets the same 404 either way — never leaks which one it was.

Example response

200 OK
{
  "job_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "status": "success",
  "created_at": "2026-08-21T12:00:00Z",
  "started_at": "2026-08-21T12:00:01Z",
  "finished_at": "2026-08-21T12:01:27Z",
  "format": {
    "filename": "pipe:0",
    "format_name": "mov,mp4,m4a,3gp,3g2,mj2",
    "duration": "600.000000"
  },
  "streams": [
    {
      "index": 0,
      "codec_type": "video",
      "nb_read_frames": "36000"
    }
  ]
}

Response fields

job_id string
UUID of the probe job.
status string
"pending" or "running" while it's still going, or "success", "failed", "timeout" (terminal — stop polling once you see one of these).
created_at string
when the job was created.
started_at string | null
when ffprobe actually started running, or null if it hasn't yet.
finished_at string | null
when the job reached a terminal status, or null if it hasn't yet.
format object
raw ffprobe "format" block — only present when status is "success".
streams array
raw ffprobe "streams" array — only present when status is "success".
detail string
error message — only present when status is "failed" or "timeout".
error_type string
machine-readable error code identifying which stage/cause failed — only present when status is "failed" or "timeout".
returncode integer
exit code ffprobe exited with — same condition as "detail", and only when ffprobe actually got to run.

Code example

GET /api/ffprobe/job/{job_id}/
(no request body — GET request)

Upload a file

POST /api/files/upload/

Uploads a file (typically a video/audio you'll want to process later) and stores it under your account. Returns a storage key you can pass as the input_source of /api/ffmpeg/job/ or /api/ffprobe/job/ instead of an external URL, plus a signed URL to download the same file directly.

Parameters

file required binary
Sent as multipart/form-data (not JSON) — a single file under the "file" field.

Example request · multipart/form-data, shown as JSON for brevity

POST /api/files/upload/
{
  "file": "video.mp4"
}

Example response

201 Created
{
  "key": "inputs/42/3fa85f64b8e94b0b9c1e2a7d6f5c4b3a_video.mp4",
  "url": "https://s3.example.com/inputs/42/3fa85f64b8e94b0b9c1e2a7d6f5c4b3a_video.mp4?X-Amz-Signature=..."
}

Response fields

key string
storage key of the uploaded file — pass this as input_source to /api/ffmpeg/job/ or /api/ffprobe/job/ later.
url string
signed, temporary URL to download/preview the file directly.

Code example

POST /api/files/upload/
{
  "file": "video.mp4"
}

Read a file

GET /api/files/read/{key}/

Returns a fresh signed, temporary URL to download a file you already own. The URL that POST /api/files/upload/ hands back expires after 15 minutes — this is how you get another one without re-uploading. A key that doesn't exist, or belongs to someone else, gets the same 404 either way — never leaks which one it was.

Parameters

key required string
The storage key returned by /api/files/upload/ (or the key of a job output). Goes in the path, not in a body — it contains slashes ("inputs/42/…"), and the route takes all of them as part of the key.

Example response

200 OK
{
  "key": "inputs/42/3fa85f64b8e94b0b9c1e2a7d6f5c4b3a_video.mp4",
  "url": "https://s3.example.com/inputs/42/3fa85f64b8e94b0b9c1e2a7d6f5c4b3a_video.mp4?X-Amz-Signature=..."
}

Response fields

key string
the same key you asked for, echoed back.
url string
signed, temporary URL to download/preview the file directly.

Code example

GET /api/files/read/{key}/
(no request body — GET request)

Ask me for anything

QuickFFmpeg is early and plenty is still missing. Tell me what you need and it goes straight to the top of my list.

This arrives anonymously. Log in first if you'd like an answer.