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-downloadDownload 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
| 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 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
}| Field | Type | Required | Description |
|---|---|---|---|
input_urls | Record<string, string> | Yes | Map of alias names (must start with in_) to public video URLs. Each URL is downloaded independently. |
format_selector | string | No | Quality preset for the download. Defaults to "best" (highest available quality). See table below for all presets. |
metadata | Record<string, string | number | boolean> | No | Arbitrary key-value metadata attached to the command. Maximum 10 keys. |
webhook_url | string | No | URL to receive a POST when this command completes. Overrides any account-level webhook configuration for this command. Requires the Business plan. |
storage_destination_id | string | No | Send downloaded outputs to this S3-compatible storage destination. Requires the Business plan. |
Format selector presets
| Preset | Description |
|---|---|
"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;
}| Field | Type | Description |
|---|---|---|
command_id | string | Unique 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_uriandexternal_object_keyare also present. - Private BYOB storage:
storage_urlisnull; useexternal_uriorexternal_object_keywith 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_filesmap, keyed by theirin_*alias. Each entry carries the sourceurl, anerror_statuscode, and a human-readableerror_message. - The command only becomes
FAILEDwhen 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
| Status | Error | Description |
|---|---|---|
401 | UNAUTHORIZED | Missing or invalid API key. |
403 | FORBIDDEN | Video downloads are unavailable on the current plan, or a Business-only webhook or storage destination was requested by an ineligible account. |
404 | NOT_FOUND | The supplied storage destination does not exist or belongs to another account. |
409 | CONFLICT | The supplied storage destination is disabled. |
422 | VALIDATION_ERROR | Missing input_urls, invalid in_ key prefix, invalid URL, excessive metadata, a malformed storage_destination_id, or ffmpeg_command/output_files included on this endpoint. |
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
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.
Related docs
Run FFmpeg Command API
Submit a single FFmpeg command for asynchronous execution in RenderIO and receive a command ID for polling results.
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.