Download + Process Video (yt-dlp)
API reference for downloading a public video via yt-dlp and optionally post-processing it with an FFmpeg command in the same async job.
Download + Process Video (yt-dlp)
POST /api/v1/run-ytdlp-commandDownload one or more publicly accessible videos via yt-dlp, then optionally pipe the output through an FFmpeg command. Use this when you need to transcode, trim, extract audio, or otherwise process the video immediately after downloading.
If you only need the raw download with no post-processing, use the simpler POST /api/v1/ytdlp-download endpoint instead.
Only public content is supported — no DRM bypass, no private accounts, no paywalled content.
Authentication
Requires API key via X-API-KEY header.
Video downloads are available during trials and on the Growth plan or higher.
Request
Headers
| Header | Type | Required | Description |
|---|---|---|---|
Content-Type | string | Yes | Must be application/json |
X-API-KEY | string | Yes | Your API key with ffsk_ prefix |
Body
interface RunYtDlpCommandRequest {
input_urls: Record<string, string>; // in_* aliases mapped to public video URLs
ffmpeg_command?: string; // FFmpeg command with {{alias}} placeholders
output_files?: Record<string, string>; // out_* aliases mapped to output filenames (required if ffmpeg_command set)
metadata?: Record<string, string | number | boolean>; // Max 10 keys
}| Field | Type | Required | Description |
|---|---|---|---|
input_urls | Record<string, string> | Yes | Map of alias names (must start with in_) to public video URLs. |
ffmpeg_command | string | No | FFmpeg command to run after download. Use {{alias}} placeholders to reference input_urls keys and output_files keys. If omitted, behaves like a simple download. |
output_files | Record<string, string> | Conditional | Map of alias names (must start with out_) to output filenames. Required when ffmpeg_command is provided. |
metadata | Record<string, string | number | boolean> | No | Arbitrary key-value metadata. Maximum 10 keys. |
Placeholder syntax
Use {{double_braces}} in ffmpeg_command to reference files:
{
"input_urls": { "in_video": "https://www.tiktok.com/@user/video/123" },
"ffmpeg_command": "-i {{in_video}} -vn -acodec libmp3lame -ab 192k {{out_audio}}",
"output_files": { "out_audio": "audio.mp3" }
}{
"input_urls": { "in_video": "https://www.tiktok.com/@user/video/123" },
"ffmpeg_command": "-i <<in_video>> -vn -acodec libmp3lame -ab 192k <<out_audio>>",
"output_files": { "out_audio": "audio.mp3" }
}Response
200 OK
{
command_id: string;
}| Field | Type | Description |
|---|---|---|
command_id | string | Unique identifier for the command. Use this to poll for status. |
Getting the output file URL
Poll GET /api/v1/commands/:commandId until status is SUCCESS.
- With
ffmpeg_command: output URL is atresult.output_files.<your_out_key>.storage_url - Without
ffmpeg_command: downloaded file is atresult.output_files.out_<suffix>.storage_url, where<suffix>matches yourin_<suffix>input key
Error responses
| Status | Error | Description |
|---|---|---|
401 | UNAUTHORIZED | Missing or invalid API key. |
403 | FORBIDDEN | Video downloads are not enabled for the current plan. |
422 | VALIDATION_ERROR | Missing input_urls, invalid key prefixes, invalid URL, output_files missing when ffmpeg_command is set, placeholder mismatch, or metadata exceeds 10 keys. |
429 | RATE_LIMITED | Too many requests. Retry after the period indicated in the Retry-After header. |
501 | NOT_IMPLEMENTED | yt-dlp backend unavailable. Retry after a short delay. |
Examples
# Download a YouTube video and extract audio as MP3
curl -X POST https://renderio.dev/api/v1/run-ytdlp-command \
-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"
},
"ffmpeg_command": "-i {{in_video}} -vn -acodec libmp3lame -ab 192k {{out_audio}}",
"output_files": {
"out_audio": "audio.mp3"
}
}'# Download a YouTube video and extract audio as MP3
curl -X POST https://renderio.dev/api/v1/run-ytdlp-command \
-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"
},
"ffmpeg_command": "-i <<in_video>> -vn -acodec libmp3lame -ab 192k <<out_audio>>",
"output_files": {
"out_audio": "audio.mp3"
}
}'import os, time, requests
API_KEY = os.environ["RENDERIO_API_KEY"]
BASE = "https://renderio.dev"
# Download TikTok and resize to 9:16 portrait
res = requests.post(
f"{BASE}/api/v1/run-ytdlp-command",
headers={"X-API-KEY": API_KEY, "Content-Type": "application/json"},
json={
"input_urls": {"in_video": "https://www.tiktok.com/@user/video/7123456789"},
"ffmpeg_command": (
"-i {{in_video}} "
"-vf scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:-1:-1 "
"{{out_reel}}"
),
"output_files": {"out_reel": "reel-9x16.mp4"},
},
)
res.raise_for_status()
command_id = res.json()["command_id"]
# Poll until done
while True:
time.sleep(2)
result = requests.get(
f"{BASE}/api/v1/commands/{command_id}",
headers={"X-API-KEY": API_KEY},
).json()
if result["status"] == "SUCCESS":
print(result["output_files"]["out_reel"]["storage_url"])
break
if result["status"] == "FAILED":
raise RuntimeError(result.get("error"))import os, time, requests
API_KEY = os.environ["RENDERIO_API_KEY"]
BASE = "https://renderio.dev"
# Download TikTok and resize to 9:16 portrait
res = requests.post(
f"{BASE}/api/v1/run-ytdlp-command",
headers={"X-API-KEY": API_KEY, "Content-Type": "application/json"},
json={
"input_urls": {"in_video": "https://www.tiktok.com/@user/video/7123456789"},
"ffmpeg_command": (
"-i {{in_video}} "
"-vf scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:-1:-1 "
"{{out_reel}}"
),
"output_files": {"out_reel": "reel-9x16.mp4"},
},
)
res.raise_for_status()
command_id = res.json()["command_id"]
# Poll until done
while True:
time.sleep(2)
result = requests.get(
f"{BASE}/api/v1/commands/{command_id}",
headers={"X-API-KEY": API_KEY},
).json()
if result["status"] == "SUCCESS":
print(result["output_files"]["out_reel"]["storage_url"])
break
if result["status"] == "FAILED":
raise RuntimeError(result.get("error"))const API_KEY = process.env.RENDERIO_API_KEY!;
const BASE = "https://renderio.dev";
interface CommandResult {
status: "QUEUED" | "PROCESSING" | "SUCCESS" | "FAILED";
output_files: Record<string, { storage_url: string | null }>;
error?: string;
}
async function downloadAndProcess(videoUrl: string): Promise<string> {
const submitRes = await fetch(`${BASE}/api/v1/run-ytdlp-command`, {
method: "POST",
headers: { "X-API-KEY": API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
input_urls: { in_video: videoUrl },
ffmpeg_command: "-i {{in_video}} -vn -acodec libmp3lame -ab 192k {{out_audio}}",
output_files: { out_audio: "audio.mp3" },
}),
});
if (!submitRes.ok) throw new Error(`Submit failed: ${await submitRes.text()}`);
const { command_id } = await submitRes.json();
while (true) {
await new Promise((r) => setTimeout(r, 2000));
const pollRes = await fetch(`${BASE}/api/v1/commands/${command_id}`, {
headers: { "X-API-KEY": API_KEY },
});
const result: CommandResult = await pollRes.json();
if (result.status === "SUCCESS") return result.output_files.out_audio.storage_url!;
if (result.status === "FAILED") throw new Error(result.error);
}
}
const mp3Url = await downloadAndProcess("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
console.log("MP3:", mp3Url);const API_KEY = process.env.RENDERIO_API_KEY!;
const BASE = "https://renderio.dev";
interface CommandResult {
status: "QUEUED" | "PROCESSING" | "SUCCESS" | "FAILED";
output_files: Record<string, { storage_url: string | null }>;
error?: string;
}
async function downloadAndProcess(videoUrl: string): Promise<string> {
const submitRes = await fetch(`${BASE}/api/v1/run-ytdlp-command`, {
method: "POST",
headers: { "X-API-KEY": API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
input_urls: { in_video: videoUrl },
ffmpeg_command: "-i <<in_video>> -vn -acodec libmp3lame -ab 192k <<out_audio>>",
output_files: { out_audio: "audio.mp3" },
}),
});
if (!submitRes.ok) throw new Error(`Submit failed: ${await submitRes.text()}`);
const { command_id } = await submitRes.json();
while (true) {
await new Promise((r) => setTimeout(r, 2000));
const pollRes = await fetch(`${BASE}/api/v1/commands/${command_id}`, {
headers: { "X-API-KEY": API_KEY },
});
const result: CommandResult = await pollRes.json();
if (result.status === "SUCCESS") return result.output_files.out_audio.storage_url!;
if (result.status === "FAILED") throw new Error(result.error);
}
}
const mp3Url = await downloadAndProcess("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
console.log("MP3:", mp3Url);const API_KEY = process.env.RENDERIO_API_KEY;
const BASE = "https://renderio.dev";
async function downloadAndProcess(videoUrl) {
const submitRes = await fetch(`${BASE}/api/v1/run-ytdlp-command`, {
method: "POST",
headers: { "X-API-KEY": API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
input_urls: { in_video: videoUrl },
ffmpeg_command: "-i {{in_video}} -vn -acodec libmp3lame -ab 192k {{out_audio}}",
output_files: { out_audio: "audio.mp3" },
}),
});
const { command_id } = await submitRes.json();
while (true) {
await new Promise((r) => setTimeout(r, 2000));
const result = await fetch(`${BASE}/api/v1/commands/${command_id}`, {
headers: { "X-API-KEY": API_KEY },
}).then((r) => r.json());
if (result.status === "SUCCESS") return result.output_files.out_audio.storage_url;
if (result.status === "FAILED") throw new Error(result.error);
}
}
const mp3Url = await downloadAndProcess("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
console.log("MP3:", mp3Url);const API_KEY = process.env.RENDERIO_API_KEY;
const BASE = "https://renderio.dev";
async function downloadAndProcess(videoUrl) {
const submitRes = await fetch(`${BASE}/api/v1/run-ytdlp-command`, {
method: "POST",
headers: { "X-API-KEY": API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
input_urls: { in_video: videoUrl },
ffmpeg_command: "-i <<in_video>> -vn -acodec libmp3lame -ab 192k <<out_audio>>",
output_files: { out_audio: "audio.mp3" },
}),
});
const { command_id } = await submitRes.json();
while (true) {
await new Promise((r) => setTimeout(r, 2000));
const result = await fetch(`${BASE}/api/v1/commands/${command_id}`, {
headers: { "X-API-KEY": API_KEY },
}).then((r) => r.json());
if (result.status === "SUCCESS") return result.output_files.out_audio.storage_url;
if (result.status === "FAILED") throw new Error(result.error);
}
}
const mp3Url = await downloadAndProcess("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
console.log("MP3:", mp3Url);Common recipes
Download only (no post-processing)
Omit ffmpeg_command and output_files — the endpoint downloads and stores the video as-is:
{
"input_urls": { "in_video": "https://www.instagram.com/reel/..." }
}Output URL: result.output_files.out_video.storage_url
Extract audio as MP3
{
"input_urls": { "in_video": "https://www.youtube.com/watch?v=..." },
"ffmpeg_command": "-i {{in_video}} -vn -acodec libmp3lame -ab 192k {{out_audio}}",
"output_files": { "out_audio": "audio.mp3" }
}{
"input_urls": { "in_video": "https://www.youtube.com/watch?v=..." },
"ffmpeg_command": "-i <<in_video>> -vn -acodec libmp3lame -ab 192k <<out_audio>>",
"output_files": { "out_audio": "audio.mp3" }
}Resize to vertical 9:16
{
"input_urls": { "in_video": "https://www.tiktok.com/@user/video/..." },
"ffmpeg_command": "-i {{in_video}} -vf scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:-1:-1 {{out_reel}}",
"output_files": { "out_reel": "reel-9x16.mp4" }
}{
"input_urls": { "in_video": "https://www.tiktok.com/@user/video/..." },
"ffmpeg_command": "-i <<in_video>> -vf scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:-1:-1 <<out_reel>>",
"output_files": { "out_reel": "reel-9x16.mp4" }
}Trim to first 30 seconds
{
"input_urls": { "in_video": "https://www.youtube.com/watch?v=..." },
"ffmpeg_command": "-i {{in_video}} -t 30 -c copy {{out_clip}}",
"output_files": { "out_clip": "clip.mp4" }
}{
"input_urls": { "in_video": "https://www.youtube.com/watch?v=..." },
"ffmpeg_command": "-i <<in_video>> -t 30 -c copy <<out_clip>>",
"output_files": { "out_clip": "clip.mp4" }
}Related docs
Download Video (yt-dlp)
API reference for downloading a publicly accessible video from YouTube, TikTok, Instagram, X/Twitter, Reddit, Vimeo, Twitch, Facebook, and other yt-dlp-supported platforms.
Run Chained Commands
Submit up to 10 FFmpeg commands that execute sequentially, with outputs from earlier steps available as inputs to later steps.