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

Turn public YouTube video links into transcript text and timestamped segments for research, AI workflows and content briefs. Export SRT or VTT when requested. No YouTube login or API key required; videos must have accessible captions.

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

## Pricing

$2.00 / 1,000 video transcripts

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Turn video links into text with usable timestamps

Retrieve accessible YouTube captions as transcript text, timestamped segments, SRT or VTT. Use the text in your own research or AI workflow. This Actor retrieves captions; it does not transcribe audio or generate summaries. Supply individual video links, not channel or playlist URLs.

### Get a useful first result

1. Open the Input tab and select JSON. Paste the small example below.
2. Check Pricing and set your run spending limit, then start the run.
3. Inspect `transcript`, `segments` and `error`. Each segment includes a start time; review caption accuracy before quoting.
4. Export the result as JSON or CSV. Check a small sample before increasing the input size.

```json
{
  "videoUrls": [
    "https://www.youtube.com/watch?v=jNQXAC9IVRw"
  ],
  "outputFormats": [
    "text",
    "segments"
  ],
  "maxItems": 1
}
```

The sample incurs $0.002 in Actor event fees if it returns the intended number of rows. The rate checked September 7, 2026 is $2.00 per 1,000 output rows. Check the live Pricing tab before running; taxes or other account services may be separate.

### What can I use it for?

- **AI and RAG pipelines.** Turn a supplied list of individual video links into text your LLM can read, chunk and embed. Timestamped segments let you cite the exact moment in the video.
- **Content repurposing.** Draft blog posts, newsletters, show notes and social snippets from what was actually said.
- **Research and monitoring.** Search across hundreds of talks, interviews, earnings calls or lectures for the terms you care about.
- **Accessibility and localisation.** Export SRT or VTT files, or use YouTube's own machine translation to get a transcript in another language.
- **SEO and analysis.** Keyword density, topic clustering and quote extraction across a competitor's catalogue.
- **Data enrichment.** Attach transcripts to a spreadsheet of video links in one run.

### What data does it return?

One record per video:

| Field | Meaning |
|---|---|
| `videoId`, `url`, `input` | The resolved video, its canonical URL and the exact string you supplied |
| `title`, `channelName`, `channelId` | Video metadata from YouTube |
| `durationSeconds`, `viewCount` | Length and public view count |
| `language`, `languageName` | Language of the returned text |
| `sourceLanguage` | The caption track the text came from (differs from `language` when translated) |
| `isAutoGenerated` | `true` when YouTube generated the captions automatically rather than the uploader |
| `isTranslated`, `translatedTo` | Whether YouTube machine-translated the text, and into what |
| `availableLanguages`, `availableLanguagesDetailed` | Every caption track the video offers, with names and auto-generated flags |
| `translationLanguages` | Language codes the track can be translated into |
| `transcript` | The full text, one continuous string |
| `segments` | `[{ start, duration, text }]` in seconds |
| `srt`, `vtt` | Ready-to-save subtitle files (only when requested) |
| `wordCount`, `characterCount`, `segmentCount` | Size of the transcript |
| `error` | `null` on success, otherwise a plain-language reason |

Videos without captions, private videos, age-restricted videos and invalid links are still returned as records with an `error`, so your input and output always line up one to one.

#### Illustrative output

The example below explains the output shape; values are illustrative and are not a live result or a promise that every field is populated.

```json
{
  "videoId": "jNQXAC9IVRw",
  "url": "https://www.youtube.com/watch?v=jNQXAC9IVRw",
  "title": "Me at the zoo",
  "channelName": "jawed",
  "durationSeconds": 19,
  "language": "en",
  "languageName": "English",
  "isAutoGenerated": false,
  "isTranslated": false,
  "availableLanguages": ["en", "es", "fr"],
  "transcript": "All right, so here we are in front of the elephants. The cool thing about these guys is that they have really, really, really long trunks, and that's cool. And that's pretty much all there is to say.",
  "segments": [
    { "start": 0.0, "duration": 2.5, "text": "All right, so here we are in front of the elephants." },
    { "start": 2.5, "duration": 5.0, "text": "The cool thing about these guys is that they have really, really, really long trunks," }
  ],
  "wordCount": 39,
  "segmentCount": 5,
  "error": null
}
```

### How to use it

1. Paste your video links or IDs into **YouTube videos**, one per line. You can also pass them through the API as `videoUrls`.
2. Optionally set a **Preferred language** (defaults to English) and, if you want a translation, a **Translate to** code.
3. Tick the **Output formats** you need. Plain text and timestamped segments are on by default; add SRT or VTT if you want subtitle files.
4. Click **Start**. Download results from the **Output** tab as JSON, CSV, Excel or XML, or read them from the dataset via the API.

#### Input example

```json
{
  "videoUrls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "https://youtu.be/jNQXAC9IVRw",
    "https://www.youtube.com/shorts/aqz-KE-bpKQ"
  ],
  "language": "en",
  "outputFormats": ["text", "segments", "srt"],
  "maxItems": 100
}
```

### How it works

The actor asks YouTube's own player service which caption tracks a video offers, chooses the best match for your preferred language, and downloads that track in YouTube's structured format. It then normalises the timing, decodes text encoding artefacts, and builds the outputs you asked for from the same data, so the plain text, segments, SRT and VTT always agree.

Language selection follows a fixed order you can rely on: an uploaded track in your preferred language, then an auto-generated track in that language, then any uploaded track, then the first track the video has. Set **Prefer human-made captions** to off if you would rather take auto-generated captions when both exist. When you request a translation, the actor translates from the track it selected and reports both the source and target language.

If YouTube refuses a direct request, the actor automatically retries that video through a residential proxy. You do not need to configure anything for this, and it only happens when needed, which keeps the cost per video low.

### Pricing

You pay per video processed. There are no subscriptions or minimums, and you can cap the maximum spend of any run before it starts. Videos that turn out to have no transcript still count as processed, because the actor has to query YouTube to find that out — keep your input list clean to get the most from a run.

### Scope and responsible use

This actor retrieves only the caption data that YouTube already serves publicly for a video. It does not log in, does not use cookies or credentials, does not download video or audio, and does not attempt to bypass age gates, membership restrictions or privacy settings. If a creator has not made captions available, the actor reports that rather than working around it. You are responsible for using transcripts in line with the rights of the people who made the content and the laws that apply to you.

### Limitations

- Videos with no captions of any kind, private videos, members-only videos and most age-restricted videos cannot be transcribed and are returned with an `error`.
- Auto-generated captions are only as accurate as YouTube's speech recognition, and translations are machine-made.
- Live streams that are still in progress do not have a finished transcript.
- Playlists and channel URLs are not expanded; supply the individual video links.

### Something wrong?

Open an issue on this actor's **Issues** tab with the video link and what you expected. YouTube changes its internals from time to time and include the affected input and expected output so the issue can be investigated.

### Before a larger run

Error rows are also charged. Start small and inspect errors before processing a long list. These are paid examples, not a free-results offer. No source-site API key is needed for the example in Console; programmatic Apify API access still requires Apify authentication.

Five internal launch smoke rounds passed on September 6, 2026. This describes small-input test coverage, not customer reviews, a service-level guarantee or a large-batch benchmark. Website behavior and data availability can change.

# Actor input Schema

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

Video URLs or IDs, one per line. Accepts watch URLs, youtu.be links, Shorts, embed URLs and bare 11-character video IDs.

## `language` (type: `string`):

Caption language code to prefer, e.g. en, es, de, ja. If the video has no track in this language the actor falls back to an auto-generated track in the same language, then to the first available track.

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

Optional. Language code to machine-translate the transcript into (uses YouTube's own translation). Leave empty to keep the original language.

## `preferManual` (type: `boolean`):

When a video has both uploaded and auto-generated captions in the preferred language, use the uploaded ones.

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

Which representations to include in each result. Plain text and timestamped segments are always cheap; SRT and VTT are generated from the same data.

## `maxItems` (type: `integer`):

Stop after this many videos.

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

How many videos to process in parallel. YouTube rate-limits caption downloads per IP, so higher values trigger the automatic proxy fallback more often.

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

Optional. The actor already retries through residential proxies automatically if YouTube blocks a request, so you normally leave this off.

## Actor input object example

```json
{
  "videoUrls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "https://www.youtube.com/watch?v=jNQXAC9IVRw"
  ],
  "language": "en",
  "preferManual": true,
  "outputFormats": [
    "text",
    "segments"
  ],
  "maxItems": 2,
  "maxConcurrency": 3,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

Dataset of video transcripts with metadata, language details and timestamped segments.

# 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=dQw4w9WgXcQ",
        "https://www.youtube.com/watch?v=jNQXAC9IVRw"
    ],
    "language": "en",
    "outputFormats": [
        "text",
        "segments"
    ],
    "maxItems": 2
};

// Run the Actor and wait for it to finish
const run = await client.actor("keystonelabs/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=dQw4w9WgXcQ",
        "https://www.youtube.com/watch?v=jNQXAC9IVRw",
    ],
    "language": "en",
    "outputFormats": [
        "text",
        "segments",
    ],
    "maxItems": 2,
}

# Run the Actor and wait for it to finish
run = client.actor("keystonelabs/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=dQw4w9WgXcQ",
    "https://www.youtube.com/watch?v=jNQXAC9IVRw"
  ],
  "language": "en",
  "outputFormats": [
    "text",
    "segments"
  ],
  "maxItems": 2
}' |
apify call keystonelabs/youtube-transcript-scraper --silent --output-dataset

```

## MCP server setup

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