Get YouTube transcripts into anything
One URL gives you the transcript of any YouTube video as text. Your AI assistant can call it, your scripts can call it, and tools like n8n or Google Sheets can call it. You do not need to be a developer to use this page — pick the path that sounds like you.
I use an AI assistant
Claude, ChatGPT, Cursor, VS Code. Connect once, then just ask for transcripts in the chat. No code.
Connect the MCP server →I write code or automate
Python, Node, curl, n8n, Make, Zapier, Sheets. Create a free key and call one URL.
Start in 3 steps →I just want the files
Paste a video, playlist, or channel link and download TXT, SRT, Markdown, or JSON.
Open the web app →Start in 3 steps
-
1
Create a free API key
Sign in with Google at bulktranscripts.co/app, open the MCP & API tab and press Create key. Copy the key — it starts with
bt_ak_and is shown only once.In plain words: an API key is a password that programs use on your behalf. Keep it private, and revoke it from the same place if it ever leaks.
Open the app and create a key → -
2
Ask for one transcript
Paste this into a terminal (Mac: Terminal; Windows: PowerShell) after replacing
YOUR_API_KEY. No terminal? Use the playground right below instead — it does the same thing with a button.curl "https://bulktranscripts.co/api/v1/transcript?video=https://www.youtube.com/watch?v=jNQXAC9IVRw" \ -H "Authorization: Bearer YOUR_API_KEY"In plain words: you are visiting a web address, and the second line shows your key so we know whose credits to use.
-
3
Read the answer
The reply is JSON — labelled fields, like a form.
textis the whole transcript,segmentsare the timestamped lines, andbilling.remainingis how many credits you have left.{ "video_id": "jNQXAC9IVRw", "url": "https://www.youtube.com/watch?v=jNQXAC9IVRw", "title": "Me at the zoo", "channel": "jawed", "duration": 19, "duration_text": "0:19", "upload_date": "2005-04-23", "language": "en", "source": "auto_caption", "cached": true, "word_count": 43, "text": "All right, so here we are in front of the elephants...", "segments": [{"text": "All right, so here we are...", "start": 0.0, "duration": 5.4}], "billing": {"remaining": 29, "creditsCharged": 1, "freeLimit": 30} }
Try it here — nothing to install
This calls the real API from your browser. Paste your key once (it stays in this browser only), pick what you want, press Send. The first fetch of a video costs 1 credit; fetching a video already in your library is free.
What you can ask for
Every request is a plain web address (a GET). Replace the parts in UPPERCASE.
Full details for each are in the reference.
| I want… | Call this | Costs | |
|---|---|---|---|
| The transcript of one video | /api/v1/transcript?video=VIDEO_URL | 1 credit, free next time | |
| The transcript as a file (SRT, TXT, Markdown…) | /api/v1/transcript?video=VIDEO_URL&format=srt | same as above | |
| Every video in a channel (to fetch them one by one) | /api/v1/channel/videos?channel=@HANDLE | 1 credit for the list | |
| Every video in a playlist | /api/v1/playlist/videos?playlist=PLAYLIST_URL | 1 credit for the list | |
| Videos about a topic | /api/v1/search?q=WORDS | 1 credit | |
| A video inside a specific channel | /api/v1/channel/search?channel=@HANDLE&q=WORDS | 1 credit | |
| To know when a channel posts something new | /api/v1/channel/latest?channel=@HANDLE | free | |
| To check my balance | /api/v1/account | free |
Rule of thumb: getting a transcript costs 1 credit the first time, and reading it again from your library is free forever. Lists and searches cost 1 credit each. Checking your balance and watching for new uploads are always free. Anything that fails is not charged.
Words on this page, in plain English
- API
- A way for programs to ask our service for things. You "call" it by visiting a web address.
- Endpoint
- One specific address that does one job —
/api/v1/transcriptgets a transcript. - API key
- Your password for programs. It starts with
bt_ak_and links calls to your credits. - Header
- Extra information sent along with a request. Your key travels in the
Authorizationheader. - JSON
- The format of every answer: labelled fields in curly braces. Every language and no-code tool can read it.
- curl
- A tiny built-in command-line tool for visiting addresses. Already installed on Mac, Windows, and Linux.
- Credit
- One unit of work. 30 free to start; packs never expire.
- MCP
- The standard AI assistants use to plug in tools. Connect our server and Claude or ChatGPT can fetch transcripts itself.
- Cache / library
- Transcripts we have already extracted. Yours are free to re-read; everyone's come back instantly.
Authentication — where the key goes
Every /api/v1/* request, including the free ones, needs a credential. The normal
one is your API key, sent as a Bearer token:
curl "https://bulktranscripts.co/api/v1/transcript?video=VIDEO_ID" \
-H "Authorization: Bearer bt_ak_YOUR_KEY"
headers={"Authorization": "Bearer bt_ak_…"}headers: { Authorization: "Bearer bt_ak_…" }Authorization, value Bearer bt_ak_…X-API-Key works tooCredits
Credits are one-time purchases that never expire — no subscription, 30 free to start.
- 1 credit: a transcript's first addition to your library, a search, or a channel/playlist listing.
- Free: re-reading your library, checking your balance, watching for new uploads, and any failure.
- Every response carries a
billingblock withcreditsChargedandremaining.
- $4.99 · 200
- $14.99 · 1,200
- $29 · 5,000
- $99 · 25,000
- $499 · 200,000
REST API reference
Base URL https://bulktranscripts.co. Every endpoint returns JSON, and all but the bulk-job start
are plain GET requests (machine-readable spec: /openapi.json). Each block shows the same
request in cURL, Python and Node, plus a real response; the table under it lists every option.
/api/v1/transcript
1 credit first time · your repeats are freeFull transcript of one YouTube video with metadata, clean paragraph text, and timestamped segments.
curl "https://bulktranscripts.co/api/v1/transcript?video=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DaircAruvnKk&segments=0" \
-H "Authorization: Bearer YOUR_API_KEY"import requests
r = requests.get(
"https://bulktranscripts.co/api/v1/transcript",
params={
"video": "https://www.youtube.com/watch?v=aircAruvnKk",
"segments": "0"
},
headers={"Authorization": "Bearer YOUR_API_KEY"},
)
data = r.json()
print(data["title"], data["word_count"], "words")const res = await fetch("https://bulktranscripts.co/api/v1/transcript?video=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DaircAruvnKk&segments=0", {
headers: { Authorization: "Bearer YOUR_API_KEY" },
});
const data = await res.json();
console.log(data.title, data.word_count, "words");{
"video_id": "aircAruvnKk",
"url": "https://www.youtube.com/watch?v=aircAruvnKk",
"title": "But what is a neural network? | Deep learning chapter 1",
"channel": "3Blue1Brown",
"duration": 1120,
"duration_text": "18:40",
"upload_date": "2017-10-05",
"language": "en",
"source": "manual_caption",
"cached": true,
"word_count": 3357,
"text": "This is a 3. It's sloppily written and rendered at an extremely low resolution...",
"paragraphs": ["This is a 3. It's sloppily written...", "..."],
"billing": {"enabled": true, "unlimited": false, "kind": "license", "remaining": 4999,
"used": 1, "granted": 5000, "creditsCharged": 1}
}| Parameter | Type | Default | Description | |
|---|---|---|---|---|
video | string | required | — | YouTube URL, youtu.be link, Shorts URL, or bare 11-char video id. |
language | string | optional | en | Preferred caption language(s), comma-separated. Falls back to whatever the video has. |
format | enum | optional | json | json, or txt, md, srt, vtt, csv, ai to get the rendered file instead of JSON. |
segments | 0 | 1 | optional | 1 | 0 omits the segment array from JSON (smaller responses). |
timestamps | 0 | 1 | optional | 0 | 1 includes timestamps in txt/md output. |
fresh | 0 | 1 | optional | 0 | 1 bypasses the cache and re-extracts. Always charges a credit, even for a video already in your library. |
/api/v1/search
1 creditSearch YouTube for videos, channels, or playlists. Returns id, title, channel, duration, type, URL and view count per result.
curl "https://bulktranscripts.co/api/v1/search?q=machine+learning&limit=5" \
-H "Authorization: Bearer YOUR_API_KEY"import requests
r = requests.get(
"https://bulktranscripts.co/api/v1/search",
params={
"q": "machine learning",
"limit": "5"
},
headers={"Authorization": "Bearer YOUR_API_KEY"},
)
data = r.json()
for item in data["results"]:
print(item["id"], item["title"])const res = await fetch("https://bulktranscripts.co/api/v1/search?q=machine+learning&limit=5", {
headers: { Authorization: "Bearer YOUR_API_KEY" },
});
const data = await res.json();
for (const item of data.results) console.log(item.id, item.title);{
"query": "machine learning",
"type": "video",
"results": [
{
"id": "aircAruvnKk",
"url": "https://www.youtube.com/watch?v=aircAruvnKk",
"title": "But what is a neural network? | Deep learning chapter 1",
"channel": "3Blue1Brown",
"channel_url": "https://www.youtube.com/@3blue1brown",
"duration": 1120,
"type": "video",
"view_count": 19234501
}
],
"count": 5,
"billing": {"enabled": true, "unlimited": false, "kind": "license", "remaining": 4998, "creditsCharged": 1}
}| Parameter | Type | Default | Description | |
|---|---|---|---|---|
q | string | required | — | Search query. |
type | enum | optional | video | video, channel, or playlist. |
limit | integer | optional | 10 | Max results, 1–50. |
/api/v1/channel/search
1 creditSearch within one channel's uploads — find a creator's videos on a topic without listing the whole archive.
curl "https://bulktranscripts.co/api/v1/channel/search?channel=%40TED&q=AI&limit=10" \
-H "Authorization: Bearer YOUR_API_KEY"import requests
r = requests.get(
"https://bulktranscripts.co/api/v1/channel/search",
params={
"channel": "@TED",
"q": "AI",
"limit": "10"
},
headers={"Authorization": "Bearer YOUR_API_KEY"},
)
data = r.json()
for item in data["results"]:
print(item["id"], item["title"])const res = await fetch("https://bulktranscripts.co/api/v1/channel/search?channel=%40TED&q=AI&limit=10", {
headers: { Authorization: "Bearer YOUR_API_KEY" },
});
const data = await res.json();
for (const item of data.results) console.log(item.id, item.title);{
"channel": "@TED",
"channel_title": "TED",
"query": "AI",
"results": [
{
"id": "hJP5GqnTrNo",
"url": "https://www.youtube.com/watch?v=hJP5GqnTrNo",
"title": "Can AI fix education?",
"channel": "TED",
"channel_url": "https://www.youtube.com/@TED",
"duration": 639,
"type": "video"
}
],
"count": 10,
"billing": {"enabled": true, "unlimited": false, "kind": "license", "remaining": 4997, "creditsCharged": 1}
}| Parameter | Type | Default | Description | |
|---|---|---|---|---|
channel | string | required | — | @handle, channel URL, or UC… channel id. |
q | string | required | — | Topic to search for within the channel. |
limit | integer | optional | 10 | Max results, 1–50. |
/api/v1/channel/videos
1 creditList a channel's videos — up to 1,000 — without fetching transcripts. Feed the ids into the transcript endpoint (ids already in your library stay free). There is no pagination: raise limit instead. has_more is true whenever the result reached limit.
curl "https://bulktranscripts.co/api/v1/channel/videos?channel=%40TED&limit=100" \
-H "Authorization: Bearer YOUR_API_KEY"import requests
r = requests.get(
"https://bulktranscripts.co/api/v1/channel/videos",
params={
"channel": "@TED",
"limit": "100"
},
headers={"Authorization": "Bearer YOUR_API_KEY"},
)
data = r.json()
for item in data["results"]:
print(item["id"], item["title"])const res = await fetch("https://bulktranscripts.co/api/v1/channel/videos?channel=%40TED&limit=100", {
headers: { Authorization: "Bearer YOUR_API_KEY" },
});
const data = await res.json();
for (const item of data.results) console.log(item.id, item.title);{
"channel": "@TED",
"title": "TED - Videos",
"results": [
{
"id": "iG9CE55wbtY",
"url": "https://www.youtube.com/watch?v=iG9CE55wbtY",
"title": "Do schools kill creativity? | Sir Ken Robinson | TED",
"channel": "TED",
"channel_url": "https://www.youtube.com/@TED",
"duration": 1164
}
],
"count": 100,
"has_more": true,
"billing": {"enabled": true, "unlimited": false, "kind": "license", "remaining": 4996, "creditsCharged": 1}
}| Parameter | Type | Default | Description | |
|---|---|---|---|---|
channel | string | required | — | @handle, channel URL, or UC… channel id. |
limit | integer | optional | 100 | Max videos, 1–1,000. |
/api/v1/channel/latest
freeA channel's newest uploads (up to 15). Free because it costs us nearly nothing — poll it for monitoring and only spend credits on what's new. When YouTube's feed is unavailable the response falls back to a direct listing and carries "source": "listing"; published can then be null, so diff on video id, not on date.
curl "https://bulktranscripts.co/api/v1/channel/latest?channel=%40TED" \
-H "Authorization: Bearer YOUR_API_KEY"import requests
r = requests.get(
"https://bulktranscripts.co/api/v1/channel/latest",
params={
"channel": "@TED"
},
headers={"Authorization": "Bearer YOUR_API_KEY"},
)
data = r.json()
for item in data["results"]:
print(item["id"], item["title"])const res = await fetch("https://bulktranscripts.co/api/v1/channel/latest?channel=%40TED", {
headers: { Authorization: "Bearer YOUR_API_KEY" },
});
const data = await res.json();
for (const item of data.results) console.log(item.id, item.title);{
"channel_id": "UCAuUUnT6oDeKwE6v1NGQxug",
"channel": "TED",
"results": [
{
"id": "xL2EKQ3Rkos",
"url": "https://www.youtube.com/watch?v=xL2EKQ3Rkos",
"title": "A bold plan for sustainable energy",
"channel": "TED",
"published": "2026-09-04T14:00:12+00:00",
"views": "48213"
}
],
"count": 15,
"billing_note": "free endpoint — no credit charged"
}| Parameter | Type | Default | Description | |
|---|---|---|---|---|
channel | string | required | — | @handle, channel URL, or UC… channel id. |
/api/v1/playlist/videos
1 creditEvery video in a playlist, in playlist order — courses stay in sequence. No pagination; has_more is true whenever the result reached limit.
curl "https://bulktranscripts.co/api/v1/playlist/videos?playlist=PLZHQObOWTQDNU6R1_67000Dx_ZCJB-3pi" \
-H "Authorization: Bearer YOUR_API_KEY"import requests
r = requests.get(
"https://bulktranscripts.co/api/v1/playlist/videos",
params={
"playlist": "PLZHQObOWTQDNU6R1_67000Dx_ZCJB-3pi"
},
headers={"Authorization": "Bearer YOUR_API_KEY"},
)
data = r.json()
for item in data["results"]:
print(item["id"], item["title"])const res = await fetch("https://bulktranscripts.co/api/v1/playlist/videos?playlist=PLZHQObOWTQDNU6R1_67000Dx_ZCJB-3pi", {
headers: { Authorization: "Bearer YOUR_API_KEY" },
});
const data = await res.json();
for (const item of data.results) console.log(item.id, item.title);{
"playlist": "PLZHQObOWTQDNU6R1_67000Dx_ZCJB-3pi",
"title": "Neural networks",
"results": [
{
"id": "aircAruvnKk",
"url": "https://www.youtube.com/watch?v=aircAruvnKk",
"title": "But what is a neural network? | Deep learning chapter 1",
"channel": "3Blue1Brown",
"channel_url": "https://www.youtube.com/@3blue1brown",
"duration": 1120
}
],
"count": 8,
"has_more": false,
"billing": {"enabled": true, "unlimited": false, "kind": "license", "remaining": 4995, "creditsCharged": 1}
}| Parameter | Type | Default | Description | |
|---|---|---|---|---|
playlist | string | required | — | Playlist URL or bare id (the list= value). Ids are 13, 18, 26 or 34 characters; public and unlisted playlists only — a private one reads as nonexistent (playlist_private). |
limit | integer | optional | 100 | Max videos, 1–1,000. |
/api/v1/account
freeThe credit balance for the key making the call. Returns the same billing block every credited response carries.
curl "https://bulktranscripts.co/api/v1/account" \
-H "Authorization: Bearer YOUR_API_KEY"import requests
r = requests.get(
"https://bulktranscripts.co/api/v1/account",
headers={"Authorization": "Bearer YOUR_API_KEY"},
)
data = r.json()
print(data["billing"])const res = await fetch("https://bulktranscripts.co/api/v1/account", {
headers: { Authorization: "Bearer YOUR_API_KEY" },
});
const data = await res.json();
console.log(data.billing);{
"billing": {"enabled": true, "unlimited": false, "kind": "license", "remaining": 4995,
"used": 5, "granted": 5000, "freeLimit": 30},
"docs": "https://bulktranscripts.co/docs"
}/api/v1/bulk
listing free · 1 credit per new transcriptStart a background job that fetches every transcript of a channel, playlist or video URL into your library (up to 1,000 videos). Returns 202 with a run_id straight away; small sources are listed within the call so videos_found is filled, larger ones report it on the first status poll. Cache hits and repeats are free, failures are never charged, and a job that runs out of credits keeps what it fetched and says how many videos it did not attempt. Two jobs per account at a time.
curl -X POST "https://bulktranscripts.co/api/v1/bulk" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://www.youtube.com/playlist?list=PLZHQObOWTQDNU6R1_67000Dx_ZCJB-3pi", "max_videos": 200}'import requests
r = requests.post(
"https://bulktranscripts.co/api/v1/bulk",
json={"url": "https://www.youtube.com/playlist?list=PLZHQObOWTQDNU6R1_67000Dx_ZCJB-3pi", "max_videos": 200},
headers={"Authorization": "Bearer YOUR_API_KEY"},
)
job = r.json()
print(job["run_id"], job["status"], job["videos_found"])const res = await fetch("https://bulktranscripts.co/api/v1/bulk", {
method: "POST",
headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json" },
body: JSON.stringify({"url": "https://www.youtube.com/playlist?list=PLZHQObOWTQDNU6R1_67000Dx_ZCJB-3pi", "max_videos": 200}),
});
const job = await res.json();
console.log(job.run_id, job.status, job.videos_found);{
"run_id": "3f6f6c0e-6a2b-4b26-9d3e-2f0c1c6f7a11",
"status": "running",
"phase": "extracting",
"source": {"url": "https://www.youtube.com/playlist?list=PLZHQObOWTQDNU6R1_67000Dx_ZCJB-3pi",
"type": "playlist", "title": "Neural networks"},
"videos_found": 8,
"total": 8,
"completed": 0, "cached": 0, "failed": 0, "skipped": 0, "quota": 0, "interrupted": 0,
"remaining": 8,
"error": null,
"check_again_in_seconds": 15,
"billing": {"enabled": true, "unlimited": false, "kind": "license", "remaining": 4999,
"used": 1, "granted": 5000}
}| Parameter | Type | Default | Description | |
|---|---|---|---|---|
url | string | required | — | Channel (@handle or URL), playlist URL or id, or a single video URL. JSON body field. |
max_videos | integer | optional | 100 | Cap on videos to process, 1 to 1,000. JSON body field. |
language | string | optional | en | Preferred caption language(s), comma-separated. JSON body field. |
/api/v1/bulk/{run_id}
freeProgress of a bulk job. status is running, completed, stopped (interrupted by a server restart; start it again, everything already fetched is free) or failed. Wait at least check_again_in_seconds between polls; it is 0 once the job has finished.
curl "https://bulktranscripts.co/api/v1/bulk/{run_id}" \
-H "Authorization: Bearer YOUR_API_KEY"import requests
r = requests.get(
"https://bulktranscripts.co/api/v1/bulk/{run_id}",
headers={"Authorization": "Bearer YOUR_API_KEY"},
)
data = r.json()
print(data["status"], data["completed"], "of", data["total"])const res = await fetch("https://bulktranscripts.co/api/v1/bulk/{run_id}", {
headers: { Authorization: "Bearer YOUR_API_KEY" },
});
const data = await res.json();
console.log(data.status, data.completed, "of", data.total);{
"run_id": "3f6f6c0e-6a2b-4b26-9d3e-2f0c1c6f7a11",
"status": "completed",
"phase": "finished",
"source": {"url": "https://www.youtube.com/playlist?list=PLZHQObOWTQDNU6R1_67000Dx_ZCJB-3pi",
"type": "playlist", "title": "Neural networks"},
"videos_found": 8,
"total": 8,
"completed": 7, "cached": 2, "failed": 0, "skipped": 1, "quota": 0, "interrupted": 0,
"remaining": 0,
"error": null,
"check_again_in_seconds": 0,
"billing": {"enabled": true, "unlimited": false, "kind": "license", "remaining": 4994,
"used": 6, "granted": 5000}
}| Parameter | Type | Default | Description | |
|---|---|---|---|---|
run_id | string | required | — | The run_id from the start call, in the path. |
/api/v1/bulk/{run_id}/results
freeOne page of per-video outcomes for a job, in source order: id, title, channel, duration, word count and status (ok, cached, or error with a code). Transcript text is not included — every video listed as ok or cached is already in your library, so /transcript returns it without spending a credit.
curl "https://bulktranscripts.co/api/v1/bulk/{run_id}/results?limit=2" \
-H "Authorization: Bearer YOUR_API_KEY"import requests
r = requests.get(
"https://bulktranscripts.co/api/v1/bulk/{run_id}/results",
params={
"limit": "2"
},
headers={"Authorization": "Bearer YOUR_API_KEY"},
)
data = r.json()
for item in data["items"]:
print(item["video_id"], item["status"], item["title"])const res = await fetch("https://bulktranscripts.co/api/v1/bulk/{run_id}/results?limit=2", {
headers: { Authorization: "Bearer YOUR_API_KEY" },
});
const data = await res.json();
for (const item of data.items) console.log(item.video_id, item.status, item.title);{
"run_id": "3f6f6c0e-6a2b-4b26-9d3e-2f0c1c6f7a11",
"status": "completed",
"total": 8,
"items": [
{"position": 0, "video_id": "aircAruvnKk", "title": "But what is a neural network?",
"status": "ok", "url": "https://www.youtube.com/watch?v=aircAruvnKk",
"channel": "3Blue1Brown", "duration": 1120, "word_count": 3357},
{"position": 1, "video_id": "IHZwWFHWa-w", "title": "Gradient descent, how neural networks learn",
"status": "cached", "url": "https://www.youtube.com/watch?v=IHZwWFHWa-w",
"channel": "3Blue1Brown", "duration": 1261, "word_count": 3720}
],
"next_cursor": 1,
"note": "Transcript text is not included here. Every video listed as ok or cached is already in this account's library, so get_transcript (MCP) or GET /api/v1/transcript returns it without spending a credit."
}| Parameter | Type | Default | Description | |
|---|---|---|---|---|
run_id | string | required | — | The run_id from the start call, in the path. |
cursor | integer | optional | — | next_cursor from the previous page; omit for the first page. |
limit | integer | optional | 50 | Items per page, 1 to 100. |
MCP server — YouTube for your AI assistant
Connect https://bulktranscripts.co/mcp once and Claude, ChatGPT, Cursor, VS Code, and 20+ other MCP
clients can fetch transcripts, search YouTube, list channels and playlists, and track new uploads —
inside the conversation. One sign-in (Google or license key) includes 30 free
credits, no card.
Example · once connected, just ask
Set it up in your client
- Settings → Connectors → Add custom connector
- Name BulkTranscripts, URL
https://bulktranscripts.co/mcp→ Connect - On the page that opens, continue with Google (or paste a license key)
claude mcp add --transport http bulktranscripts https://bulktranscripts.co/mcp
One-click install for Cursor, or add this to ~/.cursor/mcp.json
and sign in under Settings → MCP:
{
"url": "https://bulktranscripts.co/mcp"
}
- Settings → Connectors → Advanced → Developer mode
- Create a connector: name BulkTranscripts, URL
https://bulktranscripts.co/mcp, authentication OAuth - Sign in on the page that opens
Add to .vscode/mcp.json; VS Code prompts for the sign-in when the server starts:
{
"mcp": {
"servers": {
"bulktranscripts": {
"type": "http",
"url": "https://bulktranscripts.co/mcp"
}
}
}
}
Any MCP client takes the universal config:
{
"url": "https://bulktranscripts.co/mcp"
}
If your client cannot run the OAuth sign-in (n8n, Make, older Codex builds, scripts), create a free
API key at /app → MCP & API and send it as
Authorization: Bearer bt_ak_… — or, when headers are impossible, in the URL:
https://bulktranscripts.co/mcp?key=bt_ak_YOUR_KEY
The ten tools
| Tool | Parameters | Returns | Cost |
|---|---|---|---|
get_transcriptGet YouTube video transcript |
| Title, channel, duration, upload date, language, clean paragraph text, word count and (on request) timed segments for one video. | 1 credit on first library addition · repeat reads free |
get_transcriptsGet many transcripts at once |
| Up to 20 transcripts in one call, fetched in parallel; the call returns within about 40 seconds and anything still fetching comes back on the next call, free. Videos without captions are reported per item and do not fail the batch. One call is one request against the rate limit. | 1 credit per new transcript · repeats free |
search_youtubeSearch YouTube |
| Videos, channels or playlists matching a query: id, title, channel, duration, URL and view count. | 1 credit |
search_channelSearch inside a channel |
| Videos inside one channel that match a topic, same fields as search. | 1 credit |
get_channel_videosList a channel's videos |
| A channel's video list (id, title, duration, URL), up to 1,000, ready to feed into get_transcripts. | 1 credit |
get_playlist_videosList a playlist's videos |
| Every video in a playlist, in playlist order. | 1 credit |
get_latest_videosTrack new uploads (free) |
| A channel's newest uploads (up to 15) with publish dates and views. | Free — always |
start_bulk_extractStart a bulk extraction job |
| A run_id straight away, plus videos_found once the listing is done (small sources list within the call). The server then fetches every transcript into this account's library in the background. | Listing free · 1 credit per new transcript |
get_bulk_statusCheck a bulk job |
| status, total, completed, failed, skipped, quota, remaining and check_again_in_seconds for a job. | Free |
list_bulk_resultsList a bulk job's results |
| A page of per-video outcomes (id, title, channel, duration, word count, status, error code) with next_cursor. No transcript text. | Free |
Which tool the agent picks, prompts that work, troubleshooting
Which tool does the agent reach for?
You never call these by name — the assistant picks from the descriptions. This is the mapping it lands on, so you know what a request will cost before you ask.
| You say… | Tool used | Credits |
|---|---|---|
| "Summarize this video: [URL]" | get_transcript | 1 (0 if it is already in your library) |
| "Compare what these four talks say about X" | get_transcripts | 1 per new video |
| "Find the best explainers on Y" | search_youtube, then get_transcripts for the picks | 1 + 1 per transcript |
| "What has @creator said about Z?" | search_channel, then get_transcripts | 1 + 1 per transcript |
| "Turn this course playlist into study notes" | get_playlist_videos, then get_transcripts in batches of 20 | 1 + 1 per lecture |
| "Audit this channel's whole archive" | get_channel_videos (up to 1,000), then selective get_transcripts | 1 + 1 per transcript read |
| "Did @creator post anything this week?" | get_latest_videos | 0 |
Prompts that work on day one
One video
“Summarize this talk with the three strongest quotes and timestamps: https://youtube.com/watch?v=…”
“Pull the transcript and turn it into a LinkedIn post in my voice.”
Several videos
“Here are five interviews with the same founder. Where do their stories contradict each other?”
“Compare how these two channels explain the same concept. Which is clearer for a beginner?”
Research a topic
“Find the most-watched videos about intermittent fasting and tell me where the experts disagree.”
“What has @hubermanlab said about caffeine timing? Cite the episodes.”
Courses & playlists
“Read every lecture in this Stanford playlist and build a study guide with one section per lecture.”
“Which videos in this playlist mention gradient descent?”
Monitoring
“Check @TED, @veritasium and @3blue1brown for new uploads and brief me on anything about AI.”
“Every morning: new videos from these channels, one line each.” (free — uses get_latest_videos)
Whole channels
“List everything @mkbhd has published this year, then read the ten most-viewed and tell me his recurring complaints about phones.”
Troubleshooting
- The connector shows “Needs authentication” or never finishes connecting
- Open the sign-in your client offers (Claude Code:
/mcp→ Authenticate; Claude web: Settings → Connectors → the connector's menu → Reconnect; Cursor: Settings → MCP → Login). The sign-in page is served by BulkTranscripts and accepts Google or a license key. If the page never opens, the client is probably an older build without MCP OAuth: use the key-in-URL formhttps://bulktranscripts.co/mcp?key=YOUR_LICENSE_KEYinstead. - I get
401right after buying a pack - The key is issued at checkout but the payment webhook that activates it can land a few seconds later. Wait a moment and retry; nothing is charged for the failed call.
- The assistant answers from memory instead of calling the tool
- Paste the full YouTube URL (not just the title) and say “use BulkTranscripts” or “fetch the transcript” once. Check the connector is enabled for the current chat; ChatGPT and Claude web both have a per-conversation toggle.
out_of_creditsin the middle of a batch- The transcripts already fetched are returned with
partial: trueand astoppedcount, and the error carries a purchase link. Credits are added to the same account you signed in with, so nothing needs reconnecting afterwards. no_transcriptfor a video that clearly has captions- Some videos only have captions in one language; ask for that language explicitly (“get the German transcript”). Live streams and members-only videos expose no captions. Never charged.
- Long videos (2h+) take a while or the client times out
- Extraction usually finishes in a few seconds, but the first read of a long, uncached video can take longer than a strict client timeout. Ask again: the second request returns instantly from the cache and the credit is not charged twice.
- Something else
- Every error has a stable
codelisted in the error table. Still stuck? Email hello@bulktranscripts.co with the tool name and the code.
Agent skill (Claude Code, Codex, OpenClaw & friends)
Prefer a skill over a server? One command installs a skill that teaches your agent the whole API. Create a free API key first at /app → MCP & API:
mkdir -p ~/.claude/skills/youtube-transcripts
curl -fsSL https://bulktranscripts.co/skill.md -o ~/.claude/skills/youtube-transcripts/SKILL.md
OpenClaw users install it from ClawHub instead: openclaw skills install
@pratie/bulktranscripts-youtube (listing:
clawhub.ai/pratie/skills/bulktranscripts-youtube).
No-code tools: n8n, Make, Zapier, Google Sheets
Anything with an "HTTP request" step can use the API. Method GET, the URL from
the table above, and one header: Authorization =
Bearer bt_ak_…. Then read text from the JSON reply.
- n8n — HTTP Request node. Step-by-step: n8n guide.
- Make — HTTP → Make a request. Step-by-step: Make guide.
- Zapier — Webhooks by Zapier → GET, add the header under "Headers".
- Google Sheets — Extensions → Apps Script, paste this, then use
=TRANSCRIPT(A2)in a cell:
const KEY = "bt_ak_YOUR_KEY";
function TRANSCRIPT(video) {
const url = "https://bulktranscripts.co/api/v1/transcript?segments=0&video=" + encodeURIComponent(video);
const res = UrlFetchApp.fetch(url, {headers: {Authorization: "Bearer " + KEY}, muteHttpExceptions: true});
const data = JSON.parse(res.getContentText());
return data.text || (data.error && data.error.message) || "";
}
Questions people ask
Do I need to know how to code?
No. The web app needs nothing, the MCP server is a one-time connect inside your AI assistant, and the no-code tools only need a URL and a header. The code samples are for people who want them.
Is it really free to start?
Yes — 30 credits with a Google sign-in, no card. That is 30 transcripts, searches or listings. Re-reading a transcript you already fetched is free forever.
Where do I find my API key later?
You can't — it is shown once when created. Open MCP & API, revoke the old one, create a new one. Keys are free and you can have five.
What happens when I run out?
The request returns
402 out_of_credits with a purchase link. Buy a pack while signed in with the same
Google account and your existing key keeps working — nothing to reconnect.
Can I get a whole channel at once?
Yes, up to 1,000 videos per job.
Over the API: POST /api/v1/bulk with the channel or playlist URL, then poll
/api/v1/bulk/{run_id}. Over MCP: start_bulk_extract, then
get_bulk_status. The server keeps fetching after the call returns; each new transcript
costs 1 credit and lands in your library. With zero code, the web app does the
whole channel and zips the files.
Why did a video come back with "no_transcript"?
It has no captions at
all — not even auto-generated ones. You are not charged. Try another language first;
if that fails, the video simply has none.
Is my key safe in the playground?
It is stored only in your own browser's local storage, sent only to bulktranscripts.co, and never logged. Clear the field to forget it.
Errors
Every error is JSON with a stable code and a message that says what to do next:
{
"error": {"code": "no_transcript", "message": "No captions available for this video..."}
}
| HTTP | Code | Meaning | Retry? |
|---|---|---|---|
| 400 | invalid_input | Missing or malformed video / channel / playlist / q parameter, or an unknown format. A caller bug. | No — fix the request. |
| 400 | resolution_failed | A well-formed id or URL that could not be resolved (private, removed, region-locked, nonexistent). Not charged. | No. |
| 400 | empty_source | The channel or playlist resolved but contains no videos. | No. |
| 400 | playlist_private | YouTube reports the playlist as nonexistent — which is also what it says about a private playlist. Ask the owner to set Visibility to Unlisted or Public, then retry. Not charged. | After the visibility change. |
| 400 | private_video / members_only / age_restricted | YouTube will not serve this video's captions without a viewer login. Not charged; in a bulk job it counts as skipped. | No. |
| 401 | missing_api_key | No credential on the request. Sign in at /app and create a key. The error object carries setup_url and docs_url. | No — add the key. |
| 401 | invalid_api_key | The Bearer credential is not a valid API key or license key. | No — check the key. |
| 401 | invalid_token | The OAuth access token is expired or revoked — refresh or reconnect. | After refreshing or reconnecting. |
| 402 | out_of_credits | Balance empty. The error object also carries purchase_url, setup_url, and reconnect_steps so an agent can guide the user. | After topping up. |
| 403 | access_blocked | The caller has been blocked for abuse. | No. |
| 404 | no_transcript | The video has no captions at all. Not charged. | No — try another language first. |
| 404 | not_found | Unknown /api/v1 path. | No. |
| 429 | rate_limited | Too many requests. Wait the number of seconds in the Retry-After header, then retry. | Yes, after Retry-After seconds. |
| 429 | too_many_runs | This account already has 2 bulk jobs running. Let one finish, then start the next. | Yes, after Retry-After seconds. |
| 500 | internal_error | Something broke on our side. Retry once, then report it. | Yes, once. |
| 503 | restarting | The server is restarting for an update; back within a minute. | Yes, in a minute. |
| 504 | still_fetching | The transcript is still being fetched and took longer than the request allows. It keeps going in the background. | Yes — the same request returns it, usually within a minute. |
Rate limits
Per public IP, shared between REST and MCP traffic from that IP:
120 requests/minute overall, and 30 requests/minute for
anything under /api/v1/ — including /account and cache hits — plus
any MCP tool call. Exceeding either returns 429 rate_limited with a
Retry-After header in seconds. Bulk jobs have their own budget: 12 starts per
10 minutes per IP, and 2 running at once per account. The REST API fetches one transcript per request;
for batches, pace at under 30 per minute or use the MCP get_transcripts tool,
which fetches up to 20 videos in parallel and counts as a single request. If you are building something high-volume (100k+ transcripts/month),
email us for a custom pack.
Stuck anywhere on this page? Email hello@bulktranscripts.co with what you tried and what you see. A person answers, usually within a day.