← Back to blog

How to Get a YouTube Transcript as JSON in 5 Minutes

David Boulen · 6/20/2026 · 9 min read

How to Get a YouTube Transcript as JSON in 5 Minutes

How to Get a YouTube Transcript as JSON in 5 Minutes

You want the text of a YouTube video, with timestamps, in a shape your code can actually use. You don't want to parse XML caption tracks, babysit a scraper, or fight IP blocks on your production server. This guide gets you from zero to a clean JSON transcript in about five minutes, with copy-paste examples in curl, Python, and JavaScript.

We'll keep it practical: lead with the problem, show the request, walk through the response, and call out the gotchas that bite people in production.

Key Takeaways

  • YouTube blocks most datacenter IPs, so the popular youtube-transcript-api library throws RequestBlocked errors once you deploy to AWS, GCP, or Azure (PyPI, 2025).
  • The official captions.download endpoint needs OAuth and only works on videos you own, so it can't fetch arbitrary public transcripts (Google for Developers, 2025).
  • A managed transcript API returns structured JSON (text, start, duration) behind a single Bearer token, handling proxies and retries for you.

Why Not Just Grab the Raw Caption Track?

Raw caption tracks ship as timed XML or VTT, not the JSON your application expects. You'd parse markup, strip styling tags, decode HTML entities, and stitch fragments back into readable lines before writing a single feature. That's plumbing, not product.

Two facts make the DIY path harder than it looks. First, the official YouTube Data API v3 won't help with videos you don't own. The captions.download method requires OAuth 2.0 and only returns tracks for the authenticated owner; request a public video you don't control and you get a 403 (Google for Developers, 2025). So the "official" route is a dead end for most third-party use cases.

Second, the popular open-source youtube-transcript-api library works great on your laptop and then breaks on deploy. As of 2025, its own PyPI docs warn that YouTube blocks most IPs known to belong to cloud providers, so you'll likely hit RequestBlocked or IpBlocked exceptions on any cloud host (PyPI, 2025). The fix is rotating residential proxies, plus handling YouTube's newer bot challenges. That's an ongoing maintenance tax, not a one-time setup.

Structured JSON sidesteps all of it. You get an array of objects with text, start, and duration, ready to feed an LLM, render captions, or index for search. No parsing, no proxy pool to maintain.

Our take: the "works locally, breaks in prod" pattern is the single most common reason teams abandon DIY transcript scrapers. The code isn't wrong; the IP is.

How Do You Authenticate With a Single Bearer Token?

You authenticate with one HTTP header: Authorization: Bearer YOUR_API_KEY. No OAuth dance, no consent screens, no per-video owner checks. YouTube Transcriber starts you with 100 free credits and no credit card, and you pay only for successful calls, which makes testing cheap.

Grab your token from the dashboard, then keep it server-side. Treat it like a password: never commit it to git, never ship it in client-side JavaScript, and rotate it if it leaks. Store it in an environment variable and read it at runtime.

export YT_API_KEY="your_bearer_token_here"

That single token also unlocks the rest of the surface area: searching YouTube, resolving channel handles, listing channel uploads, tracking new videos, and pulling playlist data. So once you're authenticated for transcripts, channel monitoring and bulk ingestion use the same credential.

Copy-Paste Examples: curl, Python, and JavaScript

Here's the whole point of this post: working requests you can paste right now. Each one fetches the transcript for a video ID and returns JSON. Swap VIDEO_ID for a real ID (the part after v= in a watch URL) and use your own token.

curl

curl -s "https://api.getyoutubetranscriber.com/v1/transcript?videoId=VIDEO_ID" \
  -H "Authorization: Bearer $YT_API_KEY"

Python

import os
import requests

API_KEY = os.environ["YT_API_KEY"]

def get_transcript(video_id: str) -> list[dict]:
    resp = requests.get(
        "https://api.getyoutubetranscriber.com/v1/transcript",
        params={"videoId": video_id},
        headers={"Authorization": f"Bearer {API_KEY}"},
        timeout=30,
    )
    resp.raise_for_status()
    return resp.json()["transcript"]

segments = get_transcript("VIDEO_ID")
full_text = " ".join(seg["text"] for seg in segments)
print(full_text[:280])

JavaScript (Node.js)

const API_KEY = process.env.YT_API_KEY;

async function getTranscript(videoId) {
  const url = `https://api.getyoutubetranscriber.com/v1/transcript?videoId=${videoId}`;
  const res = await fetch(url, {
    headers: { Authorization: `Bearer ${API_KEY}` },
  });
  if (!res.ok) throw new Error(`Request failed: ${res.status}`);
  const data = await res.json();
  return data.transcript;
}

const segments = await getTranscript("VIDEO_ID");
const fullText = segments.map((s) => s.text).join(" ");
console.log(fullText.slice(0, 280));

Notice the shape of each example. You make one authenticated GET, you get JSON back, and the provider absorbs the proxy and retry logic. No caption parsing appears anywhere in this code, because it doesn't need to.

Best practice: set a sane request timeout (30s is reasonable) and always check the response status before you read the body. Long videos and cold caches occasionally take a few seconds.

What Does the JSON Response Look Like?

The response is an array of transcript segments, where each segment carries three core fields: text, start, and duration. That structure mirrors how timed captions actually work, so you can rebuild subtitles, jump to timestamps, or merge segments into paragraphs.

{
  "videoId": "VIDEO_ID",
  "language": "en",
  "transcript": [
    { "text": "Welcome back to the channel.", "start": 0.0, "duration": 2.48 },
    { "text": "Today we're building a summarizer.", "start": 2.48, "duration": 3.12 },
    { "text": "Let's start with the API call.", "start": 5.60, "duration": 2.04 }
  ]
}

Here's what each field gives you:

| Field | Type | Meaning | |-------|------|---------| | text | string | The spoken line for this segment | | start | number | Seconds from the video start when the line begins | | duration | number | How long the line stays on screen, in seconds |

For an LLM or RAG pipeline, you often want one big string, so you join every text value. For a clickable transcript UI, you keep start to build deep links like ?v=VIDEO_ID&t=42s. For chunking, you group segments until you hit a token budget, then carry the first segment's start as the chunk timestamp.

Language coverage matters here too. YouTube's auto-translate feature spans 100+ languages, so transcripts aren't an English-only affair (NoteLM, 2026). A good transcript API exposes a language parameter so you can request the track you need across 125+ languages rather than whatever default you're handed.

Gotchas: Missing Captions and Auto-Generated Tracks

Not every video has a transcript, and not every transcript is equal. The two failure modes that trip people up are videos with no captions at all and videos that only have auto-generated ones. Plan for both before you ship.

Some videos genuinely have no caption track. Music videos, very new uploads, and creators who disabled captions return nothing to fetch. Your code should treat "no transcript" as a normal outcome, not a crash. Catch the empty or 404 case and fall back gracefully, maybe by queuing the video for a retry later or skipping it in a batch job.

Auto-generated captions are the second gotcha. They come from speech recognition, so they lack punctuation in places, mangle proper nouns, and stumble on heavy accents or background music. They're usually good enough for search and summarization, but risky for anything you display verbatim as an official quote. When accuracy is critical, prefer manually uploaded tracks where available and flag auto-generated text downstream.

A few more things to watch:

  • Rate limits. Burst too fast and you'll get throttled. Add backoff and a small concurrency cap for bulk jobs.
  • Region and age restrictions. Some videos won't return transcripts in certain regions.
  • Long videos. Multi-hour streams produce thousands of segments; stream or paginate your processing instead of loading everything into memory.

Gotcha: don't assume language: "en" means human-edited English. Auto-generated and manually uploaded tracks can share the same language code, so check the source quality if your use case demands it.

A Real Use Case: Feeding a Summarizer

Say you're building a "summarize this video" feature. The flow is short: take a URL, extract the video ID, fetch the JSON transcript, join the text fields, and send that to your LLM with a summarization prompt. The transcript step, the part that used to mean scrapers and proxies, becomes one function call.

segments = get_transcript("VIDEO_ID")
transcript_text = " ".join(s["text"] for s in segments)

prompt = f"Summarize this video transcript in 5 bullets:\n\n{transcript_text}"
# send `prompt` to your LLM of choice

The same pattern scales to channel monitoring (list a channel's uploads, transcribe new videos, summarize on a schedule) and to dataset building for fine-tuning. Because one Bearer token covers transcripts, search, channel, and playlist endpoints, you don't bolt on a second integration to ingest content at scale.

Frequently Asked Questions

Do I need a YouTube API key to get a transcript?

Not for a managed transcript API. You authenticate with that provider's Bearer token instead. The official YouTube Data API's captions.download needs OAuth and only works on videos you own, so it can't fetch arbitrary public transcripts anyway (Google for Developers, 2025).

Why does youtube-transcript-api work locally but fail in production?

YouTube blocks most datacenter IP ranges. The library's own 2025 PyPI docs warn you'll hit RequestBlocked or IpBlocked errors on AWS, GCP, or Azure, and recommend rotating residential proxies as the fix (PyPI, 2025). Your home IP rarely gets flagged, which is why local runs succeed.

What fields are in the JSON response?

Each transcript segment has three core fields: text (the spoken line), start (seconds from the video start), and duration (how long the line shows, in seconds). Join the text values for one block of prose, or keep start to build clickable, timestamped links.

Can I get transcripts in languages other than English?

Yes. YouTube's auto-translate covers 100+ languages, and quality transcript APIs expose tracks across 125+ languages. Pass a language parameter to request a specific track rather than relying on the default returned by the video.

How do I handle videos with no captions?

Treat it as a normal outcome. Catch the empty or 404 response, then skip the video or queue it for a later retry. Never let a missing transcript crash a batch job, since music videos and brand-new uploads frequently lack tracks.

Wrapping Up

Getting a YouTube transcript as JSON doesn't have to mean parsing XML, maintaining a proxy pool, or fighting OAuth restrictions on videos you don't own. The practical path is one authenticated GET that returns text, start, and duration you can use immediately.

Quick recap:

  • Skip raw caption tracks; request structured JSON instead.
  • Authenticate with a single Bearer token, not OAuth.
  • Plan for missing and auto-generated captions before you ship.

If you want to try it, YouTube Transcriber gives you 100 free credits with no credit card, and you only pay for successful calls. Check the docs, paste one of the examples above, and you'll have a working transcript in a few minutes.