YouTube Transcript API — Captions to Text, SRT & VTT
Pricing
from $0.57 / 1,000 transcript returneds
YouTube Transcript API — Captions to Text, SRT & VTT
Get the transcript of any YouTube video as timed segments, plain text, SRT or VTT. Picks your preferred language, falls back to auto-generated captions and can machine-translate. Returns title, channel, duration and every available language. Proxy included, no API key, no login.
Pricing
from $0.57 / 1,000 transcript returneds
Rating
0.0
(0)
Developer
Insight Solutions
Maintained by CommunityActor stats
0
Bookmarked
6
Total users
3
Monthly active users
9 days ago
Last modified
Categories
Share
Get the transcript of any YouTube video, as data. Give this Actor a list of YouTube links — or bare video IDs — and get back the spoken words as timed segments, plain text, SubRip (.srt) or WebVTT (.vtt), alongside the video's title, channel, duration, publish date and every caption language it offers.
No API key. No login. No cookies to paste. $0.95 per 1,000 transcripts, proxy included ($5.00 from October 20, 2026), videos without captions are free, and a run that returns no transcripts costs nothing at all.
Try it in 30 seconds
Input — this is the Store prefill; paste it and run:
{"videoUrls": ["https://www.youtube.com/watch?v=iG9CE55wbtY","EJXTZ5jpSmk"],"languages": ["en"],"format": "segments"}
One watch URL, one bare video ID — both work, and so do youtu.be share links, Shorts, embeds and live URLs.
Output — one row per video; the fields you will use most are text, segments, language, title and durationSec (full list under Output reference). Anything that could not be fetched comes back as a free diagnostic row (ok: false, errorType, error) instead of a charge.
Price — $0.95 per 1,000 transcripts (from October 20, 2026: $5.00 per 1,000) on the FREE tier (+ $0.001 per run, charged only after the first transcript); volume tiers are Scale $0.76 and Business $0.57 per 1,000, and a video with no captions, a private or deleted video, an entry that was not a video, a video the run never reached and a fetch YouTube blocked are all free. Pay-per-event, no API key, no browser, limited permissions — works over the Apify MCP server (mcp.apify.com) and with agentic (x402) payments.
From code — client.actor("insight.solutions/youtube-transcript-api").call(run_input={…}) with apify-client, or POST https://api.apify.com/v2/acts/insight.solutions~youtube-transcript-api/run-sync-get-dataset-items.
What comes back
{"ok": true,"videoId": "iG9CE55wbtY","url": "https://www.youtube.com/watch?v=iG9CE55wbtY","title": "Do schools kill creativity? | Sir Ken Robinson | TED","channelName": "TED","channelId": "UCAuUUnT6oDeKwE6v1NGQxug","durationSec": 1164,"publishedAt": "2007-01-06T00:00:00.000Z","language": "en", // what you actually got"languageName": "English","isAutoGenerated": false, // human-written caption file, not speech recognition"isTranslated": false, // not machine-translated from another language"languageFallback": false, // true only when none of your languages could be delivered"availableLanguages": [ // every language this video offers, translations included{ "languageCode": "en", "languageName": "English", "isAutoGenerated": false, "isTranslatable": true, "isTranslation": false },{ "languageCode": "es", "languageName": "Spanish", "isAutoGenerated": false, "isTranslatable": true, "isTranslation": false }],"segments": [ // timings in seconds{ "start": 12.32, "duration": 4.08, "text": "Good morning. How are you?" },{ "start": 16.4, "duration": 3.52, "text": "It's been great, hasn't it?" }],"text": "Good morning. How are you? It's been great, hasn't it? …","wordCount": 2543,"charCount": 14208,"retrievedVia": "innertube-android","proxyPath": "datacenter","scrapedAt": "2026-09-09T11:04:22.311Z","source": "youtube.com","sourceUrl": "https://www.youtube.com/api/timedtext?v=iG9CE55wbtY&lang=en&fmt=json3"}
Ask for "format": "srt" or "format": "vtt" and the row gains an srt or vtt field holding a complete, valid subtitle file — numbered cues and HH:MM:SS,mmm for SubRip, a WEBVTT header and HH:MM:SS.mmm for WebVTT, with overlapping cues clamped so a player never doubles a line. Ask for "format": "text" and you get the prose without the segment array.
Use cases
- Summarise or search long-form video — pull
textstraight into an LLM prompt, withwordCountthere so you can size the prompt before you send it. - Build a searchable video archive — one row per video, timings intact, so a search hit can link to
youtube.com/watch?v=ID&t=42s. - Repurpose a channel's back catalogue into articles, show notes or newsletters.
- Ship subtitles —
srtandvttcome out ready to attach to a re-upload or a player. - Feed a research or RAG pipeline — segments carry timestamps, so a citation can point at the second the claim was made.
- Translate a talk — ask for a language the video does not have and YouTube's own machine translation fills it in, flagged as
isTranslated.
How it works, and why it keeps working
YouTube has no public captions API. Everything that reads transcripts reads the same private endpoint the YouTube apps use — and over 2025 and 2026 that endpoint has been progressively fenced off. Single-path scrapers break every few months. This Actor runs a chain of five paths and takes the first that answers:
| Rung | What it is | Why it is on the list |
|---|---|---|
| 1 | InnerTube player, Android client | Loosest validation of the four clients, and the fastest path |
| 2 | InnerTube player, Android VR client | The client that is not subject to the proof-of-origin token requirement — the answer when rung 1 returns a token-gated caption URL |
| 3 | InnerTube player, iOS client | A third policy bucket; occasionally the only one serving a given video |
| 4 | InnerTube player, Web client | The browser context. Most likely to be token-gated, so it goes last |
| 5 | Watch page ytInitialPlayerResponse | The same document scraped from HTML. Slow, but it is an ordinary page load and survives some API-level refusals |
Under all five: Apify datacenter proxy by default, one pinned session per parallel worker. When YouTube refuses an exit IP — HTTP 429, HTTP 403, a bot-check or consent interstitial, a player response saying "sign in to confirm you're not a bot", an empty caption file where the player response listed caption tracks, or a proxy-side 59x — that session is retired and the rung is retried once from a fresh address. Retrying on an address that was just refused only deepens the block, so it is never done.
If a fresh datacenter address is refused too, that one request is repeated through a residential exit — the player call or the caption download, whichever was refused, at most once per video. Residential transfer costs 40× what datacenter transfer does, so it is spent on the requests that need it and on nothing else. The row that comes back is an ordinary paid row and says proxyPath: "residential", so you can see exactly how often the expensive path was needed. Turn the behaviour off with residentialFallback: false and a refused video becomes a free blocked diagnostic row instead.
The retrievedVia field on every row says which rung produced it. If you are monitoring this Actor, that column is the early warning: a shift from innertube-android to the later rungs means the front of the chain is degrading, well before anything actually fails.
How it compares
- Five fallback paths, not one. Most transcript scrapers implement a single client context and break the week YouTube changes it.
- Proxy is in the price, and it is not wasted. Requests go out through Apify datacenter proxy, and a request YouTube actually refuses is repeated once through a residential exit. Both are configured by default and the cost is inside the $0.95/1,000 — you are not billed for proxy traffic on top, and you are not paying for residential bytes on requests that never needed them.
- Failures are free and legible. A video with no captions, a private video or a blocked fetch produces a diagnostic row with an
errorTypeyou can branch on — and no charge. A run whose videos were all usable but produced nothing — no captions, a private video, a fetch YouTube blocked — finishes SUCCEEDED with zero results, a status message that says so, and a free diagnostic row per video telling you why; nothing is charged, start fee included. A run finishes FAILED only when there was nothing usable to attempt, or the Actor itself hit an error. - Language handling that says what it did. You get
language,isAutoGenerated,isTranslatedand the fullavailableLanguageslist, so a fallback is never silent.
Input reference
| Field | Type | Default | What it does |
|---|---|---|---|
videoUrls (required) | array of strings | prefilled with two captioned videos | Watch URLs, youtu.be links, Shorts, embed or live URLs, or bare 11-character video IDs. Duplicates are fetched, and billed, once |
languages | array of strings | ["en"] | Language codes in order of preference. See the fallback order below |
format | segments | text | srt | vtt | segments | The primary representation. srt and vtt are only added to the row when you ask for them |
includeTimestamps | boolean | true | Keep the segments array. Turn off for a much smaller row |
includeMetadata | boolean | true | Fill in title, channel, duration and publish date. Costs no extra request |
maxConcurrency | integer | 4 | Videos in parallel. Each worker keeps its own proxy session |
videoTimeoutSecs | integer | 30 | The whole budget for one video, across every rung, retry and the caption download. Any single request is additionally capped at 15 s |
maxRunSecs | integer | 240 | Whole-run wall-clock budget. When it runs out the Actor keeps what it has and files a free diagnostic row for each video it never reached |
proxyConfiguration | object | Apify datacenter | The exit every request starts on. Set {"useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"]} to send everything residential instead — it works and costs 40× more per gigabyte. Clearing it sends requests from the run's own address |
residentialFallback | boolean | true | Repeat a request YouTube refused through a residential exit, once per video. Off means a refused video gets a free blocked row instead |
How languages resolves, in order — and the row always says which step won:
- a human-written track in one of your languages (exact code first, then the base language:
enacceptsen-GB); - an auto-generated track in one of your languages;
- a machine translation into one of your languages — the translation is always attempted when the track is translatable, even if YouTube's response did not list your language as a target;
- any human-written track the video has;
- any auto-generated track.
Output reference
Every row carries the same keys. ok: true is a transcript; ok: false is a free diagnostic row.
| Field | What it is |
|---|---|
videoId, url, input | The video, its canonical watch URL, and the entry you supplied |
title, channelName, channelId, durationSec, publishedAt | Video metadata, when includeMetadata is on |
language, languageName | The language you actually got |
isAutoGenerated | true when the text came from speech recognition rather than a human-written caption file |
isTranslated | true when YouTube machine-translated an existing track for you |
languageFallback | true when none of your languages could be delivered, so language is something else |
availableLanguages[] | Every language the video offers, machine-translation targets included |
segments[] | { start, duration, text } in seconds |
text | Every segment joined with single spaces |
srt / vtt | A complete subtitle file — present only when that format was requested |
wordCount, charCount | Size of text |
ok, error, errorType | Whether this row is a transcript, and if not, why not |
retrievedVia | Which rung of the fallback chain produced it |
proxyPath | Which kind of exit carried it — datacenter, or residential when the datacenter exit was refused. Null on diagnostic rows |
scrapedAt, source, sourceUrl | When, from where. The caption URL's expiring signature is stripped before it is written |
errorType on a diagnostic row is one of:
| Value | Meaning | Charged? |
|---|---|---|
no-captions | The video has no caption track at all | No |
private | Private, members-only or age-restricted, so a signed-in account would be needed. This Actor never signs in | No |
unavailable | Deleted, region-locked or otherwise unplayable | No |
blocked | YouTube refused our requests, including when the caption file is gated behind a proof-of-origin token | No |
invalid-id | The entry was not a YouTube video — a channel, a playlist, or something else | No |
timeout | The run's maxRunSecs budget ran out before this video was reached | No |
Pricing
$0.95 per 1,000 transcripts. Pay-per-event, with the proxy already inside that number — there is no separate proxy line on your bill for this Actor, whether a transcript came through the datacenter exit or needed a residential retry.
This used to be $4.00 per 1,000. The Actor now sends requests through datacenter proxy and pays residential rates only for the individual request YouTube refuses — about one transcript in five — instead of routing everything residential, and the price came down with the cost, to $0.95.
| Event | What triggers it | FREE | Starter | Scale | Business |
|---|---|---|---|---|---|
| Transcript returned (primary) | One transcript written to your dataset | $0.00095 | $0.00095 | $0.00076 | $0.00057 |
| Run started | Once per run, after the first transcript | $0.001 | $0.001 | $0.001 | $0.001 |
Price change notice. From October 20, 2026 (UTC) the transcript event is $0.005 / $0.005 / $0.004 / $0.003 (FREE / Starter / Scale / Business); the table above applies until then.
Worked example. 250 videos, of which 240 have captions:
- 240 transcripts × $0.00095 = $0.228
- 1 run start = $0.001
- 10 videos without captions = $0.00
- Total: $0.229
What you are never charged for: a video with no captions, a private or deleted video, an entry that was not a video, a video the run never reached before maxRunSecs, or a fetch YouTube blocked. If a whole run comes back empty it finishes SUCCEEDED with zero results and a status message that says why ("0 transcripts. N diagnostic row(s) explain why …"), and bills nothing at all, start fee included.
Set ACTOR_MAX_TOTAL_CHARGE_USD on a run and the Actor stops fetching once the ceiling is in sight, rather than handing you rows it cannot bill or billing you for rows it cannot hand over.
Limits, and the one that might bite
Videos without captions. Plenty of YouTube has no caption track at all — short clips, music videos, some live streams, and anything an uploader has explicitly opted out of. There is nothing to fetch; you get a free no-captions row saying so.
Age-restricted, private and members-only videos. These need a signed-in account. This Actor does not log in, does not accept cookies and does not take a session token, and it never will — that is a deliberate line, not a missing feature. They return a free private row.
Proof-of-origin ("PO") tokens — the real risk. Through 2025 and 2026 YouTube has been rolling out an experiment that marks a caption URL with exp=xpe, and the caption endpoint then answers HTTP 200 with an empty body unless the request carries a token minted by YouTube's own player JavaScript. There is no way to produce that token without running YouTube's JavaScript in a browser.
What this Actor does about it:
- it detects the marker on the caption URL before spending a request on it;
- it moves to the next client in the chain, because the clients are gated independently — an Android VR caption URL is routinely clean when the Android or Web one is not. This is the single reason rung 2 exists;
- when a caption file does come back empty, it treats that as a refusal rather than an absence and repeats that one download through a residential exit, because the experiment's buckets are not the same on every address;
- if every rung is gated and residential is refused too, it writes a free
blockeddiagnostic row whoseerrorsays exactly that, rather than an empty transcript or a silent success.
Today the chain gets past this for the overwhelming majority of videos. If YouTube ever extends the token requirement to every client context, transcripts would stop being retrievable by any means short of a full browser — for this Actor and for every other tool of its kind. We would say so on this page rather than quietly returning worse data, and the run would still cost you nothing.
Rate and reliability. Requests go out through datacenter addresses with per-worker sessions and one rotation per block, and a request that is still refused after that is repeated once through a residential exit. Four videos in parallel is the default because it is where throughput and block rate balance; raising maxConcurrency speeds a long list up and makes blocks more likely.
Use it from an AI agent, or from code
One JSON object in, one flat array out — the shape agent runtimes want. The Actor runs with limited permissions, uses pay-per-event pricing and never enters Standby, so it works over the Apify MCP server and with x402 agentic payments. The Integrations tab pushes results to Slack, a webhook, Zapier, Make, Google Sheets, Snowflake or BigQuery.
curl -X POST "https://api.apify.com/v2/acts/insight.solutions~youtube-transcript-api/run-sync-get-dataset-items?token=$APIFY_TOKEN" \-H "Content-Type: application/json" \-d '{"videoUrls":["iG9CE55wbtY"],"format":"text"}'
# pip install apify-clientfrom apify_client import ApifyClientclient = ApifyClient("<APIFY_TOKEN>")run = client.actor("insight.solutions/youtube-transcript-api").call(run_input={"videoUrls": ["https://www.youtube.com/watch?v=iG9CE55wbtY","https://youtu.be/EJXTZ5jpSmk",],"languages": ["en"],"format": "segments",})for row in client.dataset(run["defaultDatasetId"]).iterate_items():if row.get("ok"):print(row["title"], row["wordCount"], "words", sep=" | ")else:print("skipped:", row["input"], row["errorType"])
Set "format": "text" and "includeTimestamps": false when you only need the words — the payload shrinks by roughly half and costs the same.
FAQ
Does this work on any video? Any public video that has a caption track. Videos with captions turned off, and private, members-only or age-restricted videos, return a free diagnostic row instead.
Are auto-generated captions any good?
They are usable and they are clearly labelled — isAutoGenerated: true. Expect almost no punctuation, sentence boundaries you cannot trust, and mangled proper nouns. A human-written track is always preferred when the video has one.
Can I get a language the video does not have?
Often, yes. If any track is translatable, the Actor asks YouTube for your language and you get a machine translation, flagged isTranslated: true. If YouTube serves nothing for that language, you get the original track and the row says so (languageFallback: true). The quality is YouTube's machine translation, not ours.
Why do I sometimes get a different language than I asked for?
Because the video did not offer yours and could not translate into it. The row still tells you exactly what you got — language, plus the full availableLanguages list, so you can decide what to do next.
Do I need my own proxy? No. Apify datacenter proxy is configured by default, a request YouTube refuses is repeated through a residential exit automatically, and the cost of both is inside the per-transcript price.
Will it handle a playlist or a whole channel? Not yet — this Actor takes videos, one row each. Feed it a list of video URLs from wherever your list comes from.
What happens if one video fails?
The others still run. The failed one produces a free diagnostic row and the run finishes SUCCEEDED. If every video fails the run still finishes SUCCEEDED, with zero transcripts, a status message that says so and nothing billed at all. Only an input with no usable video URL or ID in it finishes FAILED.
How long can a run take?
maxRunSecs, 240 seconds by default, checked before each video. When it runs out the Actor stops fetching, keeps and bills for everything already collected, files a free timeout row for each video it never reached, and finishes.
Is the data fresh? Live. Every run reads YouTube at that moment; nothing is cached.
Legal and data-protection notes
- Public videos only. Every source is a public video page or the public caption endpoint behind it. The Actor never logs in, never accepts cookies or session tokens, never takes an API key belonging to anyone else, and never touches private, members-only or age-restricted content.
- Captions are the uploader's copyright. This Actor retrieves them; it does not license them. Republishing a transcript, or training on it, is your call and your responsibility, subject to YouTube's terms and to copyright law where you operate. Quoting, summarising, searching and indexing are the ordinary uses and are what this is built for.
- Expiring credentials are stripped. The caption URL YouTube signs is a short-lived bearer credential.
sourceUrlrecords where the transcript came from with that signature removed. - No personal data by design. A transcript is the spoken content of a public video. The Actor stores no viewer data, no account data and no cookies, and it writes only the video's own public metadata alongside the text.
- Not affiliated with YouTube, Google LLC, or with any channel or creator whose video you retrieve. All product names and trademarks belong to their respective owners and are used only to describe which public endpoints this Actor reads.
Our other Actors
Every Insight Solutions Actor is pay-per-result with no browser, no login and no API key, and every one of them returns free diagnostic rows instead of billing for failures. Prices are per 1,000 results.
Video, audio & social
- YouTube Comments API — comments and replies with likes, pinned and hearted flags, newest or top sort.
- YouTube Channel API — a channel's videos, Shorts and live streams, plus YouTube search.
- Podcast Search, Episodes & Charts API — Apple Podcasts search, charts and full episode feeds.
- Bluesky Scraper — profiles, posts, followers and follows from the public AT Protocol API.
- Telegram Channel Scraper — posts, views and channel stats from public Telegram channels.
- Substack Scraper — posts with full free text, comments and publication profiles.
- Hacker News API — stories, comments, users, front page and a structured "Who is hiring?" parser from the official HN APIs.
- GitHub Repos, Trending & Releases API — repository facts, trending lists, releases and search, no token needed for small runs.
- Discourse Forum API — topics, posts and categories from any Discourse community via its own JSON endpoints, usernames only.
News, documents & the web
- Google News Search, Topics & Real Article URLs — news search and topic feeds with the publisher's real URL decoded.
- Website to Markdown — Content Extractor for LLMs & RAG — any site as clean Markdown, text and heading-aware chunks.
- WordPress Posts & Pages API — posts and pages from any WordPress site as clean text or Markdown, with categories, tags and images.
- Website Metadata API — any URL's meta tags, Open Graph, Twitter cards, hreflang, feeds, JSON-LD and microdata, with normalised Product, Article, Event and FAQ entities.
- Subdomain Finder API — every subdomain of a domain from certificate-transparency logs, resolved over DNS, with an optional live HTTP check.
- Internet Archive API — archive.org search, item metadata, files and reviews.
- Wayback Machine Toolkit — archived URL inventories, snapshots and text diffs between dates.
- Website Technology Detector — the tech stack behind any site, with the evidence for each detection.
- Domain Intelligence API — DNS, RDAP registration, TLS certificate and HTTP facts in one row per domain.
- SEO Page Audit — sitemap crawl with on-page checks, structured data and broken-link reports.
- Keyword Suggestions API — Google, YouTube, Bing, Amazon and eBay autocomplete with alphabet and question expansions.
- Website Contact Extractor — emails, phone numbers and social profiles from any list of websites.
- Web Search Results API — Bing and DuckDuckGo organic results with snippets, no key, no browser.
- Company Enrichment API — a domain in, a company profile out: firmographics, contacts, tech stack, DNS and hiring signal.
- Company Dossier API — one company in, twelve sections out: profile, tech, contacts, DNS, open roles, news, SEC filings, federal awards, recalls, YC batch and apps.
- Press Releases API — GlobeNewswire and PR Newswire releases plus any newsroom feed, by keyword, company, ticker or subject.
- Federal Register API — rules, proposed rules, notices and the Public Inspection desk with dockets, comment deadlines and CFR references.
- Academic Papers Search API — OpenAlex, Crossref, arXiv and PubMed in one row per paper: abstract, citations, open-access PDF, authors and venue.
- RSS & Atom Feed Monitor — any RSS, Atom or JSON feed (or an OPML file) in, only the new items out, with keyword filters and a webhook.
- Website Change Monitor — watch any pages, diff the text between runs, get change rows with added/removed lines, keyword alerts and a webhook.
- Wikipedia & Wikidata API — article text, search, daily pageviews and Wikidata entity facts, any language edition.
Business, finance & jobs
- Congress & Insider Trades API — STOCK Act periodic transaction reports and SEC Form 4 insider trades in one schema.
- Federal Contracts, Grants & Lobbying API — SAM.gov opportunities, USAspending awards, Grants.gov notices and Senate lobbying filings in one schema.
- SEC EDGAR API — filings, XBRL financials and full-text search by ticker or CIK.
- Clinical Trials & FDA API — ClinicalTrials.gov studies plus openFDA recalls, labels, approvals, 510(k)s and adverse-event reports.
- Product & Vehicle Recalls API — CPSC, NHTSA, FDA and USDA recalls, vehicle complaints and ratings, plus a VIN decoder.
- Y Combinator Companies, Batches & Founders — the YC directory with founders and social links, filterable by batch, industry and hiring status.
- Career Site Jobs API — jobs straight from Greenhouse, Lever, Ashby, Workable and 10+ other ATS career sites.
- Workday Jobs API — every posting on any Workday career site, with facets, search, posted dates and full descriptions.
- New Job Postings Monitor — new, closed and changed postings on the career sites you watch.
- Hiring Signals API — Open Roles & Hiring Surge by Company — one row per company per run: open roles, what opened and closed, department and seniority breakdowns, and a hiring-surge flag.
- Remote Jobs API — RemoteOK, Remotive, We Work Remotely, Himalayas, Jobicy and more in one schema, deduplicated.
- Shopify Products API — any Shopify store's catalogue, variants, prices and stock signals.
- WooCommerce Products API — any WooCommerce store's catalogue, prices, variants and stock through its public Store API.
- Shopify App Store API — Shopify App Store listings by category, collection or developer: pricing plans, ratings and merchant reviews.
- Luma Events API — upcoming Luma events by city, category or calendar: dates, venue, price, capacity and organiser calendar, no host names.
- Shopify Store Monitor — price drops, sales, restocks, sell-outs and new products on any Shopify store, one row per change.
- Public Tenders API — EU TED, UK Find a Tender and Contracts Finder notices by keyword, CPV code, country, stage and deadline.
- Nonprofit & IRS 990 Lookup API — search US nonprofits and get EIN, NTEE code and multi-year Form 990 financials.
- OpenStreetMap Places API — businesses and points of interest by category and area from OpenStreetMap: name, address, coordinates, website, phone, opening hours.
Apps & games
- App Store & Google Play Reviews API — reviews from both stores with ratings, versions and developer replies.
- App Store Top Charts & App Search API — Apple top charts by country and genre, plus app search and details.
- App Store Keyword Rank Tracker — where any app ranks for any keyword on the App Store and Google Play, with rank changes and ASO suggestions.
- Steam Reviews API — Steam reviews with playtime, helpfulness and game details.
- Steam Game Data API — prices, tags, review scores, live player counts and top charts.
- Sports Scores & Schedules API — scores, schedules, standings, teams and headlines for the NFL, NBA, MLB, NHL, soccer and more, no key.
- App Store Monitor — App Store and Google Play listings watched between runs: new versions with release notes, price, rating and listing changes, one row per change.