Subtext

Subtext API

Transcripts of YouTube videos for programs and AI agents: JSON, plain text, Markdown, SRT or WebVTT over a REST API, and the same two calls as a remote MCP server.

Get an API key Pricing

Quick start

export SUBTEXT_API_KEY=stx_...
curl "https://youtubevideototranscript.com/api/v1/transcript?video=dQw4w9WgXcQ&format=text&timestamps=true" \
  -H "Authorization: Bearer $SUBTEXT_API_KEY"

Authentication

Send the key in the Authorization header of every request: Authorization: Bearer stx_.... A key belongs to one account; an account can hold several keys and they share its credits and its rate limit. A key in front-end code is public: call the API from a server. The dashboard's session cookie is not accepted.

GET /v1/transcript

Returns the transcript of one video. Costs 1 credit for every transcript returned: the same video again, another language or another format is another request and another credit. Requests sent at the same time for one new video share one fetch, and each of them is charged.

ParameterRequiredDefaultValues
videoyes11-character video id or any YouTube URL (URL-encode a URL)
langnothe video's own languagecaption language code, e.g. en, pt-BR
translatenotarget language code. Best effort: YouTube often refuses; a failure costs nothing
formatnojsonjson, text, md, srt, vtt
timestampsnofalsetrue starts each paragraph of format=text with [m:ss]
curl -G "https://youtubevideototranscript.com/api/v1/transcript" \
  --data-urlencode "video=https://www.youtube.com/watch?v=dQw4w9WgXcQ" \
  -H "Authorization: Bearer $SUBTEXT_API_KEY"

format=json answers application/json (values below are an example):

{
  "video_id": "dQw4w9WgXcQ",
  "url": "https://youtu.be/dQw4w9WgXcQ",
  "title": "Example video",
  "author": "Example Channel",
  "author_url": "https://www.youtube.com/@example",
  "language": "en",
  "language_name": "English (auto-generated)",
  "source_language": "en",
  "translated": false,
  "auto_generated": true,
  "duration": 4.5,
  "segments": [
    {
      "start": 0.0,
      "duration": 2.0,
      "text": "Hello and welcome."
    },
    {
      "start": 2.0,
      "duration": 2.5,
      "text": "Today: one idea, explained."
    }
  ]
}

start and duration are seconds. Strings that are unknown are "", never null. The other formats answer the raw body: text is text/plain, md is text/markdown, srt is application/x-subrip, vtt is text/vtt.

GET /v1/languages

Lists the caption languages of a video. Free, but the key is required and the request counts against the rate limit. Use it to choose lang before asking for a transcript. A listing we already hold is answered without asking YouTube. For a video we have not seen yet it asks YouTube for the caption tracks only: no transcript is fetched or stored. That request can fail like a transcript request, with the same 503 and 504.

curl "https://youtubevideototranscript.com/api/v1/languages?video=dQw4w9WgXcQ" \
  -H "Authorization: Bearer $SUBTEXT_API_KEY"
{
  "video_id": "dQw4w9WgXcQ",
  "title": "Example video",
  "author": "Example Channel",
  "author_url": "https://www.youtube.com/@example",
  "default_language": "en",
  "languages": [
    {
      "code": "en",
      "name": "English (auto-generated)",
      "auto_generated": true,
      "translatable": true
    },
    {
      "code": "de",
      "name": "German",
      "auto_generated": false,
      "translatable": true
    }
  ],
  "translation_languages": [
    {
      "code": "fr",
      "name": "French"
    }
  ]
}

GET /v1/account

Free. Tells a program how many credits are left. Counts against the rate limit.

curl "https://youtubevideototranscript.com/api/v1/account" \
  -H "Authorization: Bearer $SUBTEXT_API_KEY"
{
  "credits_remaining": 99,
  "plan": "free",
  "rate_limit_per_minute": 120
}

Response headers

Every answer of the three endpoints carries these once the key is accepted, errors included:

HeaderMeaning
X-Credits-Charged1 when this request spent a credit, otherwise 0
X-Credits-Remainingcredits left on the account
X-RateLimit-Limitrequests per minute allowed for the account
X-RateLimit-Remainingrequests left right now
X-RateLimit-Resetseconds until the limit is full again (a number of seconds, not a clock time)
RateLimit-Policy, RateLimitthe same numbers in the IETF draft format
X-Request-Idquote it when you report a problem
Retry-Afteron 429 and 503: seconds to wait before retrying

Until a key is accepted an answer carries X-Request-Id (and Retry-After on 429 and 503) and none of the credit and rate-limit headers. The same goes for 404 not_found and 405 method_not_allowed, even with a valid key: they do not count against the limit. Answers to CORS preflight requests carry none of these headers.

Answers of /api/v1/* are never cacheable (Cache-Control: no-store). Any origin may call the API from a browser (CORS is open, without credentials).

Errors

Every error is JSON in one shape. Branch on code; message is for people and may change.

{
  "error": {
    "code": "insufficient_credits",
    "message": "You are out of credits.",
    "status": 402,
    "retryable": false,
    "docs_url": "https://youtubevideototranscript.com/api#error-insufficient_credits",
    "request_id": "req_97514c8eed0d4cc7a7b9dc9b",
    "credits_remaining": 0,
    "topup_url": "https://youtubevideototranscript.com/account/billing"
  }
}
StatusCodeMeaning and what to doRetry
401missing_api_keyNo key in the request. Send it as a Bearer token in the Authorization header.no
401invalid_api_keyThe key is mistyped or was revoked. Create a new one on your account page.no
402insufficient_creditsNo credits left and this video is new to your account. The error carries credits_remaining and topup_url.no
404no_captionsThe video has no captions. Do not retry.no
404language_not_availableNo captions in that language. Retry with a code from available_languages in the error.no
404video_unavailableThe video does not exist, is private, was removed or cannot be played.no
404age_restrictedThe video is age-restricted, so its captions cannot be read.no
404translation_not_availableYouTube cannot translate these captions into that language.no
404not_foundThere is no such address under /api; an address with a trailing slash is one of them. Not counted against the rate limit.no
405method_not_allowedThe endpoints answer GET only, so HEAD is refused too. Not counted against the rate limit.no
422invalid_videovideo is not an 11-character video id or a YouTube URL.no
422invalid_parameterA parameter is missing or outside its documented values. The error names it in param.no
429rate_limitedOne of three limits of the account: more requests than its limit per minute, more transcript requests running at once than it has credits, or more than 4 language listings waiting for YouTube at once. Without a valid key: too many requests from your network address. Wait Retry-After seconds, then retry.yes
500internal_errorSomething went wrong on our side. Retry, and send us the request_id if it keeps happening.yes
503source_unavailableYouTube is not answering right now. Wait Retry-After seconds, then retry.yes
503busyOur queue of fetches is full, or our database was locked for a moment. Nothing was charged. Wait Retry-After seconds, then retry.yes
504timeoutThe transcript took too long. It is still being fetched: retry in a few seconds. The request is charged when its transcript arrives, and the retry is then free.yes

Extra members: param (422), available_languages (language_not_available), credits_remaining and topup_url (402), retry_after (429 and 503, the same number as the Retry-After header).

Credits

Limits

MCP server

The same two calls for AI agents, as a remote MCP server (Streamable HTTP, POST only, plain JSON answers):

Tools:

format is one of text (the default), json, md, srt, vtt; timestamps defaults to true.

Claude Code, one command:

claude mcp add --transport http subtext https://youtubevideototranscript.com/mcp \
  --header "Authorization: Bearer $SUBTEXT_API_KEY"

Claude Code, .mcp.json in the project (the type line is required):

{
  "mcpServers": {
    "subtext": {
      "type": "http",
      "url": "https://youtubevideototranscript.com/mcp",
      "headers": {
        "Authorization": "Bearer ${SUBTEXT_API_KEY}"
      }
    }
  }
}

Cursor, .cursor/mcp.json:

{
  "mcpServers": {
    "subtext": {
      "url": "https://youtubevideototranscript.com/mcp",
      "headers": {
        "Authorization": "Bearer ${env:SUBTEXT_API_KEY}"
      }
    }
  }
}

Versioning

The version is in the path (/api/v1). Within v1 changes are additive only: new optional parameters, new response fields, new error codes. Treat an unknown error code by its HTTP status.

Get an API key