Download + Process Video (yt-dlp)
Download a public video via yt-dlp then optionally post-process it with an FFmpeg command. Returns a command ID to poll for results.
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.
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 |
|---|---|---|
400 | INVALID_REQUEST | Missing input_urls, invalid key prefixes, or output_files missing when ffmpeg_command is set. |
401 | UNAUTHORIZED | Missing or invalid API key. |
422 | VALIDATION_ERROR | Input validation failed (e.g., metadata exceeds 10 keys, invalid URL). |
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" }
}