# YouTube Transcript Scraper (`dami_studio/youtube-transcript-scraper`) Actor

YouTube transcript scraper: transcripts as plain text, timed segments, SRT and VTT, from links, video ids or a keyword search. Typed or auto-generated captions (subtitles), in the language you ask for when the video has it, with title, channel and views. No login. $0.80 per 1,000 transcripts.

- **URL**: https://apify.com/dami_studio/youtube-transcript-scraper.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (community)
- **Categories:** Videos, AI, Developer tools
- **Features:** Limited permissions
- **Stats:** 9 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.77 / 1,000 transcripts

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

**YouTube transcript scraper for text, timed segments, SRT and VTT**: paste watch, Shorts, live, embed
or `youtu.be` links or bare 11 character ids, or just type a search, and each video comes back as one
row holding the full plain text, the timed segments, a ready-to-save SRT and a ready-to-save WebVTT.
No API key, no cookies, no login.

The honest limit: it reads caption tracks the video already has. It does not listen to the audio, so
a video nobody captioned and that YouTube never auto-captioned comes back as `NO_TRANSCRIPT`, and no
setting changes that.

| | |
|---|---|
| **Input** | YouTube links, video ids, or search keywords |
| **Output** | One row per video: full text, timed segments, SRT, WebVTT, the language used and every language available, title, channel, views and length |
| **Ceiling** | 400 videos per run |
| **Speed** | Roughly 20 to 30 transcripts a minute on measured cloud runs |
| **Account needed** | None |
| **Price** | $0.80 per 1,000 transcripts on the Free plan, a little less on paid plans. The free plan's $5 a month covers about 6,250 |

### 🔍 What YouTube Transcript Scraper does

For each video it finds the caption tracks, picks one using `languagePreferences` in order, and
pulls the cues. An exact match wins, then a base-language match, so `pt-BR` falls back to `pt`. If
none of your preferences exist it takes another available track rather than failing the video, and
`detectedLanguage` tells you what it actually used.

Manual captions are preferred over auto-generated ones when both exist. `isAutoGenerated` says which
you got, and `availableLanguages` lists everything the video had, so you can re-run for a different
one.

Type something into `searchKeywords` instead and it searches YouTube, takes the top
`maxVideosPerSearch` videos from each search, and handles them like pasted links. Shorts, channels and
playlists are left out of the results. Each row it finds carries `searchKeyword` and `searchRank`.

One charge covers the whole row: the plain text, the segments, the SRT, the VTT, the language list
and the metadata. There is no per-format or per-language extra. A video that turns up in more than one
place, links or searches, is processed once.

### 📋 What data you get from each YouTube transcript

| What you get | Field |
|---|---|
| The whole transcript as one string | `text` |
| Every cue with its start, duration and end in seconds | `segments`, `segmentCount` |
| Subtitle files, ready to save | `srt`, `vtt` |
| The language you got and the one you asked for first | `detectedLanguage`, `requestedLanguage` |
| Machine-made track or a person's | `isAutoGenerated` |
| Every caption track the video carried | `availableLanguages` |
| Video metadata | `videoId`, `url`, `title`, `author`, `channelId`, `viewCount`, `durationSeconds`, `thumbnailUrl` |
| Which search found it, and where it ranked | `searchKeyword`, `searchRank` |

### ▶️ How to scrape YouTube transcripts

1. Open [YouTube Transcript Scraper](https://apify.com/dami_studio/youtube-transcript-scraper) and
   click **Try for free**.
2. Press **Start** with the input empty to see the free sample row.
3. Paste links or ids into **Video URLs or IDs**, or type what you want into **Search keywords**.
4. Set **Preferred transcript languages** if English is not what you want.
5. Download the dataset as JSON, or pull `srt` out of a row and save it as a subtitle file.

### 💰 How much does it cost to scrape YouTube transcripts?

**$0.80 per 1,000 transcripts** on the Free plan. Paid plans pay a little less: $0.79 on Starter, $0.78 on Scale and $0.77 on Business. On the free plan, the $5 Apify gives
you each month covers about 6,250 transcripts.

You pay per transcript returned. The sample row, diagnostic rows and duplicate ids are never billed,
so a batch of 100 videos where 12 have no captions bills 88 transcripts, not 100.

### 📥 What you give it

```json
{
  "videoUrls": [
    "https://www.youtube.com/watch?v=M7lc1UVf-VE",
    "https://youtu.be/dQw4w9WgXcQ",
    "aqz-KE-bpKQ"
  ],
  "languagePreferences": ["fr-CA", "fr", "en"]
}
```

Or search instead of pasting:

```json
{
  "searchKeywords": ["sourdough for beginners", "no knead bread recipe"],
  "maxVideosPerSearch": 10
}
```

| Field | Default | What it is |
|---|---|---|
| `videoUrls` | `[]` | Watch, Shorts, live, embed and `youtu.be` links, or bare ids. Up to 200. The form opens with one example link. |
| `videoIds` | `[]` | A second list for raw ids, handy when your pipeline stores them separately. Up to 200. |
| `searchKeywords` | `[]` | Searches to run, one per line. Up to 20. Works on its own or next to links. |
| `maxVideosPerSearch` | `10` | How many top results to take from each search, 1 to 100. |
| `languagePreferences` | `["en"]` | BCP 47 codes in priority order, such as `en`, `es`, `pt-BR`. Up to 20. |
| `maxConcurrency` | `2` | Videos handled at once, 1 to 10. Low on purpose: it keeps the run steady and cheap. |
| `retries` | `2` | Retries for a video that did not answer, 0 to 5. |
| `requestTimeoutSecs` | `45` | Total time allowed per video, 10 to 180 seconds. |
| `proxyConfiguration` | off | Leave it off. A normal run needs nothing here. |

### 📤 What you get back

A real row from a real run. `text`, `segments`, `srt` and `vtt` are cut short here, because the full
row is about 20 KB of the same transcript in four shapes:

```json
{
  "ok": true,
  "_sample": false,
  "_diagnostic": false,
  "videoId": "M7lc1UVf-VE",
  "url": "https://www.youtube.com/watch?v=M7lc1UVf-VE",
  "title": "YouTube Developers Live: Embedded Web Player Customization",
  "author": "Google for Developers",
  "channelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw",
  "viewCount": 1656845,
  "durationSeconds": 1344,
  "detectedLanguage": "en",
  "requestedLanguage": "en",
  "isAutoGenerated": false,
  "segmentCount": 466,
  "text": "JEFF POSNICK: Hey, everybody. Welcome to this week's show of YouTube Developers Live. ...",
  "segments": [
    { "text": "JEFF POSNICK: Hey, everybody.", "start": 10.349, "duration": 1, "end": 11.349 },
    { "text": "Welcome to this week's show of YouTube Developers Live.", "start": 11.349, "duration": 2.681, "end": 14.03 }
  ],
  "srt": "1\n00:00:10,349 --> 00:00:11,349\nJEFF POSNICK: Hey, everybody. ...",
  "vtt": "WEBVTT\n\n00:00:10.349 --> 00:00:11.349\nJEFF POSNICK: Hey, everybody. ...",
  "availableLanguages": [
    { "languageCode": "en", "languageName": "English", "isAutoGenerated": false },
    { "languageCode": "en", "languageName": "English (auto-generated)", "isAutoGenerated": true }
  ]
}
```

| Field | How to read it |
|---|---|
| `segments` | `start`, `duration` and `end` in seconds, for anything timestamp driven. |
| `srt`, `vtt` | Save the string straight to `.srt` or `.vtt`. |
| `detectedLanguage`, `requestedLanguage` | Compare them to spot a fallback. |
| `isAutoGenerated` | `true` when the track is machine-made, which is where the punctuation and names get loose. |
| `title`, `author`, `channelId`, `viewCount`, `thumbnailUrl` | Any of them can be `null` when YouTube did not return it. |
| `searchKeyword`, `searchRank` | Only on rows that came from a search. |

### 🧾 Reading the output

Three kinds of row can land in your dataset, and the flags are how you tell them apart.

| Row | How to spot it | Billed |
|---|---|---|
| A transcript | `ok: true` and `_sample: false` | yes |
| The sample row | `_sample: true`, `videoId` `SAMPLE00000` | no |
| A diagnostic | `ok: false`, `_diagnostic: true`, an `errorCode` | no |

**The Overview table has no `_sample` column**, so the free sample row reads there as a perfectly good
transcript. Filter on `_sample == false` before counting anything.

The error codes are the useful part of a diagnostic row, and each carries a `hint`:

| `errorCode` | What happened |
|---|---|
| `NO_TRANSCRIPT` | The video has no caption track in any language. |
| `VIDEO_UNAVAILABLE` | Private, removed, or the id does not exist. |
| `LOGIN_REQUIRED` | Age-gated or members-only, so a signed-out reader cannot open it. |
| `INVALID_VIDEO` | The string you passed is not a YouTube link or an 11 character id. |
| `RATE_LIMITED` | That video would not answer this run. Re-running usually clears it. |
| `NO_SEARCH_RESULTS` | A search found no videos. Try fewer or different words. |
| `SEARCH_FAILED` | A search could not be run. Try again in a few minutes. |
| `EXTRACTION_FAILED` | Tracks were found but no cues came back. |
| `TIME_LIMIT` | The run got close to its time limit and stopped early. Every transcript delivered before that is in your dataset; give the run more time, or ask for fewer videos, to get the rest. If none came back at all, the run still ends as succeeded, with an amber status message. |
| `RUN_ERROR` | The run itself failed, written as one row so the reason survives. |
| `UNEXPECTED_ERROR` | The run stopped early on an unexpected error. The results before this row are kept. |

A batch where every video fails still finishes as a successful run, with diagnostics and nothing
else. Count `ok: true` rows rather than trusting the run status.

### 💡 What people use it for

- Turning a talk or interview into text you can search, quote and paste into notes.
- Feeding transcripts to a model for summaries, without paying a speech-to-text bill.
- Making `.srt` files as the starting point for a translation pass.
- Finding the exact moment a phrase was said, using `segments` to jump to the second.

From a topic to what the top videos say and what viewers ask, in two runs:

1. Type a few phrases into **Search keywords** here.
2. Sort the rows on `viewCount` and read the first `segments` of the top videos to see how they
   open.
3. Paste their `url` into the **Video URLs** box of
   [YouTube Comments Scraper](https://apify.com/dami_studio/youtube-comments-scraper) to see the
   questions viewers left.

### 🚧 What it does not do

- **No speech to text.** Published caption tracks only, auto-generated ones included. Nothing
  captioned means nothing to return.
- **No translation.** It fetches tracks that exist. If the video has no French track, asking for
  French gets you another language, not a translation.
- **One video per entry.** Channel and playlist links are not expanded here. YouTube Transcript Bulk
  Scraper does that.
- **`durationSeconds` can be the transcript's length, not the video's**, when YouTube returned no
  metadata for that video. Sanity check it against the last segment's `end` if it matters.
- **Metadata can be `null`.** Title, author, view count and thumbnail come from YouTube's own
  response and are sometimes absent, while the transcript itself is fine.
- **Auto-generated tracks have no reliable punctuation or speaker names**, and get proper nouns
  wrong. `isAutoGenerated` is how you know which you are reading.
- **Rows are large**, roughly 18 to 24 KB each, because text, segments, SRT and VTT hold the same
  words four ways. A 400 video run is a chunky dataset.
- **Search takes YouTube's own top results.** There is no filter for upload date, length or views,
  and a video without captions still uses up one of the places you asked for.
- **400 videos per run** in total, links and search results together.
- It is not an official YouTube tool. Dami's Studio is independent and is not affiliated with or endorsed by YouTube or Google, or by any other company named on this page.

### 🧭 Which YouTube scraper do you need?

| If you want | Use |
|---|---|
| What a video says, as text, SRT and VTT, for links you already have | This one |
| Transcripts for a whole channel or playlist in one run | [YouTube Transcript Bulk Scraper](https://apify.com/dami_studio/youtube-transcript-bulk-scraper) |
| The video, its audio or its subtitles as a file | [YouTube Downloader Pro](https://apify.com/dami_studio/youtube-video-downloader) |
| Comments under a video | [YouTube Comments Scraper](https://apify.com/dami_studio/youtube-comments-scraper) |
| Videos to run this on, by keyword or a channel's newest uploads | [YouTube Scraper](https://apify.com/dami_studio/youtube-scraper) |
| Speech from a video file that has no captions, with your own OpenAI key | [Video & Audio Transcriber](https://apify.com/dami_studio/video-audio-transcriber) |
| The same job on TikTok | [TikTok Transcript Scraper](https://apify.com/dami_studio/tiktok-transcript-scraper) |

### ❓ Questions people ask

#### Do I need a YouTube Data API key?

No. No key, no cookies, no OAuth, no quota.

#### What if the video has no captions?

You get an uncharged `NO_TRANSCRIPT` row and the batch carries on. It does not transcribe the audio.

#### Which language do I get?

The first one in `languagePreferences` that exists, an exact match before a base-language one.
Otherwise another available track, named in `detectedLanguage`.

#### Can I get the SRT file directly?

The `srt` field is the file's contents. Write the string to a `.srt` and it plays.

#### Why is one video missing from my results?

Look for its diagnostic row. `LOGIN_REQUIRED` and `VIDEO_UNAVAILABLE` are permanent for a signed-out
reader, `RATE_LIMITED` is worth retrying.

#### Can I call it from code or connect it to an AI assistant?

The [API tab](https://apify.com/dami_studio/youtube-transcript-scraper/api/python) has code for Python, JavaScript and
the CLI. MCP clients like Claude and ChatGPT use
`https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/youtube-transcript-scraper`; coding assistants can load
[our skill file](https://api.apify.com/v2/key-value-stores/BTBHU3DdiuoF4JOsK/records/apify-youtube-SKILL.md). All
three bill your Apify account at the same price.

#### Is scraping YouTube transcripts legal?

This reads publicly available captions. What you then do with somebody else's words is your call,
and copyright still applies. Apify's write-up on
[the legality of web scraping](https://blog.apify.com/is-web-scraping-legal/) is a good starting
point, and we are not lawyers.

### 🆘 If something breaks

Open the **Issues** tab on the actor page. Include the video link and the run ID, and the `errorCode`
on the diagnostic row if you got one.

# Actor input Schema

## `videoUrls` (type: `array`):

YouTube watch, Shorts, live, embed, or youtu.be URLs. Raw 11-character video IDs are also accepted. Duplicate videos are processed once.

## `videoIds` (type: `array`):

Optional additional list of raw 11-character YouTube video IDs. This is useful for programmatic runs that already have IDs.

## `searchKeywords` (type: `array`):

Search YouTube and get transcripts for the videos it finds, one search per line. Use it on its own or next to links: a video that turns up twice is fetched once.

## `maxVideosPerSearch` (type: `integer`):

How many videos to take from each search, top results first. Videos without captions aren't charged, so you can end up with fewer transcripts than this.

## `retries` (type: `integer`):

Retries for temporary errors. Backoff is automatic.

## `languagePreferences` (type: `array`):

BCP 47 language codes in priority order, for example en, es, fr, or pt-BR. The first available preference is used. If none match, the actor falls back to another available transcript.

## `maxConcurrency` (type: `integer`):

Number of videos processed at once. Keep this low to keep the run steady and cheap.

## `requestTimeoutSecs` (type: `integer`):

Maximum time for all transcript requests for one video.

## `proxyConfiguration` (type: `object`):

Optional. Leave this alone unless you need the run to go out through a particular network. Your own proxy servers are used exactly as given.

## Actor input object example

```json
{
  "videoUrls": [
    "https://www.youtube.com/watch?v=M7lc1UVf-VE"
  ],
  "videoIds": [],
  "searchKeywords": [],
  "maxVideosPerSearch": 10,
  "retries": 2,
  "languagePreferences": [
    "en"
  ],
  "maxConcurrency": 2,
  "requestTimeoutSecs": 45,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (type: `string`):

One charged row is stored for each successfully processed video. Invalid videos, unavailable transcripts, and run diagnostics are stored as uncharged rows.

## `dataset` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "videoUrls": [
        "https://www.youtube.com/watch?v=M7lc1UVf-VE"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami_studio/youtube-transcript-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "videoUrls": ["https://www.youtube.com/watch?v=M7lc1UVf-VE"] }

# Run the Actor and wait for it to finish
run = client.actor("dami_studio/youtube-transcript-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "videoUrls": [
    "https://www.youtube.com/watch?v=M7lc1UVf-VE"
  ]
}' |
apify call dami_studio/youtube-transcript-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/youtube-transcript-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/F1UkNTQcvlSLhvJQd/builds/k8e86VmVV9b0A2BIW/openapi.json
