yt-dlp says “Sign in to confirm you're not a bot” — what to do on a server
On a laptop yt-dlp downloads YouTube fine. Move the same command to a VPS and it answers “Sign in to confirm you're not a bot” — on nearly every video. Here is what the message means, what we measured from a datacenter address, and the API call that replaces it.
Updated
What the error actually says
YouTube's player endpoint refused to return stream data and asked for a signed-in session. yt-dlp relays that and suggests cookies, because a session is the only thing it can add on its side. The video is public; the request was rejected before it got anywhere near the video.
Terminal$ yt-dlp https://www.youtube.com/watch?v=aqz-KE-bpKQ ERROR: [youtube] aqz-KE-bpKQ: Sign in to confirm you're not a bot. Use --cookies-from-browser or --cookies for the authentication. See the yt-dlp FAQ for how to manually pass cookies.
It is the address, not the tool
We measured it before building our own pipeline: 16 randomly chosen public videos from a Hetzner address — 0 of 16 with yt-dlp's default clients (one famous video that works everywhere is an anomaly, not a signal). The same binary behind an exit address YouTube trusts: 22 of 22. Datacenter ranges are scored as bots as a whole; the fix is not a flag.
Why cookies are a treadmill
Exporting a browser session works today. Then YouTube ties the cookies to the IP and device they came from, rotates them, and flags the account when it sees hundreds of downloads from a server. You end up running a rota of throwaway accounts, PO-token generators and proxies — a scraping operation, not a download command.
Step one: what does the video offer
One GET with the link. The answer lists every rendition with its exact size in bytes, plus title, duration and thumbnail — the part of yt-dlp -F you actually used. No links yet: this call is 2 credits and is safe to cache.
Terminalcurl -s "https://api.saveapi.org/v1/youtube/info" \ -H "Authorization: Bearer sk_live_xxxx" \ -G --data-urlencode "url=https://www.youtube.com/watch?v=dQw4w9WgXcQ"
Step two: build the one you want
Pass the same link and a quality from the list. The response is a signed link that streams the MP4 (or M4A/MP3) from our side, with filename, file_size and an expiry. The stream comes through us on purpose: YouTube signs its media URLs to the requesting IP, so a raw link would answer 403 on your server.
Terminalcurl -s "https://api.saveapi.org/v1/youtube/create" \ -H "Authorization: Bearer sk_live_xxxx" \ -G --data-urlencode "url=https://www.youtube.com/watch?v=aqz-KE-bpKQ" \ --data-urlencode "quality=720p"
A drop-in replacement in Python
If your code called yt-dlp for a file and then did something with it, this is the same contract: pass the link, get a URL to fetch. What you lose is format selectors like bestvideo+bestaudio; what you get is an exact size before you commit and no bot wall to babysit.
Pythonimport httpx
API = "https://api.saveapi.org/v1/youtube"
HEADERS = {"Authorization": "Bearer sk_live_xxxx"}
def youtube_link(url: str, quality: str = "720p") -> dict:
"""Instead of yt-dlp -f: list the renditions, then build one."""
info = httpx.get(f"{API}/info", params={"url": url},
headers=HEADERS, timeout=30).json()
if not info["success"]:
raise RuntimeError(info["error"]["code"])
available = {f["quality"] for f in info["formats"]}
if quality not in available:
quality = info["formats"][-1]["quality"] # highest one this video has
made = httpx.get(f"{API}/create", params={"url": url, "quality": quality},
headers=HEADERS, timeout=60).json()
if not made["success"]:
raise RuntimeError(made["error"]["code"])
return made # url, filename, file_size, expiresQuestions before you build
- Can yt-dlp still download YouTube from a home connection?
- Usually yes. The wall is about the address class; a residential IP with a browser-like client passes most of the time. Servers are where it fails, and servers are where bots and backends run.
- Does the API use my YouTube account or cookies?
- No. The request is the link and your API key. There is no account of yours involved and nothing to rotate.
- What does it cost per video?
- 12 credits for info and create together — with the $9 pack that is about a tenth of a cent per download. A link YouTube itself refuses costs 0.1 credit; a failure on our side costs nothing.
Platform references used here
Other guides
Run this guide with your own key
Key in under a minute. 1,000 free credits, no card, no expiry.