Async Processing
RenderIO processes all FFmpeg commands asynchronously. Learn about the submit-and-poll model, command statuses, and how to retrieve results.
Async Processing
RenderIO is fully asynchronous. When you submit an FFmpeg command, the API validates your request and returns a command_id immediately. The actual FFmpeg processing happens in the background. You never wait for FFmpeg to finish on the HTTP connection.
The submit-and-poll model
Every command follows this lifecycle:
Submit command
|
v
Receive command_id (HTTP response, typically < 100ms)
|
v
FFmpeg runs in the background
|
v
Retrieve results via polling or webhookThere are two ways to get results:
- Polling - periodically call
GET /api/v1/commands/{command_id}until the status isSUCCESSorFAILED - Webhooks - configure a webhook URL and RenderIO will POST the result to your server when processing completes
Command statuses
Every command moves through these statuses:
| Status | Description |
|---|---|
QUEUED | Command accepted and waiting to be processed |
PROCESSING | FFmpeg is actively running in a sandbox |
SUCCESS | Completed successfully, output files are available |
FAILED | An error occurred, check error_status and error_message |
Status transitions always move forward: QUEUED -> PROCESSING -> SUCCESS or FAILED. A command never moves backward.
Polling for results
After submitting a command, poll the status endpoint with the returned command_id:
curl https://renderio.dev/api/v1/commands/550e8400-e29b-41d4-a716-446655440000 \
-H "X-API-KEY: ffsk_your_api_key"A successful response looks like:
{
"command_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "SUCCESS",
"command_type": "FFMPEG_COMMAND",
"total_processing_seconds": 4.2,
"ffmpeg_command_run_seconds": 3.1,
"output_files": {
"out_video": {
"file_id": "...",
"storage_url": "https://storage.renderio.dev/...",
"status": "STORED",
"filename": "output.mp4",
"size_mbytes": 12.4,
"duration": 30.5,
"width": 1920,
"height": 1080,
"codec": "h264"
}
},
"original_request": {
"input_files": { "in_video": "https://example.com/input.mp4" },
"output_files": { "out_video": "output.mp4" },
"ffmpeg_command": "-i {{in_video}} -c:v libx264 {{out_video}}"
}
}Polling best practices
- Start at 1-second intervals for short jobs (< 30 seconds expected)
- Use exponential backoff for longer jobs: poll at 1s, 2s, 4s, 8s, etc.
- Set a maximum poll duration to avoid polling indefinitely
- Check for terminal states: stop polling once the status is
SUCCESSorFAILED
Here is a polling example with exponential backoff:
COMMAND_ID="550e8400-e29b-41d4-a716-446655440000"
DELAY=1
MAX_DELAY=30
TIMEOUT=300
START=$(date +%s)
while true; do
NOW=$(date +%s)
ELAPSED=$((NOW - START))
if [ $ELAPSED -ge $TIMEOUT ]; then echo "Polling timed out"; exit 1; fi
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 $DELAY
DELAY=$((DELAY * 2))
if [ $DELAY -gt $MAX_DELAY ]; then DELAY=$MAX_DELAY; fi
done
echo $RESULT | jq .import time
import requests
def poll_command(command_id, api_key):
delay = 1 # Start at 1 second
max_delay = 30 # Cap at 30 seconds
timeout = 300 # Give up after 5 minutes
start = time.time()
while time.time() - start < timeout:
response = requests.get(
f"https://renderio.dev/api/v1/commands/{command_id}",
headers={"X-API-KEY": api_key}
)
data = response.json()
if data["status"] in ("SUCCESS", "FAILED"):
return data
time.sleep(delay)
delay = min(delay * 2, max_delay)
raise TimeoutError("Polling timed out")interface CommandResult {
command_id: string;
status: "QUEUED" | "PROCESSING" | "SUCCESS" | "FAILED";
output_files?: Record<string, { file_id: string; storage_url: string }>;
}
async function pollCommand(commandId: string, apiKey: string): Promise<CommandResult> {
let delay = 1000;
const maxDelay = 30000;
const timeout = 300000;
const start = Date.now();
while (Date.now() - start < timeout) {
const response = await fetch(
`https://renderio.dev/api/v1/commands/${commandId}`,
{ headers: { "X-API-KEY": apiKey } }
);
const data = (await response.json()) as CommandResult;
if (data.status === "SUCCESS" || data.status === "FAILED") {
return data;
}
await new Promise((resolve) => setTimeout(resolve, delay));
delay = Math.min(delay * 2, maxDelay);
}
throw new Error("Polling timed out");
}async function pollCommand(commandId, apiKey) {
let delay = 1000;
const maxDelay = 30000;
const timeout = 300000;
const start = Date.now();
while (Date.now() - start < timeout) {
const response = await fetch(
`https://renderio.dev/api/v1/commands/${commandId}`,
{ headers: { "X-API-KEY": apiKey } }
);
const data = await response.json();
if (data.status === "SUCCESS" || data.status === "FAILED") {
return data;
}
await new Promise((resolve) => setTimeout(resolve, delay));
delay = Math.min(delay * 2, maxDelay);
}
throw new Error("Polling timed out");
}<?php
function pollCommand(string $commandId, string $apiKey): array {
$delay = 1;
$maxDelay = 30;
$timeout = 300;
$start = time();
while (time() - $start < $timeout) {
$ch = curl_init("https://renderio.dev/api/v1/commands/$commandId");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-API-KEY: $apiKey"]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);
if (in_array($data["status"], ["SUCCESS", "FAILED"])) {
return $data;
}
sleep($delay);
$delay = min($delay * 2, $maxDelay);
}
throw new Exception("Polling timed out");
}Using webhooks instead of polling
If you prefer push-based notifications, configure a webhook URL either globally (via the webhook config endpoint) or per-command (via the webhook_url field in your request body). When the command completes, RenderIO sends a POST request to your URL with the full command result.
curl -X POST https://renderio.dev/api/v1/run-ffmpeg-command \
-H "X-API-KEY: ffsk_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"input_files": { "in_video": "https://example.com/input.mp4" },
"output_files": { "out_video": "output.mp4" },
"ffmpeg_command": "-i {{in_video}} -c:v libx264 {{out_video}}",
"webhook_url": "https://your-server.com/webhooks/renderio"
}'The webhook payload contains the same data as a poll response, wrapped with a timestamp:
{
"data": {
"command_id": "...",
"status": "SUCCESS",
"output_files": { ... }
},
"timestamp": 1700000000000
}Timeouts
Command timeout is determined by your subscription plan. If FFmpeg does not finish within this window, the command is marked as FAILED.
Background execution
Under the hood, RenderIO uses Cloudflare Workers' waitUntil to run FFmpeg processing in the background. This is why your initial HTTP request returns in milliseconds -- the response is sent before processing begins. The Worker continues running the sandbox, file downloads, FFmpeg execution, and file uploads after your connection closes.
Related pages
- Polling & Webhooks guide -- step-by-step setup for both result delivery methods
- Error Handling -- handle timeouts, command failures, and webhook delivery errors