RenderIO
API referenceCommands

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

Video downloads are available during trials and on the Growth plan or higher.

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
401UNAUTHORIZEDMissing or invalid API key.
403FORBIDDENVideo downloads are not enabled for the current plan.
422VALIDATION_ERRORMissing input_urls, invalid key prefixes, invalid URL, output_files missing when ffmpeg_command is set, placeholder mismatch, or metadata exceeds 10 keys.
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