How to Get a YouTube Transcript in JavaScript
Getting a YouTube transcript used to mean either relying on the official API (which is rate-limited and doesn't actually give you transcript text), scraping the page, or hacking together a workaround. This post shows how to do it properly with one fetch call.
We'll use Stophy. You send a video URL, you get back the transcript text and timestamps. No browser automation, no parsing HTML.
Setup
You need Node.js 18+ (fetch is built in) and a Stophy API key. Grab one from stophy.dev/dashboard.
Get the transcript
1async function getTranscript(videoUrl) {2 const res = await fetch('https://api.stophy.dev/v1/video', {3 method: 'POST',4 headers: {5 'Authorization': `Bearer ${process.env.STOPHY_API_KEY}`,6 'Content-Type': 'application/json',7 },8 body: JSON.stringify({ videoUrl, type: 'transcript' }),9 });1011 if (!res.ok) throw new Error(`Request failed: ${res.status}`);12 const { data } = await res.json();13 return data;14}1516const data = await getTranscript('https://www.youtube.com/watch?v=YOUR_VIDEO_ID');17console.log(data.text);data.text is the full transcript as a single string — useful for passing directly to an LLM or writing to a file.
Working with timestamps
The segments array gives you each chunk of text with its start time. Handy for linking to specific moments or chunking for embeddings:
1for (const segment of data.segments) {2 const minutes = Math.floor(segment.start / 60).toString().padStart(2, '0');3 const seconds = Math.floor(segment.start % 60).toString().padStart(2, '0');4 console.log(`[${minutes}:${seconds}] ${segment.text}`);5}Each segment has text, start (in seconds), and duration.
Check if it's auto-generated
1const { language } = data;2console.log(language.isAutoGenerated); // true or falseAuto-generated captions are fine for most videos, but if you're processing something where exact wording matters — interviews, legal content, technical talks — it's good to know what you're working with.
Some videos have no transcript
The API returns a 200 with an empty field instead of throwing an error:
1if ('empty' in data) {2 console.log('No transcript available:', data.empty.code);3} else {4 console.log(data.text);5}Full script
1async function getTranscript(videoUrl) {2 const res = await fetch('https://api.stophy.dev/v1/video', {3 method: 'POST',4 headers: {5 'Authorization': `Bearer ${process.env.STOPHY_API_KEY}`,6 'Content-Type': 'application/json',7 },8 body: JSON.stringify({ videoUrl, type: 'transcript' }),9 });1011 if (!res.ok) throw new Error(`Request failed: ${res.status}`);12 const { data } = await res.json();1314 if ('empty' in data) return null;15 return data.text;16}1718const transcript = await getTranscript('https://www.youtube.com/watch?v=YOUR_VIDEO_ID');19if (transcript) {20 console.log(transcript);21} else {22 console.log('No transcript for this video.');23}The docs have the full response shape and the rest of the endpoints.