RenderIO

RenderIO for AI Coding Agents

Concise API reference for LLMs and coding agents. Everything needed to use RenderIO (FFmpeg-as-a-Service) without reading full documentation.

RenderIO for AI Coding Agents

This page is optimized for LLMs and coding agents. It contains everything you need to use RenderIO — no other pages required.

What RenderIO is

RenderIO is an FFmpeg-as-a-Service REST API. You send an FFmpeg command over HTTP; RenderIO runs it in a secure cloud sandbox, stores the output files, and returns metadata. You do not install FFmpeg or manage servers.

Base URL and authentication

Base URL: https://renderio.dev
Auth header: X-API-KEY: ffsk_your_api_key_here
Content-Type: application/json

API keys use the ffsk_ prefix. Pass the key on every request via the X-API-KEY header.

Core concept: placeholders

FFmpeg commands use {{double_brace}} placeholders that map to input/output file keys:

  • Input keys must start with in_
  • Output keys must start with out_
  • Every placeholder in the command must match a defined key
  • Every defined key must appear as a placeholder in the command
{
  "ffmpeg_command": "-i {{in_video}} -c:v libx264 {{out_video}}",
  "input_files": { "in_video": "https://example.com/input.mp4" },
  "output_files": { "out_video": "result.mp4" }
}

Endpoints

POST /api/v1/run-ffmpeg-command

Run a single FFmpeg command. Returns immediately with a command_id. Processing is async.

Request:

{
  "ffmpeg_command": "-i {{in_video}} -vf scale=1280:720 -c:v libx264 -crf 23 {{out_video}}",
  "input_files": {
    "in_video": "https://example.com/input.mp4"
  },
  "output_files": {
    "out_video": "output.mp4"
  }
}

Response (202):

{
  "command_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}

GET /api/v1/commands/:commandId

Poll for status and results. Call this until status is SUCCESS or FAILED.

Response (SUCCESS):

{
  "command_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "SUCCESS",
  "command_type": "FFMPEG_COMMAND",
  "total_processing_seconds": 3.42,
  "ffmpeg_command_run_seconds": 2.18,
  "original_request": {
    "ffmpeg_command": "-i {{in_video}} -vf scale=1280:720 {{out_video}}",
    "input_files": { "in_video": "https://example.com/input.mp4" },
    "output_files": { "out_video": "output.mp4" }
  },
  "output_files": {
    "out_video": {
      "file_id": "f1a2b3c4-d5e6-7890-abcd-ef1234567890",
      "storage_url": "https://media.renderio.dev/files/f1a2b3c4...",
      "status": "STORED",
      "filename": "output.mp4",
      "size_mbytes": 2.48,
      "duration": 12.5,
      "codec": "h264",
      "width": 1280,
      "height": 720
    }
  }
}

Status values: QUEUED | PROCESSING | SUCCESS | FAILED (always uppercase)

Output file URL: output_files.out_video.storage_url (not a direct URL in the response root)

POST /api/v1/run-chained-ffmpeg-commands

Run up to 10 FFmpeg commands sequentially. Output of one command can be input of the next.

Request:

{
  "commands": [
    {
      "ffmpeg_command": "-i {{in_video}} -vf scale=1280:720 {{out_resized}}",
      "input_files": { "in_video": "https://example.com/input.mp4" },
      "output_files": { "out_resized": "resized.mp4" }
    },
    {
      "ffmpeg_command": "-i {{in_resized}} -c:v libx264 -crf 28 {{out_compressed}}",
      "input_files": { "in_resized": "{{out_resized}}" },
      "output_files": { "out_compressed": "final.mp4" }
    }
  ]
}

Response: Same shape as single command — one command_id, poll the same endpoint.

POST /api/v1/run-multiple-ffmpeg-commands

Run up to 10 FFmpeg commands in parallel. Each command is independent.

Request:

{
  "commands": [
    {
      "ffmpeg_command": "-i {{in_video}} -vf scale=1920:1080 {{out_1080p}}",
      "input_files": { "in_video": "https://example.com/input.mp4" },
      "output_files": { "out_1080p": "1080p.mp4" }
    },
    {
      "ffmpeg_command": "-i {{in_video}} -vf scale=1280:720 {{out_720p}}",
      "input_files": { "in_video": "https://example.com/input.mp4" },
      "output_files": { "out_720p": "720p.mp4" }
    }
  ]
}

POST /api/v1/files/upload

Upload a local file. Returns a file_id and a URL you can use as an input.

Request: multipart/form-data with a file field.

Response:

{
  "file_id": "f1a2b3c4-d5e6-7890-abcd-ef1234567890",
  "storage_url": "https://media.renderio.dev/files/f1a2b3c4...",
  "filename": "upload.mp4",
  "size_mbytes": 14.2
}

Use storage_url as the value in input_files.

POST /api/v1/files/store-file

Store a remote file by URL. Useful when the source URL is temporary and you want a persistent RenderIO-hosted copy.

Request:

{
  "url": "https://example.com/video.mp4",
  "filename": "stored-video.mp4"
}

Response: Same shape as file upload.

GET /api/v1/files/:fileId

Get file metadata and download URL.

GET /api/v1/files

List stored files. Supports limit and offset query params.

GET /api/v1/commands

List commands. Supports limit, offset, status query params.

Complete polling example (TypeScript)

const RENDERIO_API_KEY = process.env.RENDERIO_API_KEY!;
const BASE_URL = "https://renderio.dev";

async function runFFmpeg(videoUrl: string): Promise<string> {
  // 1. Submit
  const submitRes = await fetch(`${BASE_URL}/api/v1/run-ffmpeg-command`, {
    method: "POST",
    headers: {
      "X-API-KEY": RENDERIO_API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      ffmpeg_command: "-i {{in_video}} -vf scale=1280:720 -c:v libx264 -crf 23 {{out_video}}",
      input_files: { in_video: videoUrl },
      output_files: { out_video: "output.mp4" },
    }),
  });

  const { command_id } = await submitRes.json();

  // 2. Poll until done
  while (true) {
    await new Promise((r) => setTimeout(r, 2000));

    const pollRes = await fetch(`${BASE_URL}/api/v1/commands/${command_id}`, {
      headers: { "X-API-KEY": RENDERIO_API_KEY },
    });

    const result = await pollRes.json();

    if (result.status === "SUCCESS") {
      return result.output_files.out_video.storage_url;
    }

    if (result.status === "FAILED") {
      throw new Error(`FFmpeg failed: ${result.error ?? "unknown error"}`);
    }
    // QUEUED or PROCESSING — keep polling
  }
}

Complete polling example (Python)

import os
import time
import requests

RENDERIO_API_KEY = os.environ["RENDERIO_API_KEY"]
BASE_URL = "https://renderio.dev"

def run_ffmpeg(video_url: str) -> str:
    # 1. Submit
    res = requests.post(
        f"{BASE_URL}/api/v1/run-ffmpeg-command",
        headers={"X-API-KEY": RENDERIO_API_KEY},
        json={
            "ffmpeg_command": "-i {{in_video}} -vf scale=1280:720 -c:v libx264 -crf 23 {{out_video}}",
            "input_files": {"in_video": video_url},
            "output_files": {"out_video": "output.mp4"},
        },
    )
    command_id = res.json()["command_id"]

    # 2. Poll until done
    while True:
        time.sleep(2)
        result = requests.get(
            f"{BASE_URL}/api/v1/commands/{command_id}",
            headers={"X-API-KEY": RENDERIO_API_KEY},
        ).json()

        if result["status"] == "SUCCESS":
            return result["output_files"]["out_video"]["storage_url"]

        if result["status"] == "FAILED":
            raise RuntimeError(f"FFmpeg failed: {result.get('error', 'unknown')}")

Webhooks

Instead of polling, configure a webhook to receive a POST when processing completes.

Configure:

curl -X PUT https://renderio.dev/api/v1/webhook-config \
  -H "X-API-KEY: ffsk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://your-server.com/webhook"}'

Payload your server receives:

{
  "data": {
    "command_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "status": "SUCCESS",
    "output_files": {
      "out_video": {
        "storage_url": "https://media.renderio.dev/files/...",
        "filename": "output.mp4",
        "size_mbytes": 2.48
      }
    }
  },
  "timestamp": 1712345678000
}

Webhooks retry with exponential backoff. After 8 failures, the webhook is auto-disabled.

Common FFmpeg recipes

# Convert MP4 → WebM
-i {{in_video}} -c:v libvpx-vp9 -crf 30 -b:v 0 {{out_video}}

# Extract audio as MP3
-i {{in_video}} -vn -acodec libmp3lame -ab 192k {{out_audio}}

# Resize to 1280x720
-i {{in_video}} -vf scale=1280:720 -c:v libx264 -crf 23 {{out_video}}

# Compress video (reduce file size)
-i {{in_video}} -c:v libx264 -crf 28 -preset slow {{out_video}}

# Generate thumbnail at 5 seconds
-i {{in_video}} -ss 5 -vframes 1 {{out_thumb}}

# Add watermark image
-i {{in_video}} -i {{in_logo}} -filter_complex "overlay=10:10" {{out_video}}

# Convert to GIF
-i {{in_video}} -vf "fps=12,scale=480:-1:flags=lanczos" {{out_gif}}

# Trim (from 10s to 30s)
-i {{in_video}} -ss 10 -to 30 -c copy {{out_video}}

# Mute video
-i {{in_video}} -an -c:v copy {{out_video}}

# Stack two videos side by side
-i {{in_left}} -i {{in_right}} -filter_complex "[0:v][1:v]hstack=inputs=2" {{out_video}}

Error handling

All errors return a consistent shape:

{
  "error": "INVALID_COMMAND",
  "message": "Human-readable description of what went wrong"
}
HTTP StatusMeaning
400Bad request — invalid body, missing fields, bad placeholder syntax
401Missing or invalid API key
429Rate limit exceeded — check Retry-After header
404Command or file not found
500Server error — safe to retry

Key rules agents must follow

  1. Use {{double_braces}} for placeholders — single braces {brace} will fail validation
  2. Input keys must start with in_, output keys must start with out_
  3. Every key defined in input_files/output_files must appear in ffmpeg_command
  4. Status values are always uppercase: QUEUED, PROCESSING, SUCCESS, FAILED
  5. Access output file URLs via output_files.out_key.storage_url, not a top-level field
  6. The domain is renderio.dev and renderio.dev — not any other domain
  7. File size is in size_mbytes (float), not bytes
  8. Processing time is in total_processing_seconds (float)

Presets (reusable command templates)

Create a preset once, execute it many times with different inputs.

Create:

POST /api/v1/presets
{
  "name": "resize-720p",
  "ffmpeg_command": "-i {{in_video}} -vf scale=1280:720 -c:v libx264 -crf 23 {{out_video}}",
  "input_files": { "in_video": "" },
  "output_files": { "out_video": "output-720p.mp4" }
}

Execute:

POST /api/v1/presets/:presetId/execute
{
  "input_files": { "in_video": "https://example.com/my-video.mp4" }
}

Returns a command_id — poll the same way as a regular command.

Get your API key

Get a free API key at renderio.dev/get-api-key — no credit card required to start.

On this page