Your First Command
Step-by-step guide to submitting an FFmpeg command or yt-dlp download, polling for results, and downloading output files.
Your First Command
This guide walks you through submitting an FFmpeg command, polling for results, and downloading output files. The same submit-and-poll flow also applies to yt-dlp video downloads. Before you begin, make sure you have your API key ready.
RenderIO processes FFmpeg commands in three steps:
- Submit -- POST your command with input and output file mappings
- Poll -- Check the command status until it completes
- Download -- Retrieve your output files from the storage URLs
Step 1: Submit a command
Send a POST request to the /api/v1/run-ffmpeg-command endpoint.
Request body
{
"input_files": {
"in_video": "https://example.com/sample.mp4"
},
"output_files": {
"out_video": "converted.webm"
},
"ffmpeg_command": "ffmpeg -i {{in_video}} -c:v libvpx-vp9 -crf 30 -b:v 0 {{out_video}}",
"metadata": {
"project": "marketing",
"task": "web-optimization"
},
"webhook_url": "https://your-server.com/webhooks/renderio"
}{
"input_files": {
"in_video": "https://example.com/sample.mp4"
},
"output_files": {
"out_video": "converted.webm"
},
"ffmpeg_command": "ffmpeg -i <<in_video>> -c:v libvpx-vp9 -crf 30 -b:v 0 <<out_video>>",
"metadata": {
"project": "marketing",
"task": "web-optimization"
},
"webhook_url": "https://your-server.com/webhooks/renderio"
}Field reference
| Field | Type | Required | Description |
|---|---|---|---|
input_files | object | Yes | Map of aliases to source URLs. Each key must start with in_ (e.g. in_video, in_audio, in_watermark). Values are publicly accessible URLs to your source files. |
output_files | object | Yes | Map of aliases to output filenames. Each key must start with out_ (e.g. out_video, out_thumbnail). Values are the desired filenames for the output. |
ffmpeg_command | string | Yes | The FFmpeg command to execute. Use {{alias}} or <<alias>> placeholders that match your input and output file keys. RenderIO replaces them with actual file paths at runtime. |
metadata | object | No | Arbitrary key-value pairs (up to 10 properties) attached to the command. Returned in poll responses and webhooks. Useful for tracking jobs in your system. |
webhook_url | string | No | A URL to receive a POST request when this specific command completes. Overrides any account-level webhook configuration for this command. |
Alias rules
- Input file keys must start with
in_(e.g.in_video,in_audio) - Output file keys must start with
out_(e.g.out_video,out_thumb) - Keys must be alphanumeric with underscores only (
[a-zA-Z0-9_]) - Use the same alias names in your
ffmpeg_commandwrapped as{{in_video}}/{{out_video}}or<<in_video>>/<<out_video>>
Response
The API returns immediately with a command_id:
{
"command_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}The FFmpeg processing runs asynchronously in the background. Your client does not need to keep the connection open.
Download a public video instead
Use yt-dlp endpoints when your source is a public platform URL instead of a direct file URL. For example, to download a public YouTube video:
curl -X POST https://renderio.dev/api/v1/ytdlp-download \
-H "Content-Type: application/json" \
-H "X-API-KEY: ffsk_your_api_key_here" \
-d '{
"input_urls": {
"in_video": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
},
"metadata": {
"source": "youtube"
}
}'The response still contains a command_id, and you still poll GET /api/v1/commands/:commandId. When it succeeds, in_video becomes output_files.out_video.storage_url.
To download and process in one call, use POST /api/v1/run-ytdlp-command with input_urls, ffmpeg_command, and output_files.
Step 2: Poll for status
Send a GET request to /api/v1/commands/:commandId to check the status:
curl https://renderio.dev/api/v1/commands/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
-H "X-API-KEY: ffsk_your_api_key_here"Command statuses
| Status | Description |
|---|---|
QUEUED | Command has been received and is waiting to be processed |
PROCESSING | The command is currently running |
SUCCESS | Command completed successfully, output files are available |
FAILED | Command failed, check error_status and error_message for details |
Poll response (SUCCESS)
{
"command_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "SUCCESS",
"command_type": "FFMPEG_COMMAND",
"total_processing_seconds": 4.21,
"ffmpeg_command_run_seconds": 2.87,
"metadata": {
"project": "marketing",
"task": "web-optimization"
},
"original_request": {
"input_files": {
"in_video": "https://example.com/sample.mp4"
},
"output_files": {
"out_video": "converted.webm"
},
"ffmpeg_command": "ffmpeg -i {{in_video}} -c:v libvpx-vp9 -crf 30 -b:v 0 {{out_video}}"
},
"output_files": {
"out_video": {
"file_id": "f1a2b3c4-d5e6-7890-abcd-ef1234567890",
"storage_url": "https://media.renderio.dev/files/f1a2b3c4...",
"status": "STORED",
"rendi_store_type": "OUTPUT",
"is_deleted": false,
"filename": "converted.webm",
"size_mbytes": 2.34,
"duration": 15.0,
"file_type": "video",
"file_format": "webm",
"codec": "vp9",
"mime_type": "video/webm",
"width": 1920,
"height": 1080,
"frame_rate": 30,
"bitrate_video_kb": 1280
}
}
}{
"command_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "SUCCESS",
"command_type": "FFMPEG_COMMAND",
"total_processing_seconds": 4.21,
"ffmpeg_command_run_seconds": 2.87,
"metadata": {
"project": "marketing",
"task": "web-optimization"
},
"original_request": {
"input_files": {
"in_video": "https://example.com/sample.mp4"
},
"output_files": {
"out_video": "converted.webm"
},
"ffmpeg_command": "ffmpeg -i <<in_video>> -c:v libvpx-vp9 -crf 30 -b:v 0 <<out_video>>"
},
"output_files": {
"out_video": {
"file_id": "f1a2b3c4-d5e6-7890-abcd-ef1234567890",
"storage_url": "https://media.renderio.dev/files/f1a2b3c4...",
"status": "STORED",
"rendi_store_type": "OUTPUT",
"is_deleted": false,
"filename": "converted.webm",
"size_mbytes": 2.34,
"duration": 15.0,
"file_type": "video",
"file_format": "webm",
"codec": "vp9",
"mime_type": "video/webm",
"width": 1920,
"height": 1080,
"frame_rate": 30,
"bitrate_video_kb": 1280
}
}
}Poll response (FAILED)
{
"command_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "FAILED",
"command_type": "FFMPEG_COMMAND",
"error_status": "FFMPEG_ERROR",
"error_message": "Invalid codec specified",
"original_request": {
"input_files": { "in_video": "https://example.com/sample.mp4" },
"output_files": { "out_video": "converted.webm" },
"ffmpeg_command": "ffmpeg -i {{in_video}} -c:v invalid_codec {{out_video}}"
}
}{
"command_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "FAILED",
"command_type": "FFMPEG_COMMAND",
"error_status": "FFMPEG_ERROR",
"error_message": "Invalid codec specified",
"original_request": {
"input_files": { "in_video": "https://example.com/sample.mp4" },
"output_files": { "out_video": "converted.webm" },
"ffmpeg_command": "ffmpeg -i <<in_video>> -c:v invalid_codec <<out_video>>"
}
}Step 3: Download output files
Each entry in output_files includes a storage_url. Use it to download the file directly:
curl -o converted.webm "https://media.renderio.dev/files/f1a2b3c4..."The storage_url is a pre-signed URL that provides temporary access to your file.
Full code examples
# Submit
COMMAND_ID=$(curl -s -X POST https://renderio.dev/api/v1/run-ffmpeg-command \
-H "Content-Type: application/json" \
-H "X-API-KEY: $RENDERIO_API_KEY" \
-d '{
"input_files": { "in_video": "https://example.com/sample.mp4" },
"output_files": { "out_video": "converted.webm" },
"ffmpeg_command": "ffmpeg -i {{in_video}} -c:v libvpx-vp9 -crf 30 -b:v 0 {{out_video}}"
}' | jq -r '.command_id')
# Poll
while true; do
RESULT=$(curl -s https://renderio.dev/api/v1/commands/$COMMAND_ID \
-H "X-API-KEY: $RENDERIO_API_KEY")
STATUS=$(echo $RESULT | jq -r '.status')
echo "Status: $STATUS"
if [ "$STATUS" = "SUCCESS" ] || [ "$STATUS" = "FAILED" ]; then break; fi
sleep 2
done
# Download
DOWNLOAD_URL=$(echo $RESULT | jq -r '.output_files.out_video.storage_url')
curl -o converted.webm "$DOWNLOAD_URL"# Submit
COMMAND_ID=$(curl -s -X POST https://renderio.dev/api/v1/run-ffmpeg-command \
-H "Content-Type: application/json" \
-H "X-API-KEY: $RENDERIO_API_KEY" \
-d '{
"input_files": { "in_video": "https://example.com/sample.mp4" },
"output_files": { "out_video": "converted.webm" },
"ffmpeg_command": "ffmpeg -i <<in_video>> -c:v libvpx-vp9 -crf 30 -b:v 0 <<out_video>>"
}' | jq -r '.command_id')
# Poll
while true; do
RESULT=$(curl -s https://renderio.dev/api/v1/commands/$COMMAND_ID \
-H "X-API-KEY: $RENDERIO_API_KEY")
STATUS=$(echo $RESULT | jq -r '.status')
echo "Status: $STATUS"
if [ "$STATUS" = "SUCCESS" ] || [ "$STATUS" = "FAILED" ]; then break; fi
sleep 2
done
# Download
DOWNLOAD_URL=$(echo $RESULT | jq -r '.output_files.out_video.storage_url')
curl -o converted.webm "$DOWNLOAD_URL"import os
import time
import requests
API_KEY = os.environ["RENDERIO_API_KEY"]
BASE_URL = "https://renderio.dev/api/v1"
# 1. Submit the command
submit_response = requests.post(
f"{BASE_URL}/run-ffmpeg-command",
headers={
"Content-Type": "application/json",
"X-API-KEY": API_KEY,
},
json={
"input_files": {"in_video": "https://example.com/sample.mp4"},
"output_files": {"out_video": "converted.webm"},
"ffmpeg_command": "ffmpeg -i {{in_video}} -c:v libvpx-vp9 -crf 30 -b:v 0 {{out_video}}",
},
)
command_id = submit_response.json()["command_id"]
print(f"Command ID: {command_id}")
# 2. Poll for results
while True:
poll_response = requests.get(
f"{BASE_URL}/commands/{command_id}",
headers={"X-API-KEY": API_KEY},
)
result = poll_response.json()
print(f"Status: {result['status']}")
if result["status"] in ("SUCCESS", "FAILED"):
break
time.sleep(2)
# 3. Download the output
if result["status"] == "SUCCESS":
file_url = result["output_files"]["out_video"]["storage_url"]
print(f"Download URL: {file_url}")
download = requests.get(file_url)
with open("converted.webm", "wb") as f:
f.write(download.content)
print("File saved to converted.webm")import os
import time
import requests
API_KEY = os.environ["RENDERIO_API_KEY"]
BASE_URL = "https://renderio.dev/api/v1"
# 1. Submit the command
submit_response = requests.post(
f"{BASE_URL}/run-ffmpeg-command",
headers={
"Content-Type": "application/json",
"X-API-KEY": API_KEY,
},
json={
"input_files": {"in_video": "https://example.com/sample.mp4"},
"output_files": {"out_video": "converted.webm"},
"ffmpeg_command": "ffmpeg -i {{in_video}} -c:v libvpx-vp9 -crf 30 -b:v 0 {{out_video}}",
},
)
command_id = submit_response.json()["command_id"]
print(f"Command ID: {command_id}")
# 2. Poll for results
while True:
poll_response = requests.get(
f"{BASE_URL}/commands/{command_id}",
headers={"X-API-KEY": API_KEY},
)
result = poll_response.json()
print(f"Status: {result['status']}")
if result["status"] in ("SUCCESS", "FAILED"):
break
time.sleep(2)
# 3. Download the output
if result["status"] == "SUCCESS":
file_url = result["output_files"]["out_video"]["storage_url"]
print(f"Download URL: {file_url}")
download = requests.get(file_url)
with open("converted.webm", "wb") as f:
f.write(download.content)
print("File saved to converted.webm")const API_KEY: string = process.env.RENDERIO_API_KEY!;
const BASE_URL = "https://renderio.dev/api/v1";
interface SubmitResponse {
command_id: string;
}
interface OutputFile {
file_id: string;
storage_url: string;
status: string;
filename: string;
size_mbytes: number;
}
interface CommandResult {
command_id: string;
status: "QUEUED" | "PROCESSING" | "SUCCESS" | "FAILED";
output_files: Record<string, OutputFile>;
}
// 1. Submit the command
const submitResponse = await fetch(`${BASE_URL}/run-ffmpeg-command`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-KEY": API_KEY,
},
body: JSON.stringify({
input_files: { in_video: "https://example.com/sample.mp4" },
output_files: { out_video: "converted.webm" },
ffmpeg_command:
"ffmpeg -i {{in_video}} -c:v libvpx-vp9 -crf 30 -b:v 0 {{out_video}}",
}),
});
const { command_id } = (await submitResponse.json()) as SubmitResponse;
console.log("Command ID:", command_id);
// 2. Poll for results
let result: CommandResult;
while (true) {
const pollResponse = await fetch(
`${BASE_URL}/commands/${command_id}`,
{ headers: { "X-API-KEY": API_KEY } },
);
result = (await pollResponse.json()) as CommandResult;
console.log("Status:", result.status);
if (result.status === "SUCCESS" || result.status === "FAILED") break;
await new Promise((resolve) => setTimeout(resolve, 2000));
}
// 3. Download the output
if (result.status === "SUCCESS") {
const fileUrl = result.output_files.out_video.storage_url;
console.log("Download URL:", fileUrl);
}const API_KEY: string = process.env.RENDERIO_API_KEY!;
const BASE_URL = "https://renderio.dev/api/v1";
interface SubmitResponse {
command_id: string;
}
interface OutputFile {
file_id: string;
storage_url: string;
status: string;
filename: string;
size_mbytes: number;
}
interface CommandResult {
command_id: string;
status: "QUEUED" | "PROCESSING" | "SUCCESS" | "FAILED";
output_files: Record<string, OutputFile>;
}
// 1. Submit the command
const submitResponse = await fetch(`${BASE_URL}/run-ffmpeg-command`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-KEY": API_KEY,
},
body: JSON.stringify({
input_files: { in_video: "https://example.com/sample.mp4" },
output_files: { out_video: "converted.webm" },
ffmpeg_command:
"ffmpeg -i <<in_video>> -c:v libvpx-vp9 -crf 30 -b:v 0 <<out_video>>",
}),
});
const { command_id } = (await submitResponse.json()) as SubmitResponse;
console.log("Command ID:", command_id);
// 2. Poll for results
let result: CommandResult;
while (true) {
const pollResponse = await fetch(
`${BASE_URL}/commands/${command_id}`,
{ headers: { "X-API-KEY": API_KEY } },
);
result = (await pollResponse.json()) as CommandResult;
console.log("Status:", result.status);
if (result.status === "SUCCESS" || result.status === "FAILED") break;
await new Promise((resolve) => setTimeout(resolve, 2000));
}
// 3. Download the output
if (result.status === "SUCCESS") {
const fileUrl = result.output_files.out_video.storage_url;
console.log("Download URL:", fileUrl);
}const API_KEY = process.env.RENDERIO_API_KEY;
const BASE_URL = "https://renderio.dev/api/v1";
// 1. Submit the command
const submitResponse = await fetch(`${BASE_URL}/run-ffmpeg-command`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-KEY": API_KEY,
},
body: JSON.stringify({
input_files: { in_video: "https://example.com/sample.mp4" },
output_files: { out_video: "converted.webm" },
ffmpeg_command:
"ffmpeg -i {{in_video}} -c:v libvpx-vp9 -crf 30 -b:v 0 {{out_video}}",
}),
});
const { command_id } = await submitResponse.json();
console.log("Command ID:", command_id);
// 2. Poll for results
let result;
while (true) {
const pollResponse = await fetch(
`${BASE_URL}/commands/${command_id}`,
{ headers: { "X-API-KEY": API_KEY } },
);
result = await pollResponse.json();
console.log("Status:", result.status);
if (result.status === "SUCCESS" || result.status === "FAILED") break;
await new Promise((resolve) => setTimeout(resolve, 2000));
}
// 3. Download the output
if (result.status === "SUCCESS") {
const fileUrl = result.output_files.out_video.storage_url;
console.log("Download URL:", fileUrl);
}const API_KEY = process.env.RENDERIO_API_KEY;
const BASE_URL = "https://renderio.dev/api/v1";
// 1. Submit the command
const submitResponse = await fetch(`${BASE_URL}/run-ffmpeg-command`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-KEY": API_KEY,
},
body: JSON.stringify({
input_files: { in_video: "https://example.com/sample.mp4" },
output_files: { out_video: "converted.webm" },
ffmpeg_command:
"ffmpeg -i <<in_video>> -c:v libvpx-vp9 -crf 30 -b:v 0 <<out_video>>",
}),
});
const { command_id } = await submitResponse.json();
console.log("Command ID:", command_id);
// 2. Poll for results
let result;
while (true) {
const pollResponse = await fetch(
`${BASE_URL}/commands/${command_id}`,
{ headers: { "X-API-KEY": API_KEY } },
);
result = await pollResponse.json();
console.log("Status:", result.status);
if (result.status === "SUCCESS" || result.status === "FAILED") break;
await new Promise((resolve) => setTimeout(resolve, 2000));
}
// 3. Download the output
if (result.status === "SUCCESS") {
const fileUrl = result.output_files.out_video.storage_url;
console.log("Download URL:", fileUrl);
}<?php
$apiKey = getenv("RENDERIO_API_KEY");
$baseUrl = "https://renderio.dev/api/v1";
// 1. Submit the command
$ch = curl_init("$baseUrl/run-ffmpeg-command");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Content-Type: application/json",
"X-API-KEY: $apiKey",
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
"input_files" => ["in_video" => "https://example.com/sample.mp4"],
"output_files" => ["out_video" => "converted.webm"],
"ffmpeg_command" => "ffmpeg -i {{in_video}} -c:v libvpx-vp9 -crf 30 -b:v 0 {{out_video}}",
]));
$submitResult = json_decode(curl_exec($ch), true);
curl_close($ch);
$commandId = $submitResult["command_id"];
echo "Command ID: $commandId\n";
// 2. Poll for results
while (true) {
$ch = curl_init("$baseUrl/commands/$commandId");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-API-KEY: $apiKey"]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
echo "Status: " . $result["status"] . "\n";
if (in_array($result["status"], ["SUCCESS", "FAILED"])) break;
sleep(2);
}
// 3. Download the output
if ($result["status"] === "SUCCESS") {
$fileUrl = $result["output_files"]["out_video"]["storage_url"];
echo "Download URL: $fileUrl\n";
file_put_contents("converted.webm", file_get_contents($fileUrl));
echo "File saved to converted.webm\n";
}<?php
$apiKey = getenv("RENDERIO_API_KEY");
$baseUrl = "https://renderio.dev/api/v1";
// 1. Submit the command
$ch = curl_init("$baseUrl/run-ffmpeg-command");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Content-Type: application/json",
"X-API-KEY: $apiKey",
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
"input_files" => ["in_video" => "https://example.com/sample.mp4"],
"output_files" => ["out_video" => "converted.webm"],
"ffmpeg_command" => "ffmpeg -i <<in_video>> -c:v libvpx-vp9 -crf 30 -b:v 0 <<out_video>>",
]));
$submitResult = json_decode(curl_exec($ch), true);
curl_close($ch);
$commandId = $submitResult["command_id"];
echo "Command ID: $commandId\n";
// 2. Poll for results
while (true) {
$ch = curl_init("$baseUrl/commands/$commandId");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-API-KEY: $apiKey"]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
echo "Status: " . $result["status"] . "\n";
if (in_array($result["status"], ["SUCCESS", "FAILED"])) break;
sleep(2);
}
// 3. Download the output
if ($result["status"] === "SUCCESS") {
$fileUrl = $result["output_files"]["out_video"]["storage_url"];
echo "Download URL: $fileUrl\n";
file_put_contents("converted.webm", file_get_contents($fileUrl));
echo "File saved to converted.webm\n";
}Next steps
- Polling & Webhooks -- choose between polling and webhook notifications
- Command Types -- learn about FFmpeg, chained, batch, and yt-dlp commands
- Download Videos with yt-dlp -- fetch public platform videos with the API
- Error Handling -- handle HTTP errors and command failures
RenderIO API Authentication
Authenticate RenderIO API requests with X-API-KEY headers, manage API keys, handle 401 and 429 errors, and store keys securely.
Polling and Webhooks for FFmpeg Commands
Learn how to track RenderIO FFmpeg command completion with polling or webhooks, including statuses, payloads, signatures, and retries.