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 would run locally. The inputs map stages source files into the working directory by name. The command references them by bare name, and inline -i URLs also work.
Accepts: video, audio, image
From a shell? Use the CLI: rb ffmpeg -i input.mp4 -vf scale=1280:720 out.mp4. Local files upload automatically.

Request

import { createClient, outputUrl } from "@rendobar/sdk";

const client = createClient({ apiKey: "rb_YOUR_KEY" });

const job = await client.jobs.create({
  type: "ffmpeg",
  params: {
    command: "ffmpeg -i https://cdn.rendobar.com/assets/examples/sample.mp4 -vf scale=1280:720 -c:v libx264 -crf 23 -preset fast output.mp4",
  },
});

const result = await client.jobs.wait(job.id);
console.log(outputUrl(result));

How it runs

  1. Validate: command parsed against 8 security layers (flag whitelist, format/filter blocklists)
  2. Download: input URLs pulled to a sandboxed runner
  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 is 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 cannot go inline: a file a filter reads by name (see below), inline text you do not 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 do not 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 are not 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 are 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

inputs
object
default:"{}"
Working directory for the command. Optional, defaults to an empty map. Each key is a path-safe filename staged under that exact name. Each value is the source for that file. Omit it when every input is an inline -i URL. A value is one of:Filters that read a file by name (subtitles=, lut3d=, drawtext fontfile=, concat lists) must get that file through inputs. They are not -i arguments. See Input sources.
command
string
required
Real FFmpeg command starting with ffmpeg. Input URLs go in -i positions, or reference staged inputs files by name.
outputFormat
enum
default:"inferred"
Container format. Inferred from the trailing filename if omitted. One of: mp4, mkv, webm, mov, avi, ts, gif, png, jpg, mp3, wav, flac, ogg, aac, opus, m4a, srt, vtt.
timeout
integer
default:"120"
Max execution time in seconds. Range 1–900. Plan caps apply (Free 5 min, Pro 15 min).
compute
enum
default:"auto"
Which machine class runs the job. One of auto, cpu, gpu.
  • auto (default): a command using an NVENC or CUDA encoder, like h264_nvenc, routes to a GPU. Everything else runs on CPU. You do not set anything.
  • gpu: force a GPU machine.
  • cpu: force a CPU machine. A command that calls an NVENC encoder is rejected with VALIDATION_ERROR.
GPU jobs run the hardware encoders h264_nvenc, hevc_nvenc, and av1_nvenc on NVIDIA L4 GPUs, billed per second. gpu and auto-routed GPU jobs require the Pro plan. A GPU job on the Free plan returns 403 PLAN_LIMIT. cpu and auto work on every plan. 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 do not 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",
  "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. Plan timeouts: 5 min (Free), 15 min (Pro). See plan limits for the full table. Exceeding the timeout returns RUNNER_TIMEOUT, and no credits are charged.

Examples

Scale to 720p

{
  "type": "ffmpeg",
  "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",
  "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",
  "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",
  "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 do not 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": "RUNNER_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

  • Job output: the output shape every job returns
  • CLI: run the same job from your terminal with rb ffmpeg
  • Webhooks: receive job.completed instead of polling
  • Credits and billing: per-compute-second pricing detail
  • Plan limits: file-size caps, timeouts, and concurrency per plan
  • Job lifecycle: how a job moves from dispatched to complete
For a full list of FFmpeg-based operations supported by the API, see rendobar.com/ffmpeg/.