Skip to content

yt-dlp as an API: embedding it, self-hosting a REST wrapper, or using a hosted service

By Tunelio teamPublished

There is no official yt-dlp REST API — yt-dlp is a library and a CLI. To call it over HTTP you either embed it in your own service, deploy one of the community wrappers, or pay a hosted API that runs the extraction fleet for you. This guide shows a minimal self-hosted wrapper, what it actually costs to run at scale (CPU, IP reputation, breakage, bandwidth), and the honest decision rule between building and buying.

Option 1: embed yt-dlp in your own code

If your application is Python, import yt_dlp and call extract_info directly — no HTTP hop, no second service. The Python guide covers options, hooks, error handling and the async pitfalls. This is the right answer for scripts, internal tools and single-tenant apps.

Option 2: self-host a REST wrapper

A wrapper exposes yt-dlp to non-Python code, bots and other services. The minimum viable version is a few dozen lines:

# app.py — minimal yt-dlp REST wrapper (FastAPI)
import asyncio, yt_dlp
from fastapi import FastAPI, HTTPException, Query

app = FastAPI()
SEM = asyncio.Semaphore(4)                 # each extraction ≈ 1 s CPU; cap it

def extract(url: str, fmt: str) -> dict:
    opts = {"quiet": True, "skip_download": True, "format": fmt, "noplaylist": True,
            "source_address": "0.0.0.0"}   # IPv4
    with yt_dlp.YoutubeDL(opts) as ydl:
        return ydl.extract_info(url, download=False)

@app.get("/info")
async def info(url: str = Query(...)):
    try:
        async with SEM:
            i = await asyncio.to_thread(extract, url, "b")
    except yt_dlp.utils.DownloadError as e:
        raise HTTPException(status_code=502, detail=str(e)[:200])
    return {"id": i["id"], "title": i["title"], "duration": i["duration"],
            "formats": [{"id": f["format_id"], "height": f.get("height"), "ext": f["ext"]}
                        for f in i["formats"] if f.get("vcodec") != "none" and f.get("acodec") != "none"]}

@app.get("/url")
async def direct_url(url: str, quality: int = 720):
    async with SEM:
        i = await asyncio.to_thread(extract, url, f"b[height<={quality}]")
    return {"url": i["url"], "expires_in_hours": 6}   # signed for THIS server's IP

Run it with uvicorn behind nginx, in the Docker image from the Python guide (yt-dlp + ffmpeg + Deno). Community projects such as yt-dlp-web-ui, MeTube and various “yt-dlp server” images do the same with a UI and a queue; they inherit every limitation below.

What the wrapper does not solve

The returned URL is tied to your server

googlevideo links are signed for the requesting IP and client and expire in about six hours. Handing them to end users on other networks yields 403s; the real design is to stream the media through your server (bandwidth ×2) or download and re-serve it (disk + bandwidth). That is the part hosted APIs call a “tunnel”.

Merging and audio need ffmpeg per request

Anything above 720p is separate video and audio streams; MP3 is a transcode. Both are ffmpeg CPU on your box for every download (ffmpeg guide).

CPU per extraction

On our fleet a single YouTube extraction costs about 1.2–1.3 seconds of CPU, mostly evaluating the player JavaScript, and it is paid on every request. Ten requests per second is a dozen cores before serving a single byte of media. Cache extraction results per video for a few hours to survive bursts.

IP reputation is the real ceiling

A server extracting hundreds of videos a day from one datacenter address hits “Sign in to confirm you’re not a bot” and media 403s — in our measurements roughly one in four fresh datacenter exits is challenged on first contact, and IPv6 makes it worse. Staying online means residential proxies, sticky sessions, PO-token minting and cookie rotation (proxy, cookies and 403 guides). This is the operational cost people underestimate most.

Breakage

YouTube changes something every few weeks; every change is a yt-dlp update and a redeploy, usually discovered by an outage (update guide). Budget for on-call.

Option 3: a hosted YouTube download API

curl "https://tunelio.dev/create?url=https://youtu.be/dQw4w9WgXcQ&quality=720p" \
  -H "Authorization: Bearer tnl_your_api_key"
# → { "url": "https://…/tunnel?id=…&sig=…", "file_size_str": "28.52 MB", "status": "ok" }

A hosted API is the wrapper above, plus the fleet, plus the tunnel: one request returns a signed download URL you can hand straight to a user, a bot or an S3 upload, with extraction, ffmpeg, IP rotation and YouTube changes handled server-side. Tunelio is our own service, so read the comparison with that in mind; the numbers are the same ones we live with.

Build vs buy — the honest table

  • Volume under ~50 videos/day from one machine, personal or internal use → embed or self-host; the free tools are enough and you learn the stack.
  • A product feature (bot, SaaS, pipeline) with users you do not control → hosted API usually wins: the proxy bill alone often exceeds the API bill, and you do not carry the on-call.
  • Strict data-residency or air-gapped requirements → self-host, and plan for residential exits and a maintainer.
  • Non-YouTube sites → yt-dlp’s breadth is unmatched; hosted APIs are usually YouTube-first.
  • Need transcripts, metadata and media from one contract → hosted (Tunelio exposes /info, /create and /transcript on one key).

Pricing sanity check

Self-hosting looks free until you add the pieces: a VPS with a few cores, a residential proxy plan billed per GB (every video byte passes through it), someone updating yt-dlp at 3 a.m., and lost users during outages. Hosted pricing is per request: Tunelio charges 6 credits for /info or /transcript and 10 for /create, with 100 free credits on signup and paid plans from $9 per month for 100,000 credits. Do the arithmetic for your own volume before choosing.

Sources

  • yt-dlp README — “Embedding yt-dlp”, network and format options.
  • Community wrappers — yt-dlp-web-ui, MeTube (GitHub) for reference implementations.
  • Our own measurements (Tunelio extraction fleet, 2026): CPU per extraction, first-contact challenge rate on datacenter IPs, proxy bytes per extraction.

Frequently asked questions

Is there an official yt-dlp API?

No. yt-dlp is a Python library and CLI. Any REST API is either your own wrapper, a community project, or a hosted service that runs yt-dlp-like extraction for you.

Can I give the yt-dlp URL to my users?

Not reliably: the googlevideo URL is signed for your server’s IP and client and expires in ~6 hours. Stream it through your server or use a hosted API that provides a user-safe signed link.

How many requests per second can one server handle?

Roughly one extraction per second per core, plus ffmpeg and bandwidth — before IP reputation limits you. Cache results and cap concurrency.

When is self-hosting the better choice?

Low volume, internal use, non-YouTube sites, or hard data-residency rules. For user-facing products at scale, the proxy and maintenance costs usually make a hosted API cheaper.

Related guides