Get YouTube transcripts in Python
The shortest way to get a YouTube transcript in Python that also works on a server: one HTTP request, no proxies, no scraping code to maintain.
1. Get an API key
Enter your email on the sign-up form. The key arrives by email with 100 free credits. Keep it in an environment variable:
export TRANSKRIO_API_KEY="tk_live_..."
2. Get a transcript
import os
import requests
API = "https://api.transkrio.com/v1"
HEADERS = {"x-api-key": os.environ["TRANSKRIO_API_KEY"]}
def youtube_transcript(url: str, lang: str | None = None) -> dict:
r = requests.get(f"{API}/transcript", params={"url": url, "lang": lang},
headers=HEADERS, timeout=60)
body = r.json()
if r.status_code != 200:
raise RuntimeError(body.get("error", {}).get("message") or body.get("detail"))
return body
t = youtube_transcript("https://www.youtube.com/watch?v=dQw4w9WgXcQ")
print(t["language_code"], len(t["segments"]), "segments")
print(t["text"][:300])
Each segment has text, start and duration in seconds. text is the whole transcript joined, which is what you usually want to send to an LLM.
3. Plain text or subtitles
Ask for format=text, srt or vtt and the response body is the file itself:
srt = requests.get(f"{API}/transcript", headers=HEADERS, timeout=60,
params={"url": "dQw4w9WgXcQ", "format": "srt"}).text
open("video.srt", "w", encoding="utf-8").write(srt)
4. Handle errors
Errors come back as JSON with a stable error.code (or a detail message for 402 and 429), and none of them cost credits:
| Status | code | Meaning |
|---|---|---|
| 400 | invalid_url | Not a YouTube, TikTok, Instagram or podcast URL |
| 404 | no_transcript | The video has no captions |
| 404 | video_unavailable | Private, deleted or unavailable video |
| 403 | video_restricted | Age-restricted or region-blocked video |
| 402 | - | Out of credits |
| 429 | - | More than 60 requests a minute |
5. A whole playlist or channel
def channel_transcripts(channel: str, limit: int = 50) -> list[dict]:
videos = requests.get(f"{API}/videos", headers=HEADERS, timeout=60,
params={"url": channel, "limit": limit}).json()["videos"]
urls = [v["url"] for v in videos]
out = []
for i in range(0, len(urls), 50):
r = requests.post(f"{API}/batch", headers=HEADERS, timeout=300,
json={"urls": urls[i:i + 50], "format": "text"})
out += [item for item in r.json()["results"] if item["ok"]]
return out
for item in channel_transcripts("@veritasium", limit=10):
print(item["video_id"], item["text"][:80])
TikTok, Reels and podcasts too
The same function works with TikTok links, Instagram Reels and podcast episodes. Those may need to be transcribed from the audio; a long episode can answer 202 with "status": "processing" the first time. Wait retry_after seconds and send the same request again:
import time
def transcript(url: str) -> dict:
while True:
r = requests.get(f"{API}/transcript", params={"url": url}, headers=HEADERS, timeout=90)
if r.status_code == 202:
time.sleep(r.json()["retry_after"])
continue
r.raise_for_status()
return r.json()
See also: the same in Node.js, building RAG over YouTube videos, and why the free library gets RequestBlocked on servers.