How to get YouTube transcripts in Node.js
This is a practical guide with code. You will fetch a YouTube transcript in Node.js with a free package, see where it breaks, and then do the same with an API that runs from a server. Node 18 or later has fetch built in, which the examples use.
Why not the official API?
The YouTube Data API's captions.download needs OAuth and requires the user to have permission to edit the video. It also costs 200 quota units. Google So it only works for your own videos.
Option 1: the youtube-transcript package
youtube-transcript is MIT-licensed and exports fetchTranscript(videoIdOrUrl, { lang }). Its README says it uses an unofficial API and may break over time. README
npm i youtube-transcript
import { fetchTranscript } from "youtube-transcript";
const url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ";
try {
const lines = await fetchTranscript(url, { lang: "en" });
const text = lines.map((l) => l.text).join(" ");
console.log(lines.length + " lines");
console.log(text.slice(0, 500));
} catch (err) {
// Error classes include YoutubeTranscriptDisabledError,
// YoutubeTranscriptNotAvailableError and YoutubeTranscriptTooManyRequestError.
console.error(err.name, err.message);
}
Each line has text, offset, duration and lang. One gotcha from the source code: when YouTube returns its newer caption format, offset and duration are in milliseconds; with the classic format they are in seconds. source Normalize before you build timestamps:
// If any offset is larger than the video length in seconds, assume milliseconds.
function toSeconds(lines, videoSeconds) {
const ms = lines.some((l) => l.offset > videoSeconds + 5);
return lines.map((l) => ({ ...l, start: ms ? l.offset / 1000 : l.offset }));
}
Where it breaks
- Cloud servers. The maintainers of the popular Python library report that YouTube blocks most IPs known to belong to cloud providers. youtube-transcript-api README Expect similar trouble for any unofficial client on AWS, GCP or Azure.
- YouTube changes. Unofficial clients break when YouTube changes its pages. Pin your version and watch the repo.
- No captions. Some videos have none. You need speech-to-text on the audio for those.
Option 2: an API, for production
A hosted API runs the fetching for you and returns stable JSON. Here is the same job with ScrapeIO. The response is { success, cached, credits_used, credits_remaining, data }, and data holds id, language, the full transcript string and segments with start_ms and end_ms.
const KEY = process.env.SCRAPEIO_API_KEY;
async function transcript(videoUrl, language = "en") {
const qs = new URLSearchParams({ url: videoUrl, language });
const res = await fetch("https://scrapeio.com/v1/youtube/video/transcript?" + qs, {
headers: { "x-api-key": KEY },
});
const body = await res.json();
if (!body.success) throw new Error(body.error ?? "request failed");
return body.data;
}
const t = await transcript("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
console.log(t.language, t.segments.length, "segments");
// Link each segment to the exact second.
for (const s of t.segments.slice(0, 5)) {
const sec = Math.floor(s.start_ms / 1000);
console.log("https://youtu.be/" + t.id + "?t=" + sec, s.text);
}
Transcribing a whole channel
Once one video works, a channel is a loop: list the uploads, then fetch each transcript. Two rules keep it fast and cheap. First, limit concurrency, because every API has a rate limit; ScrapeIO allows 300 requests a minute per key, so five requests in flight is plenty. Second, cache by video ID. Transcripts rarely change after upload, so you never need to fetch the same video twice.
const cache = new Map();
async function many(urls, limit = 5) {
const out = [];
let i = 0;
async function worker() {
while (i < urls.length) {
const url = urls[i++];
if (cache.has(url)) { out.push(cache.get(url)); continue; }
try {
const t = await transcript(url);
cache.set(url, t);
out.push(t);
} catch (err) {
console.warn("skip", url, err.message);
}
}
}
await Promise.all(Array.from({ length: limit }, worker));
return out;
}
In production, swap the Map for a file, a key-value store or a database table keyed by video ID.
Errors to handle
- No captions: the response has
success: falseand a message that the video has no captions. It is not charged. Mark the video and move on. - 401: the key is missing or wrong. Check the
x-api-keyheader. - 402: not enough credits. Top up and retry.
- 429: you went over 300 requests a minute. Wait and retry with backoff.
Chunking for an LLM
function chunk(segments, maxWords = 500) {
const out = [];
let cur = [], words = 0;
for (const s of segments) {
const n = s.text.split(/\s+/).length;
if (words + n > maxWords && cur.length) {
out.push({ start_ms: cur[0].start_ms, text: cur.map((c) => c.text).join(" ") });
cur = []; words = 0;
}
cur.push(s); words += n;
}
if (cur.length) out.push({ start_ms: cur[0].start_ms, text: cur.map((c) => c.text).join(" ") });
return out;
}
Keep start_ms on each chunk so answers can cite a timestamp. If you embed the chunks for search, store the video ID and the chunk's start time as metadata. When a user asks a question, you can then answer with a link that opens the video at the right second, which builds far more trust than a bare quote.
On language: pass the code you want, such as en or de. If the video has no track in that language, you may get another available track, and data.language tells you which one it was. Check it before you send the text to a model that expects one language.
Which to use
| youtube-transcript | Hosted API | |
|---|---|---|
| Cost | Free | Per call |
| Runs on cloud servers | Often blocked | Yes |
| Maintenance | You track YouTube changes | Vendor does |
| Timestamp units | Seconds or ms, check | Always ms |
Doing it with ScrapeIO
The YouTube transcript API costs 1 credit per video, and videos without captions are not charged. Pair it with the channel videos API to transcribe a whole channel, or the playlist API for a course. The 100 signup credits are enough to test the code above.
Sources
Checked on 8 November 2026 from each vendor's own pages. Prices and limits change; follow the links before you decide.
- https://developers.google.com/youtube/v3/docs/captions/download
- https://github.com/Kakulukian/youtube-transcript
- https://github.com/jdepoix/youtube-transcript-api
100 free credits, no card. Credits never expire.
Get API key