RenderIO
API referenceCommands

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.

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 and stores it in RenderIO-managed storage by default, or in a Business account's S3-compatible bucket when storage_destination_id is supplied. 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.

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 YtDlpDownloadRequest {
  input_urls: Record<string, string>;  // in_* aliases mapped to public video URLs
  format_selector?: string;            // Quality preset (default: "best")
  metadata?: Record<string, string | number | boolean>; // Max 10 keys
  webhook_url?: string;                // Per-command webhook override
  storage_destination_id?: string;     // Business: send outputs to your S3-compatible bucket
}
FieldTypeRequiredDescription
input_urlsRecord<string, string>YesMap of alias names (must start with in_) to public video URLs. Each URL is downloaded independently.
format_selectorstringNoQuality preset for the download. Defaults to "best" (highest available quality). See table below for all presets.
metadataRecord<string, string | number | boolean>NoArbitrary key-value metadata attached to the command. Maximum 10 keys.
webhook_urlstringNoURL to receive a POST when this command completes. Overrides any account-level webhook configuration for this command. Requires the Business plan.
storage_destination_idstringNoSend downloaded outputs to this S3-compatible storage destination. Requires the Business plan.

Format selector presets

PresetDescription
"best"Best available quality (default)
"2160p"Max 4K resolution
"1440p"Max 1440p resolution
"1080p"Max 1080p (Full HD)
"720p"Max 720p (HD)
"480p"Max 480p (SD)
"360p"Max 360p
"audio_only"Audio only, no video
"worst"Lowest available quality (smallest file)

If a video is not available at the requested resolution, yt-dlp downloads the closest available quality below the cap.

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

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

result.output_files.out_<suffix>

The downloaded file is keyed as out_<suffix> where <suffix> matches your in_<suffix> input key. For example, in_video maps to output_files.out_video.

  • Managed storage: use storage_url.
  • Public BYOB storage: use storage_url; external_uri and external_object_key are also present.
  • Private BYOB storage: storage_url is null; use external_uri or external_object_key with your own S3 client or delivery layer.

The command and its external object key can be retrieved again later with the same command_id; the media job does not need to be rerun. See Bring Your Own Bucket for R2 configuration and private-download examples.

Partial failures in batches

Every URL in input_urls downloads independently. When you submit a batch:

  • The command finishes with status: "SUCCESS" when at least one URL stored successfully.
  • URLs that could not be downloaded are reported in a failed_files map, keyed by their in_* alias. Each entry carries the source url, an error_status code, and a human-readable error_message.
  • The command only becomes FAILED when every URL fails.
{
  "command_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "SUCCESS",
  "output_files": {
    "out_video_1": { "storage_url": "https://media.renderio.dev/output/abc.mp4", "status": "STORED" }
  },
  "failed_files": {
    "in_video_2": {
      "url": "https://www.tiktok.com/@user/video/7123456790",
      "error_status": "SOURCE_LOGIN_REQUIRED",
      "error_message": "This post is only visible to logged-in users"
    }
  }
}

Always check for failed_files on SUCCESS responses when you submit batches. See Error Handling for the full list of error_status codes.

Note this is different from POST /api/v1/run-ytdlp-command with an ffmpeg_command, which stays all-or-nothing — the FFmpeg step needs every input, so one failed download fails that whole command.

Submit-time URL rejection

Obviously unavailable YouTube and TikTok URLs (removed videos, malformed IDs) are rejected at submit time with a 422 response that lists every bad URL, so you never burn a command on a dead link. These submit-time checks are being extended to cover TikTok login-gated and removed posts.

Error responses

StatusErrorDescription
401UNAUTHORIZEDMissing or invalid API key.
403FORBIDDENVideo downloads are unavailable on the current plan, or a Business-only webhook or storage destination was requested by an ineligible account.
404NOT_FOUNDThe supplied storage destination does not exist or belongs to another account.
409CONFLICTThe supplied storage destination is disabled.
422VALIDATION_ERRORMissing input_urls, invalid in_ key prefix, invalid URL, excessive metadata, a malformed storage_destination_id, or ffmpeg_command/output_files included on this endpoint.
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"
    },
    "format_selector": "720p",
    "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
        output = result["output_files"]["out_video"]
        location = (
            output.get("storage_url")
            or output.get("external_uri")
            or output.get("external_object_key")
        )
        print(location)
        break
    if result["status"] == "FAILED":
        raise RuntimeError(result.get("error_message"))
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;
    storage_location?: "INTERNAL" | "EXTERNAL";
    external_uri?: string;
    external_object_key?: string;
    status: string;
  }>;
  error_message?: 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") {
      const output = result.output_files.out_video;
      const location = output.storage_url ?? output.external_uri ?? output.external_object_key;
      if (!location) throw new Error("Completed output has no storage location");
      return location;
    }
    if (result.status === "FAILED") throw new Error(result.error_message);
  }
}

const location = await downloadVideo("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
console.log("Output:", location);
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") {
      const output = result.output_files.out_video;
      const location = output.storage_url ?? output.external_uri ?? output.external_object_key;
      if (!location) throw new Error("Completed output has no storage location");
      return location;
    }
    if (result.status === "FAILED") throw new Error(result.error_message);
  }
}

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

Supported platforms

RenderIO uses yt-dlp, which supports thousands of extractors including YouTube, TikTok, Instagram, X/Twitter, Reddit, Vimeo, Twitch, Facebook, and more. See the yt-dlp supported sites list for the current catalog.

Platforms update their APIs frequently, and the yt-dlp project notes that listed sites are not guaranteed to work forever because websites change. RenderIO patches yt-dlp server-side, so your endpoint and code remain stable when extractor fixes ship.

On this page