# yt-dlp says "Sign in to confirm you're not a bot". How do I get YouTube subtitles without cookies?

> When yt-dlp is bot-challenged on a VPS, get YouTube subtitles and video metadata over an API instead, with no cookies or account at risk. Cost, code and limits.

Canonical page: https://okdata.online/answers/yt-dlp-not-a-bot-subtitles/

**Short answer: **If you only need the words and the numbers, skip the download: the YouTube transcript endpoint returns the subtitles with timestamps for $0.004, and YouTube video detail returns title, description, tags, views and likes for $0.004. No cookies, no signed-in account, nothing runs from your server's IP.

A nightly job runs `yt-dlp --write-auto-subs --skip-download` on a VPS to collect subtitles for a research corpus. One morning every video fails with *Sign in to confirm you're not a bot*. The usual fix is `--cookies-from-browser`, and yt-dlp's own maintainers have discussed warning users that the account behind those cookies can be banned. For a subtitle job, putting a real Google account at risk is a poor trade.

Most subtitle jobs never needed the video file. They need the text and a few facts about the video, and both are available without a download.

## What replaces which yt-dlp output

| yt-dlp gives you | Use instead | Price |
|---|---|---|
| `.vtt` / `.srt` subtitles | [YouTube video transcript](/apis/youtube/videos/transcript/get/) | $0.004 |
| `title`, `description`, `tags`, `view_count`, `like_count`, `duration` | [YouTube video detail](/apis/youtube/videos/get/) | $0.004 |
| Both, for 100 videos | 200 calls | $0.80 |

Detail returns tags as `hashtags`. Add `view=full` to get `ext.duration_ms`.

## Write a .vtt file like yt-dlp did

```python
import os, requests

API = "https://api.okdata.online/v1"
H = {"Authorization": f"Bearer {os.environ['OKDATA_API_KEY']}"}

def ts(sec):
    ms = int(round(sec * 1000))
    return f"{ms // 3_600_000:02}:{ms // 60_000 % 60:02}:{ms // 1000 % 60:02}.{ms % 1000:03}"

def save_vtt(video_id, lang="en"):
    body = requests.get(f"{API}/youtube/videos/transcript/get", headers=H,
                        params={"id": video_id, "lang": lang}, timeout=60).json()
    if body["code"] != "ok":
        print(video_id, body["code"])  # not_found = no captions, not charged
        return
    t = body["data"]["item"]
    cues = [f"{ts(s['start'])} --> {ts(s['end'])}\n{s['text']}" for s in t["segments"]]
    with open(f"{video_id}.{t['lang']}.vtt", "w", encoding="utf-8") as f:
        f.write("WEBVTT\n\n" + "\n\n".join(cues) + "\n")

save_vtt("dQw4w9WgXcQ")
```

## What you give up

- No media. If the job needs the video or audio file itself, this does not replace yt-dlp.
- One track per call, in your preferred `lang` when the video has it. Check the returned `lang`.
- No flag for manual versus automatic captions.
- Videos without captions return `not_found` and cost nothing; there is no speech-to-text.
- The ID goes in as `id`; links are not accepted on this endpoint.

## When yt-dlp is still the right tool

- You need the media file. Run yt-dlp from a home connection, or accept the cookie risk with a spare account.
- Small, occasional batches from a residential IP usually pass the check.
- Subtitles for your own channel can come from YouTube Studio or the official Data API, since you own the videos.

For whole lists and costs, see the [video transcript solution page](/solutions/transcripts/).

## Endpoints used

- [YouTube video transcript](https://okdata.online/apis/youtube/videos/transcript/get/) `youtube.videos.transcript.get`: $0.004 per call
- [YouTube video detail](https://okdata.online/apis/youtube/videos/get/) `youtube.videos.get`: $0.004 per call

Updated 2026-11-12
