Download Any Public Video via API: The yt-dlp Guide

May 22, 2026 · RenderIO

yt-dlp is a headache to run in production

Running yt-dlp on your own machine is fine. Running it as part of a real service is something else.

Platforms change their extraction logic all the time, so you're constantly chasing yt-dlp updates just to stay functional. You need residential or rotating proxies or you'll get IP-banned. Serverless platforms can't run binaries at all. And all the retry and rate-limit logic falls on you.

The RenderIO yt-dlp API handles that stack. You POST a URL, you get back a file. Here's how.

What the API does

The API accepts a public video URL, downloads it via yt-dlp, and stores the result. It supports YouTube, TikTok, Instagram, Twitter/X, Reddit, Vimeo, Twitch, and 1,000+ other platforms.

Private videos, DRM-protected content, and anything behind a login wall return an error — public only.

Two endpoints:

  • POST /api/v1/ytdlp-download — download, no post-processing

  • POST /api/v1/run-ytdlp-command — download, then optionally run an FFmpeg command on the result

Both are async: submit, get a command_id, poll for the result.

Your first download

curl -X POST https://renderio.dev/api/v1/ytdlp-download \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: ffsk_your_key" \
  -d '{
    "input_urls": {
      "in_video": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
    }
  }'

You get back:

{
  "command_id": "cmd_abc123"
}

Poll for status:

curl https://renderio.dev/api/v1/commands/cmd_abc123 \
  -H "X-API-KEY: ffsk_your_key"

When it finishes:

{
  "command_id": "cmd_abc123",
  "status": "SUCCESS",
  "output_files": {
    "out_video": {
      "storage_url": "https://media.renderio.dev/downloads/cmd_abc123/out_video.mp4",
      "size_mbytes": 45.2
    }
  }
}

The file URL is at output_files.out_video.storage_url. The output key is derived from your input key: in_videoout_video, in_clipout_clip.

The input_urls key format

Keys must start with in_. Whatever comes after the underscore becomes the out_ key in the response.

{
  "input_urls": {
    "in_clip": "https://www.tiktok.com/@user/video/1234567890"
  }
}

output_files.out_clip.storage_url

Pick a name that makes sense for your code. in_video, in_clip, in_source — all valid.

Polling

Status values: QUEUED, PROCESSING, SUCCESS, FAILED.

COMMAND_ID="cmd_abc123"
API_KEY="ffsk_your_key"

while true; do
  STATUS=$(curl -s https://renderio.dev/api/v1/commands/$COMMAND_ID \
    -H "X-API-KEY: $API_KEY" | jq -r '.status')

  echo "Status: $STATUS"

  if [ "$STATUS" = "SUCCESS" ] || [ "$STATUS" = "FAILED" ]; then
    break
  fi

  sleep 3
done

Most downloads wrap up in 10–60 seconds. Larger files take longer, as you'd expect.

Metadata

Pass up to 10 key/value pairs in metadata to tag the job with your own identifiers — user IDs, job references, whatever you need for correlation later.

curl -X POST https://renderio.dev/api/v1/ytdlp-download \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: ffsk_your_key" \
  -d '{
    "input_urls": {
      "in_video": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
    },
    "metadata": {
      "user_id": "usr_12345",
      "job_ref": "batch_2026_05"
    }
  }'

It comes back in poll responses and webhooks.

Download and process in one call

Need to transcode the video right after downloading? Use /api/v1/run-ytdlp-command instead. It downloads first, then runs your FFmpeg command on the result.

curl -X POST https://renderio.dev/api/v1/run-ytdlp-command \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: ffsk_your_key" \
  -d '{
    "input_urls": {
      "in_video": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
    },
    "ffmpeg_command": "-i {{in_video}} -vn -c:a libmp3lame -q:a 2 {{out_audio}}",
    "output_files": {
      "out_audio": "audio.mp3"
    }
  }'

{{double_braces}} for placeholders. output_files maps each output key to a filename. Skip ffmpeg_command entirely and it behaves like the plain download endpoint.

More recipes in the download and process guide.

When things fail

If the URL is unavailable or unsupported, the poll response returns status: "FAILED":

{
  "command_id": "cmd_xyz789",
  "status": "FAILED",
  "error": "Video is private or unavailable"
}

The usual culprits: the video was deleted or is private, the platform requires a login, the URL format isn't something yt-dlp recognizes, or the platform temporarily rate-limited the request (retry usually works for the last one).

Rate limits

Your plan determines the limit. Check X-RateLimit-Remaining in response headers before batching a lot of requests.

Language-specific guides

Full examples with polling and error handling for:

  • Node.js — fetch-based, works on any serverless platform

  • Python — requests-based with a complete client class

Supported platforms

PlatformNotes
YouTubePublic videos, Shorts, playlists (one URL per call)
TikTokPublic posts only
InstagramPublic posts and Reels
Twitter/XPublic tweets with video
RedditVideo posts
VimeoPublic videos
TwitchVODs and clips
FacebookPublic videos

Age-gated content and anything requiring a login won't work.

FAQ

Does this support downloading entire YouTube playlists?

No — one URL per request. For playlists, iterate over the individual video URLs and submit separately.

What format does the downloaded video come in?

yt-dlp picks the best available format, usually MP4 with H.264 video and AAC audio. If you need something specific, use the run-ytdlp-command endpoint and add an FFmpeg transcode step.

Can I call this from a serverless function?

Yes. It's just HTTP. Works from Lambda, Cloudflare Workers, Vercel — anything with outbound requests.