Skip to main content
You write the FFmpeg command. Rendobar downloads inputs, runs the command in an isolated container, returns a signed download URL. Same syntax you’d run locally.
curl -X POST https://api.rendobar.com/jobs \
  -H "Authorization: Bearer rb_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "ffmpeg",
    "inputs": {},
    "params": {
      "command": "ffmpeg -i https://example.com/video.mp4 -vf scale=1280:720 -c:v libx264 -crf 23 output.mp4"
    }
  }'
From a shell? Use the CLI: rb ffmpeg -i input.mp4 -vf scale=1280:720 out.mp4. Local files upload automatically.

How it runs

  1. Validate: command parsed against 8 security layers (flag whitelist, format/filter blocklists)
  2. Download: input URLs pulled to a sandboxed worker
  3. Substitute: URLs replaced with local paths in the FFmpeg command
  4. Execute: FFmpeg runs in an isolated container with network disabled
  5. Upload: output goes to R2
  6. Return: signed output.file.url available via GET /jobs/{id}

Input sources

Most commands put the source URL straight in the command and skip inputs entirely. It’s optional and defaults to an empty map:
{
  "type": "ffmpeg",
  "params": {
    "command": "ffmpeg -i https://example.com/clip.mp4 -vf scale=1280:720 output.mp4"
  }
}
Reach for the inputs map only when a file can’t go inline: a file a filter reads by name (see below), inline text you don’t want to host, an already-uploaded asset, or when you need a stable filename. Each key is a filename staged under that exact name, and the command references it by bare name. A value can be one of three shapes:
ValueUse it for
"https://..."A remote file. Shorthand for { "url": "..." }
{ "url": "https://..." }A remote file, explicit form
{ "content": "...text..." }Inline text you don’t want to host (≤64 KB)
A file you already uploaded is referenced by its content URL: the url returned by the uploads endpoint, like https://api.rendobar.com/assets/asset_abc123/content. Pass it like any other URL.
{
  "type": "ffmpeg",
  "inputs": {
    "clip.mp4": "https://example.com/clip.mp4",
    "logo.png": "https://api.rendobar.com/assets/asset_brandlogo/content"
  },
  "params": {
    "command": "ffmpeg -i clip.mp4 -i logo.png -filter_complex overlay=10:10 output.mp4"
  }
}

Auxiliary files read by a filter

Some files aren’t passed as -i arguments. Filters like subtitles=, lut3d=, and drawtext fontfile=, and the lists read by the concat demuxer, read a file by name from the working directory. Provide those through inputs so they’re staged before the command runs. This burns a subtitle track that lives only in the request, with no file to host:
{
  "type": "ffmpeg",
  "inputs": {
    "video.mp4": "https://example.com/video.mp4",
    "subs.srt": {
      "content": "1\n00:00:01,000 --> 00:00:04,000\nRendered on Rendobar.\n"
    }
  },
  "params": {
    "command": "ffmpeg -i video.mp4 -vf subtitles=subs.srt -c:v libx264 -c:a copy output.mp4"
  }
}

Parameters

FieldTypeRequiredDefaultNotes
typestringyes-"ffmpeg"
inputsobjectyes-Working directory for the command. Maps a filename to a source (URL, {url}, or {content}; an uploaded asset’s content URL is just a URL). {} when every input is an inline -i URL. See Input sources
params.commandstringyes-Real FFmpeg command starting with ffmpeg. Input URLs go in -i positions
params.outputFormatstringnoinferredOverride container format. Inferred from trailing filename if omitted
params.timeoutintno120Server-side max execution in seconds. Range 1–900
params.computestringnoautoMachine class: auto, cpu, or gpu. See GPU acceleration

Validate without executing

Free, no auth:
curl -X POST https://api.rendobar.com/ffmpeg/validate \
  -H "Content-Type: application/json" \
  -d '{ "command": "ffmpeg -i video.mp4 -vf scale=1280:720 output.mp4" }'
Returns { data: { valid: true, args: [...], inferredOutputFormat: "mp4" } } or { data: { valid: false, error: "..." } }.

Allowed flags

~120 whitelisted flags. Common ones:
FlagPurpose
-iInput file
-fForce format
-c:v, -c:aVideo / audio codec (stream specifiers like -c:v:0 work)
-vf, -afVideo / audio filter
-filter_complexComplex filter graph
-mapStream mapping
-ss, -tSeek position, duration
-r, -sFrame rate, resolution
-b:v, -b:aVideo / audio bitrate
-crf, -presetQuality + preset
-an, -vnDisable audio / video
-yOverwrite output
-movflags, -strictContainer flags, strict standards
Unrecognised flags are rejected before dispatch with VALIDATION_ERROR.

GPU acceleration

Your jobs run on CPU-powered or GPU-powered machines. compute defaults to auto, so a command that uses an NVENC encoder routes to a GPU on its own. You don’t send anything. Switch one flag to move an encode to the hardware encoder. The GPU path runs h264_nvenc, hevc_nvenc, and av1_nvenc on NVIDIA L4 GPUs, several times faster than software libx264 at the same resolution.
ffmpeg -i input.mp4 -c:v libx264 -preset medium -crf 23 output.mp4
Set compute only to force a choice. gpu pins the job to a GPU; cpu pins it to CPU and rejects an NVENC command with VALIDATION_ERROR.
{
  "type": "ffmpeg",
  "inputs": {},
  "params": {
    "command": "ffmpeg -i https://example.com/video.mp4 -c:v h264_nvenc -preset p5 -cq 23 output.mp4",
    "compute": "gpu"
  }
}
GPU jobs require the Pro plan and bill per second at GPU rates. A GPU job on the Free plan returns 403 PLAN_LIMIT. CPU jobs run on every plan.

Security model

Every command is validated before it runs, then executed in an isolated, network-restricted sandbox.
  • Flag allowlist. Only known-safe flags pass. Anything unrecognised is rejected with VALIDATION_ERROR before dispatch.
  • No reads through filters. Filters and formats that read from disk, fetch over the network, or run external code are blocked.
  • Path containment. Inputs are limited to the files you provide. Path traversal and access to system paths are rejected.
  • Sandboxed execution. Each job runs in an isolated container with a clean environment, restricted I/O, and no ambient network access.

Cost & timeout

Priced per compute second from your credit balance, the wall-clock time FFmpeg ran, excluding file transfer. Typical cost ~$0.05/min. Plan timeouts: 5 min (Free), 15 min (Pro). See plan limits for the full table. Exceeding the timeout returns PROVIDER_TIMEOUT; no credits charged.

Examples

Scale to 720p

{
  "type": "ffmpeg",
  "inputs": {},
  "params": {
    "command": "ffmpeg -i https://example.com/video.mp4 -vf scale=1280:720 -c:v libx264 -preset fast -crf 23 output.mp4"
  }
}

Extract audio

{
  "type": "ffmpeg",
  "inputs": {},
  "params": {
    "command": "ffmpeg -i https://example.com/video.mp4 -vn -c:a aac -b:a 128k output.m4a"
  }
}

Trim 30s starting at 1:00

{
  "type": "ffmpeg",
  "inputs": {},
  "params": {
    "command": "ffmpeg -i https://example.com/video.mp4 -ss 00:01:00 -t 00:00:30 -c:v libx264 -c:a aac output.mp4"
  }
}

Merge video + audio tracks

{
  "type": "ffmpeg",
  "inputs": {},
  "params": {
    "command": "ffmpeg -i https://example.com/video.mp4 -i https://example.com/narration.mp3 -c:v libx264 -c:a aac -map 0:v -map 1:a output.mp4"
  }
}

Output files

The filenames your command writes define the output. You get back every file it produced. Every completed job, FFmpeg or otherwise, returns the same output shape. Job output documents it in full. The short version:
  • output.data: the computed answer (a probe result, a transcript). null for FFmpeg jobs, which only write files.
  • output.file: the headline result you play or download, one file or a stream manifest. null for pure file sets.
  • output.files: every file the job produced, the complete list.
  • output.expiresAt: Unix ms when the URLs expire, present when files is non-empty.
Each file is { url, path, type, size, meta? }. The type is an open enum: video, image, audio, captions, playlist, data, or other. Tolerate values you don’t recognise. A command that writes one file sets output.file to that file, and output.files to a list of one:
{
  "data": {
    "id": "job_abc123",
    "status": "complete",
    "output": {
      "data": null,
      "file": {
        "url": "https://api.rendobar.com/dl/job_abc123?token=<token>",
        "path": "output.mp4",
        "type": "video",
        "size": 4194304,
        "meta": { "format": "mp4", "width": 1280, "height": 720, "durationMs": 30000 }
      },
      "files": [
        {
          "url": "https://api.rendobar.com/dl/job_abc123?token=<token>",
          "path": "output.mp4",
          "type": "video",
          "size": 4194304,
          "meta": { "format": "mp4", "width": 1280, "height": 720, "durationMs": 30000 }
        }
      ],
      "expiresAt": 1735689600000
    }
  }
}
A command that writes a stream (HLS or DASH) sets output.file to the manifest, typed playlist. Point a player at output.file.url directly. output.files carries the manifest plus every segment:
{
  "type": "ffmpeg",
  "inputs": { "video.mp4": "https://example.com/video.mp4" },
  "params": {
    "command": "ffmpeg -i video.mp4 -c:v libx264 -c:a aac -f hls -hls_time 6 -hls_segment_filename seg_%03d.ts master.m3u8"
  }
}
{
  "data": {
    "id": "job_abc123",
    "status": "complete",
    "output": {
      "data": null,
      "file": {
        "url": "https://api.rendobar.com/v/job_abc123/<token>/master.m3u8",
        "path": "master.m3u8",
        "type": "playlist",
        "size": 412
      },
      "files": [
        { "url": "https://api.rendobar.com/v/job_abc123/<token>/master.m3u8", "path": "master.m3u8", "type": "playlist", "size": 412 },
        { "url": "https://api.rendobar.com/v/job_abc123/<token>/seg_000.ts", "path": "seg_000.ts", "type": "video", "size": 1048576 },
        { "url": "https://api.rendobar.com/v/job_abc123/<token>/seg_001.ts", "path": "seg_001.ts", "type": "video", "size": 1041203 }
      ],
      "expiresAt": 1735689600000
    }
  }
}
A command that writes an unordered set (image sequence, -f segment, a resolution ladder) has no single headline result. output.file is null. Read output.files for the list:
{
  "data": {
    "id": "job_abc123",
    "status": "complete",
    "output": {
      "data": null,
      "file": null,
      "files": [
        { "url": "https://api.rendobar.com/v/job_abc123/<token>/frame_000.png", "path": "frame_000.png", "type": "image", "size": 204800 },
        { "url": "https://api.rendobar.com/v/job_abc123/<token>/frame_001.png", "path": "frame_001.png", "type": "image", "size": 205120 },
        { "url": "https://api.rendobar.com/v/job_abc123/<token>/frame_002.png", "path": "frame_002.png", "type": "image", "size": 203904 }
      ],
      "expiresAt": 1735689600000
    }
  }
}
Consuming the result is the same every time. The answer is output.data. The thing you play or download is output.file.url. The full list is output.files. Two invariants hold: output.file is always one of output.files (or null), and output.expiresAt is present whenever output.files is non-empty. URLs expire at output.expiresAt. Re-fetch the job with GET /jobs/{id} to mint fresh ones.

Error handling

A failed job carries an error object with code, message, detail, and retryable. When FFmpeg exits non-zero, error.detail holds the last ~2 KB of the real stderr. Fetch the job to see exactly why FFmpeg stopped.
{
  "data": {
    "id": "job_abc123",
    "status": "failed",
    "error": {
      "code": "PROVIDER_ERROR",
      "message": "FFmpeg process exited with code 1",
      "detail": "[in#0 @ 0x55…] Error opening input: Invalid data found when processing input\nError opening input file clip.mp4.\n",
      "retryable": false
    }
  }
}
A disallowed flag is rejected before dispatch. The request fails with the error envelope, so no job is created:
{ "error": { "code": "VALIDATION_ERROR", "message": "Flag '-protocol_whitelist' is not allowed in FFmpeg commands" } }
Full error catalogue: Error codes.

See also

For a full list of FFmpeg-based operations supported by the API, see rendobar.com/ffmpeg/.