Skip to content

Python guide

Get YouTube transcripts
with Python.
One call per video.

Timestamped lines and the full text of a YouTube video’s captions, saved as plain text or SRT subtitles. One API key. No YouTube login, OAuth or proxies.

  • 2 creditsper video
  • 469lines in the recorded video
  • 0YouTube logins
import os
import requests

API_URL = "https://www.monocrawl.com/v1/youtube/post-transcript"
HEADERS = {"x-api-key": os.environ["MONOCRAWL_API_KEY"]}
VIDEO = "https://www.youtube.com/watch?v=ohqxP8EEumo"

body = requests.get(API_URL, params={"url": VIDEO}, headers=HEADERS, timeout=60).json()
if not body.get("success"):
    raise RuntimeError(body["error"]["message"])
data = body["data"]

if not data["has_transcript"]:
    raise SystemExit("This video has no captions")

with open("transcript.txt", "w", encoding="utf-8") as file:
    file.write(data["transcript"])


def srt_time(ms):
    hours, ms = divmod(ms, 3_600_000)
    minutes, ms = divmod(ms, 60_000)
    seconds, ms = divmod(ms, 1000)
    return f"{hours:02}:{minutes:02}:{seconds:02},{ms:03}"

with open("transcript.srt", "w", encoding="utf-8") as file:
    for number, segment in enumerate(data["segments"], start=1):
        start, end = srt_time(segment["start_ms"]), srt_time(segment["end_ms"])
        file.write(f"{number}\n{start} --> {end}\n{segment['text']}\n\n")

$ python transcript.py

GET /v1/youtube/post-transcript v=ohqxP8EEumo

✓ 469 segments · 3,343 words · 2 credits

✓ transcript.txt · 17,840 characters

✓ transcript.srt · 469 subtitles · 16:33 of video

Replay of a real call, recorded 26 Sep 2026

Four steps

Copy, paste, run.
See what each step returns.

Terminal
export MONOCRAWL_API_KEY="mn_your_key_here"
pip install requests

What you get

MONOCRAWL_API_KEY=mn_••••••••Free plan · 1,000 credits a month · No card

What comes back

One call.
The whole transcript.

A real response for Marques Brownlee’s iPhone 18 Pro review: 469 timed lines and 3,343 words of text, for 2 credits. The first spoken lines are below.

  • video_idThe YouTube video id
  • languageThe caption language
  • has_transcriptFalse when the video has no captions
  • transcriptThe full text as one string
  • segmentsEach line with start_ms and end_ms
  1. So, if this were back in 2011, [music]

    0:16–0:22
  2. this is what we would call an S update.

    0:19–0:24
  3. The iPhone 18 Pro is basically an iPhone

    0:22–0:26
  4. 17 Pro with a chip update, a camera

    0:24–0:28
  5. upgrade, and better battery life, plus

    0:26–0:30
  6. some new colors to make sure people know

    0:28–0:32
  7. it's the new one. That's an S update if

    0:30–0:33
  8. we've ever seen one. And I think it's

    0:32–0:35
Show the JSON
{
  "success": true,
  "data": {
    "platform": "youtube",
    "video_id": "ohqxP8EEumo",
    "url": "https://www.youtube.com/watch?v=ohqxP8EEumo",
    "language": "English",
    "has_transcript": true,
    "transcript": "[music] [music] So, if this were back in 2011, [music] this is what we would call an S update. The iPhone 18 Pro is basically an iPhone 17 Pro with a chip update, a camera upgrade, and better battery life, plus some new colors to make sure people know it's the new one. That's an S update if we've ever seen one. And I think it's tempting to be disappointed in that, but after using it for the past week, I actually think it's underrated. I know, I know. Cue all the shill comments. What …",
    "segments": [
      {
        "text": "[music]",
        "start_ms": 1309,
        "end_ms": 3329
      },
      {
        "text": "[music]",
        "start_ms": 7860,
        "end_ms": 9880
      },
      {
        "text": "So, if this were back in 2011, [music]",
        "start_ms": 16560,
        "end_ms": 22160
      },
      {
        "text": "this is what we would call an S update.",
        "start_ms": 19439,
        "end_ms": 24480
      },
      {
        "text": "The iPhone 18 Pro is basically an iPhone",
        "start_ms": 22160,
        "end_ms": 26880
      },
      {
        "text": "17 Pro with a chip update, a camera",
        "start_ms": 24480,
        "end_ms": 28960
      },
      {
        "text": "upgrade, and better battery life, plus",
        "start_ms": 26880,
        "end_ms": 30320
      },
      {
        "text": "some new colors to make sure people know",
        "start_ms": 28960,
        "end_ms": 32399
      },
      {
        "text": "it's the new one. That's an S update if",
        "start_ms": 30320,
        "end_ms": 33520
      },
      {
        "text": "we've ever seen one. And I think it's",
        "start_ms": 32399,
        "end_ms": 35200
      },
      {
        "text": "tempting to be disappointed in that, but",
        "start_ms": 33520,
        "end_ms": 37120
      },
      {
        "text": "after using it for the past week, I",
        "start_ms": 35200,
        "end_ms": 38800
      },
      {
        "text": "actually think it's underrated. I know,",
        "start_ms": 37120,
        "end_ms": 41040
      },
      {
        "text": "I know. Cue all the shill comments. What",
        "start_ms": 38800,
        "end_ms": 42879
      }
    ]
  }
}

Cost

2 credits a video.
Nothing for failures.

Each transcript costs 2 credits, whether the video runs one minute or an hour. The recorded 16:33 review cost 2. Slide to see what a batch costs.

200credits, 2 per video
500videos a month on the free plan
5,000on Starter, $29 a month

The batch endpoint charges the same per video, and leaves failed or empty items uncharged.

Two more things

Many videos,
errors and rate limits.

Up to 20 videos in one call

POST a list of URLs or ids to youtube/transcripts. Each result has its own success flag, data and credits_used; failed or empty items are not charged.

batch.py
BATCH_URL = "https://www.monocrawl.com/v1/youtube/transcripts"
VIDEOS = ["https://www.youtube.com/watch?v=ohqxP8EEumo",
          "https://www.youtube.com/watch?v=jNQXAC9IVRw"]

body = requests.post(BATCH_URL, json={"urls": VIDEOS}, headers=HEADERS, timeout=120).json()
for item in body["results"]:
    if item["success"]:
        print(item["input"], len(item["data"]["segments"]), "segments")
    else:
        print(item["input"], item["error"]["message"])

Errors and rate limits

A failed call returns success: false with an error type and message, and costs nothing. On HTTP 429, wait and try again.

Rate limits by plan
transcript.py
import time

def get_page(params, attempts=5):
    for attempt in range(attempts):
        response = requests.get(API_URL, params=params, headers=HEADERS, timeout=60)
        if response.status_code != 429:
            return response.json()
        time.sleep(2 ** attempt)
    raise RuntimeError("Still rate limited; try again later")

Other platforms

Same loop.
Swap the endpoint.

The same key reads video transcripts on other platforms, at 5 credits each. Each endpoint’s reference lists its fields.

Questions

YouTube transcripts,
answered.

Do I need a YouTube account, OAuth or proxies?

No. You call Monocrawl with your API key and get the transcript back as JSON. It works for public videos with captions.

What if a video has no captions?

has_transcript comes back false and transcript is null. In the batch endpoint, empty items are not charged.

What does it cost?

2 credits a transcript, however long the video. The free plan’s 1,000 credits a month cover 500 transcripts, and failed calls are free.

Do I get timestamps?

Yes. Every segment has start_ms and end_ms, so you can link to a moment in the video or write subtitles, as the SRT step shows.

Can I transcribe many videos at once?

Yes. POST up to 20 URLs or ids to youtube/transcripts. Each item is billed on its own at the same price.

Why not use YouTube’s own API?

YouTube’s API downloads captions only for videos you own or can edit. Our YouTube transcript API comparison sets out the other routes side by side.

Start free

A transcript for any captioned YouTube video.
2 credits a video.

1,000 free credits every month. No card required.

Get a free API key