Guides

How to Get a YouTube Transcript with an API in Python

Published 2026-11-14 · TextFrom

A transcript gives your application text it can search, display, or pass to a downstream workflow. When the text comes with timestamps, you can also connect a result to a moment in the original video. But there are several choices hidden inside a seemingly simple request: which language should the application use, are automatic captions acceptable, and what should happen if the requested track is unavailable?

This guide shows how to request an existing caption track with TextFrom's API using Python. It does not turn audio into new captions, and it does not guarantee that every video or language has a usable track. Use videos and caption data you have the rights and permissions to process, and review the applicable platform terms before building your workflow.

What you need

  • Python and the requests package.
  • A TextFrom API key stored in an environment variable, not hard-coded in source control.
  • The ID of a video you are authorized to process.
  • A preferred language or an explicit ordered list of acceptable languages.

The free plan includes 1,000 requests per month (counted per UTC month), up to 10 per minute and up to 5 active keys. You sign up with an email address and get a key at once; later you can log in with a 6-digit code sent to that email to create or revoke keys. Plans can change, so check the plan page. A free plan does not mean every request is guaranteed to succeed: the service also has a shared daily capacity, and a request can come back as capacity_exhausted when that is used up.

Install the Python dependency in your own environment:

python -m pip install requests

Set the API key using your shell's normal secret-management method. On a Unix-like shell, a local development session might use:

export TEXTFROM_API_KEY='YOUR_API_KEY'

Replace the placeholder locally. Do not publish the key in a repository, paste it into an AI chat, or expose it in a browser application. If it leaks, log in to the console, revoke it and create a new one.

Make a request

Replace VIDEO_ID with the actual ID. The ordered language list below says that en-US is preferred and en is acceptable as the next choice. It does not mean that the API should silently choose an unrelated language.

import os
import requests

video_id = "VIDEO_ID"  # Replace with an authorized video ID.
url = f"https://api.textfrom.dev/v1/youtube/videos/{video_id}/transcript"

response = requests.get(
    url,
    params={
        "lang": "en-US,en",
        "kind": "manual,auto",
        "format": "json",
    },
    headers={"Authorization": f"Bearer {os.environ['TEXTFROM_API_KEY']}"},
    timeout=20,
)

body = response.json()

if response.ok:
    print("Delivered track:", body["data"]["delivered"])
    for segment in body["data"]["segments"]:
        print(segment["start"], segment["end"], segment["text"])
else:
    error = body.get("error", {})
    print("HTTP:", response.status_code)
    print("Code:", error.get("code"))
    print("Retryable:", error.get("retryable"))

The endpoint is GET /v1/youtube/videos/{id}/transcript. The kind parameter accepts manual and auto, describing creator-provided and automatically generated tracks. The format parameter supports json (the default), text, srt and vtt. If your application needs machine-readable segments, start with JSON.

The code above intentionally prints only the delivered track and segments. For production, handle network errors and unexpected non-JSON responses before calling response.json(). Avoid logging caption bodies and credentials by default, especially when processing content that might contain personal information.

Read the response, not your assumptions

A successful response includes data.requested, data.delivered, data.segments, and request metadata. The following is an abbreviated real response for a public sample video (one segment shown; the request ID is elided):

{
  "data": {
    "requested": {"lang": ["en-US", "en"], "kind": ["manual", "auto"], "format": "json"},
    "delivered": {"lang": "en", "kind": "manual", "source": "native"},
    "segments": [
      {"start": 1.2, "end": 3.36, "text": "All right, so here we are, in front of the elephants"}
    ]
  },
  "meta": {
    "request_id": "…",
    "service": "youtube.transcript",
    "operation": "transcript",
    "billable": true,
    "units": 1
  }
}

The example requested en-US first but delivered en, which the caller explicitly listed as acceptable. manual means a creator-provided track and auto an automatically generated one; neither is a new speech-to-text job performed by the API. Segment start and end values are seconds. If you need word-level timing or speaker labels, do not assume they exist: check the actual response and the service's current documentation.

Handle missing languages and temporary failures

A language_not_available result means the requested language could not be delivered under the request's conditions. The error includes an available list of the tracks the video does have, each with its lang and kind. Check the returned information and the caller's preferences; don't automatically replace a missing language with one the caller never selected.

Treat temporary upstream errors differently. upstream_error and upstream_refused are HTTP 503 responses. A response may include Retry-After; respect it and cap your retries. rate_limit_exceeded (too many requests per minute) and quota_exceeded (the monthly allowance is used up) are 429 responses. Their practical fix is to wait for the relevant limit or reduce requests, not to repeat the same call in a tight loop.

A 401 invalid_token points to an authentication problem. Check whether the key was supplied, remains active, and was copied without extra characters. A 400 not_supported response indicates that the requested operation is not supported, for example speech-to-text for a video without captions; changing the retry interval will not make an unavailable feature appear. The error list in the quickstart has the exact codes.

How is this different from the official captions API?

YouTube's official captions.list endpoint returns caption-track information, not the caption text itself. Its official captions.download method requires the authorized user to have permission to edit the video. Those requirements matter when choosing a workflow for content you own or manage. TextFrom is a separate service, not an official YouTube product, and its availability does not replace your responsibility to check rights and applicable terms.

Next steps

Try the TextFrom quickstart with a video you are authorized to process. First request a single explicit language, inspect delivered, and then add only the fallback languages your application can genuinely use. Once that works, test unavailable-language, authentication, and temporary-error cases before connecting the result to a larger pipeline.

Sources: TextFrom quickstart; YouTube captions.list; YouTube captions.download. Accessed 2026-11-14 PT.

Try it with your own key. Free plan: sign up with an email and get a key at once.

Sign up free