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

YouTube transcript scraper for videos, Shorts, playlists and channels. Get captions with timestamps, full text, SRT or VTT, in your language with auto-generated fallback, plus title, channel, date and views. No API key. Videos without captions are not charged.

- **URL**: https://apify.com/groupoject/youtube-transcript-scraper.md
- **Developed by:** [Group Oject](https://apify.com/groupoject) (community)
- **Categories:** Videos, AI, Social media
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.90 / 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

YouTube Transcript Scraper pulls captions and subtitles from YouTube videos, Shorts, playlists and whole channels. Each video comes back as one clean row with timestamped segments, the full plain text, optional SRT or WebVTT, and video metadata. No YouTube API key, no login, and **you only pay for videos that actually have a transcript**.

![YouTube Transcript Scraper output in Apify: video ID, title, channel, publish date, views, language and full transcript text](https://api.apify.com/v2/key-value-stores/lwQu1lCMCLQIIhSMQ/records/youtube-transcript-scraper-output.png)

*Real output from a default run of YouTube Transcript Scraper in Apify Console.*

### What can it do?

- **Extract transcripts from any public video URL**: `watch?v=` links, `youtu.be` short links, Shorts, `/live/` and `/embed/` URLs, mobile and music links, or bare 11-character video IDs.
- **Transcribe whole playlists and channels**: paste a playlist URL, a channel URL or an `@handle` and set how many of the newest videos to take. Pick long-form videos, Shorts, live streams or any mix.
- **Choose the language**: list preferred languages in order (`["es", "en"]`). Manual, creator-uploaded subtitles are preferred, and YouTube's auto-generated captions are the fallback. You can also restrict the run to manual-only or auto-only captions.
- **Fall back to the original language** when none of your languages exist, or skip those videos. Your choice.
- **Request a translation**: set `translateTo` and the actor first looks for a native subtitle track in that language, then tries YouTube's built-in auto-translate.
- **Export in the format you need**: timestamped JSON segments (`start`, `duration`, `text`), one plain-text string, an SRT subtitle file and/or a WebVTT file, all inside the same row.
- **Add video metadata**: title, channel name, ID and URL, publish date, category, views, likes, duration, description, tags, thumbnail and a Shorts flag.
- **Monitor channels for new videos**: turn on `onlyNewVideos`, schedule the run, and each run returns transcripts only for videos it has not delivered before. Add `publishedAfter` (a date or a window such as `"7 days"`) to skip older uploads.
- **Stay within budget**: the run stops when it reaches your maximum charge. Videos without captions are listed separately and never billed.

Typical uses: feeding transcripts to ChatGPT, Claude or a RAG pipeline, summarising podcasts and lectures, SEO content research, subtitle export, quote mining, and brand or competitor monitoring.

### What data do you get?

| Field | Description |
|---|---|
| `videoId`, `url`, `title` | Video ID, canonical watch URL and title |
| `channelName`, `channelId`, `channelUrl` | Channel of the video (metadata) |
| `publishDate`, `category` | Publish date-time (ISO 8601 with offset) and YouTube category (metadata) |
| `viewCount`, `likeCount`, `durationSeconds` | Counts at scrape time and video length (metadata) |
| `isShort`, `isLiveContent` | Shorts and live-stream flags (metadata) |
| `description`, `keywords`, `thumbnailUrl` | Description, tags and thumbnail (metadata) |
| `language`, `languageCode` | Language of the returned transcript |
| `isAutoGenerated` | `true` when the captions are YouTube's automatic speech recognition |
| `matchedPreferredLanguage` | `false` when the fallback language was used |
| `isTranslated`, `translatedFrom`, `translationError` | Translation result, and the reason when a translation could not be applied |
| `transcript` | Array of `{ start, duration, text }` segments, in seconds |
| `text` | Full transcript as one plain-text string |
| `srt`, `vtt` | Ready-to-save subtitle files (when selected) |
| `segmentCount`, `wordCount`, `characterCount` | Size of the transcript |
| `availableLanguages` | Every caption track the video offers |
| `sourceType`, `sourceInput`, `scrapedAt` | Which input produced the row, and when |

Videos without a transcript go to the `NO_TRANSCRIPT` record in the key-value store. Failed videos, playlists and channels go to `ERRORS`, and a run summary goes to `SUMMARY`.

### How much does it cost?

You pay **$2.50 per 1,000 transcripts** ($0.0025 per video saved to the dataset). Proxy traffic and compute are included. Videos with no captions, or none in the languages you allowed, are **not charged**. Neither are failed, private or deleted videos.

On paid Apify plans the per-result price drops automatically (Bronze and above get volume discounts); the Pricing tab shows your exact rate.

- **100 lecture videos** from a playlist, all with captions: 100 × $0.0025 = **$0.25**.
- **A 2,000-video channel** where 1,800 videos have captions: 1,800 × $0.0025 = **$4.50**. The 200 videos without captions are free.

Set a maximum charge on the run and the actor stops cleanly before going over it. Check the Pricing tab for the current rate.

### How to use YouTube Transcript Scraper

1. Paste one or more YouTube URLs, video IDs, playlist URLs or channel URLs into **YouTube URLs or video IDs**.
2. Set **Preferred languages** (default `en`) and, if you like, the **Caption type** and **Translate to**.
3. Pick **Output formats**: segments, text, SRT, VTT.
4. For playlists and channels, set **Max videos per playlist or channel** and the **Channel content** types. To get only new uploads on a schedule, turn on **Only new videos (monitoring)**.
5. Click **Start**. Download the dataset as JSON, CSV, Excel or HTML, or read it through the API.

#### Input example

```json
{
  "urls": [
    "https://www.youtube.com/watch?v=iG9CE55wbtY",
    "https://youtu.be/UF8uR6Z6KLc",
    "https://www.youtube.com/shorts/abcdefghijk",
    "https://www.youtube.com/playlist?list=PLxxxxxxxxxxxxxxxx",
    "@channelhandle"
  ],
  "languages": ["en", "es"],
  "captionType": "any",
  "fallbackToAnyLanguage": true,
  "outputFormats": ["segments", "text", "srt"],
  "includeVideoMetadata": true,
  "maxVideosPerSource": 50,
  "channelContentTypes": ["videos", "shorts"]
}
```

#### Output example

The values below are illustrative.

```json
{
  "videoId": "abcDEF12345",
  "url": "https://www.youtube.com/watch?v=abcDEF12345",
  "title": "How Solar Panels Actually Work",
  "channelName": "Example Science",
  "channelId": "UCxxxxxxxxxxxxxxxxxxxxxx",
  "channelUrl": "https://www.youtube.com/@examplescience",
  "publishDate": "2024-03-14T09:00:05-07:00",
  "category": "Education",
  "viewCount": 1843201,
  "likeCount": 52310,
  "durationSeconds": 742,
  "isShort": false,
  "isLiveContent": false,
  "language": "English",
  "languageCode": "en",
  "isAutoGenerated": false,
  "matchedPreferredLanguage": true,
  "isTranslated": false,
  "translatedFrom": null,
  "translationError": null,
  "segmentCount": 3,
  "wordCount": 24,
  "characterCount": 131,
  "text": "Sunlight is made of photons. When they hit a silicon cell they knock electrons loose. That flow of electrons is electricity.",
  "transcript": [
    { "start": 0.48, "duration": 2.9, "text": "Sunlight is made of photons." },
    { "start": 3.38, "duration": 4.12, "text": "When they hit a silicon cell they knock electrons loose." },
    { "start": 7.5, "duration": 3.05, "text": "That flow of electrons is electricity." }
  ],
  "srt": "1\n00:00:00,480 --> 00:00:03,380\nSunlight is made of photons.\n\n2\n...",
  "availableLanguages": [
    { "languageCode": "en", "language": "English", "isAutoGenerated": false },
    { "languageCode": "en", "language": "English", "isAutoGenerated": true },
    { "languageCode": "de", "language": "German", "isAutoGenerated": false }
  ],
  "description": "Why a slab of silicon turns light into power...",
  "keywords": ["solar", "physics", "energy"],
  "thumbnailUrl": "https://i.ytimg.com/vi/abcDEF12345/sddefault.jpg",
  "sourceType": "video",
  "sourceInput": "https://youtu.be/abcDEF12345",
  "scrapedAt": "2026-10-26T12:00:00.000Z"
}
```

### Monitor new videos on a schedule

Get the transcript of every new upload from a list of channels or playlists: podcast and news monitoring, competitor tracking, or a daily AI summary of what your favourite creators published.

1. Put the channel URLs (or `@handles`) and playlist URLs in **YouTube URLs or video IDs**.
2. Turn on **Only new videos (monitoring)** and give the monitor a **State key**, for example `ai-podcasts`. Runs that share a key share the list of videos already delivered, so use one key per monitor.
3. Optional: set **Published after** to a date (`2026-10-01`) or a rolling window (`7 days`). The first run then skips the back catalogue. You can also keep **Max videos per playlist or channel** small (5 to 20).
4. Save the input as a task, then open **Schedules** in Apify Console and run it hourly or daily.
5. Add an integration to the task: a **Slack** or **email** notification, a **webhook**, or a **Make / Zapier / n8n** flow that sends each new transcript to an LLM for a summary.

```json
{
  "urls": ["https://www.youtube.com/@lexfridman", "https://www.youtube.com/@hubermanlab"],
  "onlyNewVideos": true,
  "stateKey": "ai-podcasts",
  "publishedAfter": "7 days",
  "maxVideosPerSource": 10,
  "outputFormats": ["text"]
}
```

How it works:

- The first run delivers the newest videos (up to **Max videos per playlist or channel**). Later runs skip every video already delivered, so you are charged only for new transcripts. A run with no new videos succeeds with an empty dataset.
- A video counts as delivered once its transcript row is saved, or once YouTube confirms it has no captions. YouTube can take a few hours to add auto-generated captions to a fresh upload, so a new video without captions is re-checked on later runs for 24 hours (at no charge) before it is marked as done.
- Videos that failed for other reasons (blocked request, live stream still running, max-charge limit reached) are not recorded and are retried on the next run.
- State is kept in your own named key-value store `youtube-transcript-monitor-<stateKey>` (record `SEEN_VIDEOS`). Delete that record, or change the key, to start over.
- Monitoring and **Published after** apply to channel and playlist inputs. Single video URLs are always fetched.
- Each run's `SUMMARY` record reports how many videos were skipped as already seen, how many were newly recorded and how many are waiting for captions.
- Check at least as often as **Max videos per playlist or channel** new uploads can appear. If a channel publishes 30 videos between two runs and the cap is 10, the 20 older ones are not listed.

### Use with the API

Replace `<YOUR_APIFY_TOKEN>` with your token from Apify Console → Settings → Integrations.

**cURL** (run synchronously and get the dataset items back):

```bash
curl -X POST "https://api.apify.com/v2/acts/groupoject~youtube-transcript-scraper/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["https://www.youtube.com/watch?v=iG9CE55wbtY"], "outputFormats": ["text"]}'
```

**Python** (`pip install apify-client`):

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("groupoject/youtube-transcript-scraper").call(run_input={
    "urls": ["https://www.youtube.com/watch?v=iG9CE55wbtY", "@channelhandle"],
    "languages": ["en"],
    "maxVideosPerSource": 25,
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["videoId"], item["wordCount"], item["text"][:80])
```

**JavaScript** (`npm install apify-client`):

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('groupoject/youtube-transcript-scraper').call({
    urls: ['https://www.youtube.com/playlist?list=PLxxxxxxxxxxxxxxxx'],
    outputFormats: ['segments', 'vtt'],
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.length, 'transcripts');
```

### Use with AI agents / MCP

Connect Claude, Cursor, ChatGPT or any other MCP client to the Apify MCP server with this actor enabled:

```
https://mcp.apify.com?tools=groupoject/youtube-transcript-scraper
```

Example prompt: *"Get the transcript of https://youtu.be/UF8uR6Z6KLc and summarise the three main points in bullet form."*

### Integrations

Send transcripts wherever you need them with Apify integrations: **Make**, **Zapier**, **n8n**, **Google Sheets**, Slack, Airtable, or your own **webhooks**, which fire when a run finishes. Pair a scheduled run with a channel URL to keep a transcript archive up to date, then push new rows to a vector database or a spreadsheet.

### FAQ

#### Is it legal to scrape YouTube transcripts?

The actor reads only public caption data that YouTube shows to any visitor, with no login. Scraping public data is generally lawful, but how you use it matters. Respect copyright, YouTube's terms and privacy laws such as GDPR. If you are unsure about your use case, ask a lawyer.

#### Why did a video return no row?

That video has no captions on YouTube, has none in the languages you allowed (with fallback switched off), or is private, deleted, age-restricted or region-blocked. Check the `NO_TRANSCRIPT` and `ERRORS` records in the run's key-value store, which give the reason and the caption languages the video does offer. These videos are not charged.

#### Does it work for videos without any captions?

No. The actor returns YouTube's own captions, both creator-uploaded and auto-generated. It does not transcribe audio. Most spoken videos have auto-generated English captions. Music-only clips, ambient videos and some Shorts have none.

#### Why is my translation missing?

YouTube's auto-translate endpoint is heavily rate-limited for automated clients. The actor first uses a native subtitle track in your target language when the video has one. If it doesn't and YouTube refuses the translation, the row keeps the original-language transcript and `translationError` explains what happened. You still get the transcript.

#### Do I need proxies?

No setup is needed. YouTube often answers datacenter IPs with a bot check, so the actor tries the Apify datacenter proxy first and automatically retries through Apify residential proxy, with a fresh IP on every retry. That traffic is included in the price.

#### How fast is it, and are there limits?

In our tests, a run with two videos finishes in about 15 seconds, and 60 videos take one to two minutes at the default concurrency of 10. There is no fixed cap on URLs. Playlists and channels are limited by **Max videos per playlist or channel** (up to 5,000 per source), and the whole run is limited by your maximum charge.

#### Can I monitor a channel for new transcripts?

Yes. Turn on **Only new videos (monitoring)**, set a **State key**, save the input as a task and schedule it hourly or daily. Each run returns only transcripts of videos that earlier runs have not delivered, and you pay only for those. See [Monitor new videos on a schedule](#monitor-new-videos-on-a-schedule). If you only need new-upload alerts without transcripts, use [YouTube Channel Monitor](https://apify.com/groupoject/youtube-channel-monitor).

#### Which output format should I use for an LLM?

Use `text` for summarisation and embeddings. Add `segments` when you need timestamps for citations or chaptering. Choose `srt` or `vtt` when you want subtitle files for a video editor or player.

### Related actors

- [YouTube Scraper API](https://apify.com/groupoject/youtube-scraper-api): search results, playlists, channel uploads and video details
- [YouTube Comments Scraper](https://apify.com/groupoject/youtube-comments-scraper-api): comments, likes and reply counts for videos and Shorts
- [YouTube Channel Scraper](https://apify.com/groupoject/youtube-channel-scraper-api): subscribers, video counts and channel details
- [YouTube Channel Monitor](https://apify.com/groupoject/youtube-channel-monitor): scheduled new-video alerts across many channels

# Actor input Schema

## `urls` (type: `array`):

One entry per line. Accepts watch URLs, youtu.be links, Shorts, /live/ and /embed/ URLs, bare 11-character video IDs, playlist URLs (youtube.com/playlist?list=…), and channel URLs or handles (@handle, /channel/UC…, /c/…, /user/…). Playlists and channels are expanded up to "Max videos per playlist or channel".

## `languages` (type: `array`):

Caption languages to look for, in order of preference (ISO codes such as en, es, de, pt-BR). The first one the video offers is used. "en" also matches regional variants such as en-GB.

## `captionType` (type: `string`):

Which captions to accept. "Prefer manual" uses creator-uploaded subtitles when they exist and falls back to YouTube's auto-generated captions.

## `fallbackToAnyLanguage` (type: `boolean`):

If none of the preferred languages exist, return the video's original-language captions instead of skipping the video. Turn off to get only the languages you listed.

## `translateTo` (type: `string`):

ISO code (e.g. es, fr, de) to request YouTube's built-in auto-translation of the chosen captions. YouTube frequently rate-limits translated captions; when that happens the row keeps the original-language transcript and explains why in translationError. Leave empty for no translation.

## `outputFormats` (type: `array`):

What to include in each row: timestamped segments (start, duration, text), plain full text, an SRT subtitle string and/or a WebVTT subtitle string.

## `includeVideoMetadata` (type: `boolean`):

Add channel name and ID, publish date, category, view and like counts, duration, description, tags and thumbnail to each row.

## `maxVideosPerSource` (type: `integer`):

Cap for each playlist URL, and for each selected content type of each channel (newest first). Single video URLs are not affected.

## `channelContentTypes` (type: `array`):

Which channel uploads to take when you paste a channel URL: long-form videos, Shorts and/or past live streams.

## `onlyNewVideos` (type: `boolean`):

For channel and playlist URLs, skip videos already processed in earlier runs with the same state key, so a scheduled run returns only transcripts of newly published videos. A video counts as processed once its transcript row is saved, or once YouTube confirms it has no captions (freshly uploaded videos are re-checked for auto-captions for 24 hours). Single video URLs are always fetched.

## `stateKey` (type: `string`):

Name of the monitor. Runs with the same key share the list of already-processed videos (stored in your named key-value store youtube-transcript-monitor-<key>). Use a different key for each separate monitor. Letters, digits and hyphens; other characters are replaced.

## `publishedAfter` (type: `string`):

Only save channel and playlist videos published after this point: a date (2026-01-31), an ISO date-time, or a rolling window such as "7 days" or "48 hours". Leave empty for no date filter. Single video URLs are not filtered.

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

How many videos to fetch in parallel.

## `residentialFallback` (type: `boolean`):

YouTube often answers datacenter IPs with a bot check. When that happens, retry the video through Apify residential proxy automatically. Recommended.

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

Primary proxy. The default (Apify datacenter proxy) is tried first; residential is used as a fallback when enabled above.

## Actor input object example

```json
{
  "urls": [
    "https://www.youtube.com/watch?v=iG9CE55wbtY",
    "https://youtu.be/UF8uR6Z6KLc"
  ],
  "languages": [
    "en"
  ],
  "captionType": "any",
  "fallbackToAnyLanguage": true,
  "outputFormats": [
    "segments",
    "text"
  ],
  "includeVideoMetadata": true,
  "maxVideosPerSource": 20,
  "channelContentTypes": [
    "videos"
  ],
  "onlyNewVideos": false,
  "stateKey": "default",
  "maxConcurrency": 10,
  "residentialFallback": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `transcripts` (type: `string`):

One row per video with transcript segments, full text, optional SRT/VTT and video metadata.

## `noTranscript` (type: `string`):

Videos that have no captions (or none in your languages). Not charged.

## `errors` (type: `string`):

Videos, playlists or channels that could not be fetched, with reasons.

## `summary` (type: `string`):

Counts, failure reasons, proxy tier usage and estimated traffic.

# 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 = {
    "urls": [
        "https://www.youtube.com/watch?v=iG9CE55wbtY",
        "https://youtu.be/UF8uR6Z6KLc"
    ],
    "languages": [
        "en"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("groupoject/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 = {
    "urls": [
        "https://www.youtube.com/watch?v=iG9CE55wbtY",
        "https://youtu.be/UF8uR6Z6KLc",
    ],
    "languages": ["en"],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("groupoject/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 '{
  "urls": [
    "https://www.youtube.com/watch?v=iG9CE55wbtY",
    "https://youtu.be/UF8uR6Z6KLc"
  ],
  "languages": [
    "en"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call groupoject/youtube-transcript-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,groupoject/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/pBm4xgaPkHPXXlBoN/builds/XOwZ3IP8N80KFvuTi/openapi.json
