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/jsonAPI 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 Status | Meaning |
|---|---|
400 | Bad request — invalid body, missing fields, bad placeholder syntax |
401 | Missing or invalid API key |
429 | Rate limit exceeded — check Retry-After header |
404 | Command or file not found |
500 | Server error — safe to retry |
Key rules agents must follow
- Use
{{double_braces}}for placeholders — single braces{brace}will fail validation - Input keys must start with
in_, output keys must start without_ - Every key defined in
input_files/output_filesmust appear inffmpeg_command - Status values are always uppercase:
QUEUED,PROCESSING,SUCCESS,FAILED - Access output file URLs via
output_files.out_key.storage_url, not a top-level field - The domain is
renderio.devandrenderio.dev— not any other domain - File size is in
size_mbytes(float), not bytes - 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.