RenderIO

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-command

Download 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

HeaderTypeRequiredDescription
Content-TypestringYesMust be application/json
X-API-KEYstringYesYour 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
}
FieldTypeRequiredDescription
input_urlsRecord<string, string>YesMap of alias names (must start with in_) to public video URLs.
ffmpeg_commandstringNoFFmpeg 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_filesRecord<string, string>ConditionalMap of alias names (must start with out_) to output filenames. Required when ffmpeg_command is provided.
metadataRecord<string, string | number | boolean>NoArbitrary 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;
}
FieldTypeDescription
command_idstringUnique 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 at result.output_files.<your_out_key>.storage_url
  • Without ffmpeg_command: downloaded file is at result.output_files.out_<suffix>.storage_url, where <suffix> matches your in_<suffix> input key

Error responses

StatusErrorDescription
400INVALID_REQUESTMissing input_urls, invalid key prefixes, or output_files missing when ffmpeg_command is set.
401UNAUTHORIZEDMissing or invalid API key.
422VALIDATION_ERRORInput validation failed (e.g., metadata exceeds 10 keys, invalid URL).
429RATE_LIMITEDToo many requests. Retry after the period indicated in the Retry-After header.
501NOT_IMPLEMENTEDyt-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" }
}

On this page