RenderIO

Download Video (yt-dlp)

Download a publicly accessible video from YouTube, TikTok, Instagram, Reddit, Vimeo, Twitch, and other yt-dlp-supported platforms. Returns a command ID to poll for the download URL.

Download Video (yt-dlp)

POST /api/v1/ytdlp-download

Download a publicly accessible video using yt-dlp. RenderIO fetches the video from the source platform, stores it in managed R2 storage, and returns a signed URL. The endpoint returns immediately with a command_id that you poll for the result.

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 YtDlpDownloadRequest {
  input_urls: Record<string, string>;  // in_* aliases mapped to public video URLs
  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. Each URL is downloaded independently.
metadataRecord<string, string | number | boolean>NoArbitrary key-value metadata attached to the command. Maximum 10 keys.

This endpoint does not accept ffmpeg_command or output_files. To post-process the downloaded video, use POST /api/v1/run-ytdlp-command instead.

Response

200 OK

{
  command_id: string;
}
FieldTypeDescription
command_idstringUnique identifier for the command. Use this to poll for status.

Getting the downloaded file URL

Poll GET /api/v1/commands/:commandId until status is SUCCESS, then access the file at:

result.output_files.out_<suffix>.storage_url

The downloaded file is keyed as out_<suffix> where <suffix> matches your in_<suffix> input key. For example, in_videooutput_files.out_video.storage_url.

Error responses

StatusErrorDescription
400INVALID_REQUESTMissing input_urls, invalid in_ key prefix, or ffmpeg_command/output_files included (not allowed on this endpoint).
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

curl -X POST https://renderio.dev/api/v1/ytdlp-download \
  -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"
    },
    "metadata": {
      "source": "youtube",
      "project": "archive"
    }
  }'
import os, time, requests

API_KEY = os.environ["RENDERIO_API_KEY"]
BASE = "https://renderio.dev"

# Submit download
res = requests.post(
    f"{BASE}/api/v1/ytdlp-download",
    headers={"X-API-KEY": API_KEY, "Content-Type": "application/json"},
    json={
        "input_urls": {"in_video": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"},
        "metadata": {"source": "youtube"},
    },
)
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":
        # input key in_video → output key out_video
        print(result["output_files"]["out_video"]["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 {
  command_id: string;
  status: "QUEUED" | "PROCESSING" | "SUCCESS" | "FAILED";
  output_files: Record<string, { storage_url: string | null; status: string }>;
  error?: string;
}

async function downloadVideo(url: string): Promise<string> {
  const submitRes = await fetch(`${BASE}/api/v1/ytdlp-download`, {
    method: "POST",
    headers: { "X-API-KEY": API_KEY, "Content-Type": "application/json" },
    body: JSON.stringify({ input_urls: { in_video: url } }),
  });

  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();
    // input key in_video → output key out_video
    if (result.status === "SUCCESS") return result.output_files.out_video.storage_url!;
    if (result.status === "FAILED") throw new Error(result.error);
  }
}

const storageUrl = await downloadVideo("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
console.log("Downloaded:", storageUrl);
const API_KEY = process.env.RENDERIO_API_KEY;
const BASE = "https://renderio.dev";

async function downloadVideo(url) {
  const submitRes = await fetch(`${BASE}/api/v1/ytdlp-download`, {
    method: "POST",
    headers: { "X-API-KEY": API_KEY, "Content-Type": "application/json" },
    body: JSON.stringify({ input_urls: { in_video: url } }),
  });

  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());

    // input key in_video → output key out_video
    if (result.status === "SUCCESS") return result.output_files.out_video.storage_url;
    if (result.status === "FAILED") throw new Error(result.error);
  }
}

const url = await downloadVideo("https://www.tiktok.com/@username/video/7123456789");
console.log("Downloaded:", url);

Supported platforms

RenderIO uses yt-dlp, which supports 1000+ platforms including YouTube, TikTok, Instagram, Twitter/X, Reddit, Vimeo, Twitch, Facebook, and more. See the yt-dlp supported sites list for a full catalog.

Platforms update their APIs frequently. RenderIO patches yt-dlp server-side, so your endpoint and code remain stable even when platforms change their formats.

On this page