# YouTube Transcripts Scraper (`barefoot_grade/youtube-transcripts-scraper`) Actor

Download timestamped transcripts from YouTube videos, playlists and channels in bulk. Manual captions are used before auto-generated ones, and you only pay for transcripts actually delivered.

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

## Pricing

from $5.00 / 1,000 results

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?

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 Transcripts Scraper

Download timestamped transcripts from YouTube videos, playlists and channels in bulk. Each successful video becomes one row with the full text, per-segment timings, caption-language details, and an optional SRT or WebVTT subtitle. Manual captions are preferred over auto-generated ones, and transcript charges apply only to transcripts that are actually delivered, in addition to the run-start charge.

### Quick Start

```json
{
  "sources": [
    "https://www.youtube.com/watch?v=jNQXAC9IVRw",
    "@TED"
  ],
  "maxVideos": 20,
  "languages": ["en", "es"],
  "subtitleFormat": "srt"
}
```

`sources` accepts video links or IDs, channel URLs or @handles, and playlist URLs. A `.../playlist?list=...` URL collects the whole playlist; a watch URL that also carries a `list` parameter collects only that video. Videos are deduplicated across sources, sources are processed in input order, and the run stops at `maxVideos` (successful and unsuccessful videos both count toward the limit).

### Input

| Input | Type | Default | Meaning |
| --- | --- | --- | --- |
| `sources` | array | — | Video URLs or IDs, channel URLs or @handles, and playlist URLs. |
| `maxVideos` | integer | `100` | Total video limit across every source, including videos with no usable captions. |
| `languages` | array | `["en"]` | Preferred caption languages in order, for example `["en", "es", "pt-BR"]`. For each preferred language, manual captions are chosen before auto-generated ones. |
| `allowTranslation` | boolean | `false` | When no preferred track exists, ask for a translation into the first preferred language, if one is offered. |
| `subtitleFormat` | string | `none` | `none`, `srt` or `vtt`. Adds the matching subtitle text field next to the structured transcript. |
| `proxyConfiguration` | object | Apify proxy | Optional proxy settings; the default works for most runs. |

Channel collections cover the **Videos** tab; Shorts and live streams are not included in channel expansion. Individual Shorts and live-video URLs can be supplied directly.

### What it returns

The default dataset contains **only successful transcripts** — one row per video:

| Field | Meaning |
| --- | --- |
| `videoId`, `url` | Unique video ID and canonical watch URL. |
| `title`, `channelName`, `channelId` | Video and creator metadata when available. |
| `publishDate`, `duration` | Publication date and total duration in seconds, when available. |
| `languageCode`, `isAutoGenerated` | The caption language that was selected and whether those captions are auto-generated. |
| `availableLanguages` | The caption languages available for the video, with their auto-generated flags. |
| `transcript` | Caption segments: `[{"start": 0, "duration": 1.5, "text": "…"}]`. `start` and `duration` are in seconds. |
| `fullText` | All segment text joined with newlines. |
| `error` | Always `null` in the default dataset. Videos that fail are reported in `OUTPUT.videoErrors` instead. |
| `srt` or `vtt` | Subtitle text, present when you set `subtitleFormat` to that value. |

The `OUTPUT` store record holds the requested, successful and error counts, the run status, `errorsLocation: "OUTPUT.videoErrors"`, and the `videoErrors` array. Each error entry is the video row, with `code` and `message` nested under `error`, not at the top level. For example:

```json
{
  "videoErrors": [{
    "videoId": "jNQXAC9IVRw",
    "url": "https://www.youtube.com/watch?v=jNQXAC9IVRw",
    "title": null,
    "channelName": null,
    "channelId": null,
    "publishDate": null,
    "duration": null,
    "languageCode": null,
    "isAutoGenerated": null,
    "availableLanguages": [],
    "transcript": [],
    "fullText": "",
    "error": {"code": "NO_CAPTIONS", "message": "No captions available"}
  }]
}
```

This is an illustrative excerpt of `OUTPUT`; metadata may be populated when available. Videos that fail are not dataset rows and incur no transcript charge; the run-start charge still applies.

### Output example

Illustrative transcript row (caption availability and metadata can change):

```json
{
  "videoId": "jNQXAC9IVRw",
  "url": "https://www.youtube.com/watch?v=jNQXAC9IVRw",
  "title": "Me at the zoo",
  "channelName": "jawed",
  "channelId": "UCb1GQXk-4V-G9GQ4m2v5A5g",
  "publishDate": "2005-04-24",
  "duration": 19,
  "languageCode": "en",
  "isAutoGenerated": false,
  "availableLanguages": [{"languageCode": "en", "isAutoGenerated": false}],
  "transcript": [{"start": 0.0, "duration": 1.5, "text": "All right, so here we are..."}],
  "fullText": "All right, so here we are...",
  "error": null,
  "srt": "1\n00:00:00,000 --> 00:00:01,500\nAll right, so here we are...\n"
}
```

### Limits and caveats

- Only caption tracks that are publicly available are collected. Removed, age-restricted, region-restricted, members-only and sign-in-required videos may not be accessible.
- Auto-generated captions can be inaccurate, overlapping or incomplete, and their timing is segment-level, not word-level.
- Some videos have no captions. They are listed in the run summary as `NO_CAPTIONS` and incur no transcript charge. The run-start charge still applies. The same applies to other inaccessible videos, which are listed in `OUTPUT.videoErrors`.
- Metadata such as title, channel or publish date can be `null` when YouTube does not expose it.
- A mixed run keeps its successful rows and lists the failures separately. A run in which every selected video fails exits with an error.
- Large channels may need to be split into smaller `sources` groups.
- Use this actor only where you have the necessary permissions and a lawful basis; YouTube's terms restrict automated access.

Error codes you may see in `OUTPUT.videoErrors`: `NO_CAPTIONS`, `VIDEO_PRIVATE`, `VIDEO_UNAVAILABLE`, `AGE_RESTRICTED`, `LOGIN_REQUIRED`, `BOT_BLOCKED`, `CONSENT_REQUIRED`, `PO_TOKEN_REQUIRED` and `LANGUAGE_UNAVAILABLE`.

### FAQ

**Are manual or automatic captions used?** For each preferred language, a manual caption track is chosen before an auto-generated one. If none of your preferred languages match, the video is reported as `LANGUAGE_UNAVAILABLE` unless you set `allowTranslation: true` and a translation is offered.

**Do failed videos cost anything?** There is no transcript charge for failed videos. The run-start charge still applies.

**Can I get subtitle files?** Set `subtitleFormat` to `srt` or `vtt` and the text arrives in the `srt` or `vtt` field of each row.

**Can I mix videos, channels and playlists in one run?** Yes — put them all in `sources`; they are deduplicated before collection.

### Price

**$0.005 per transcript** delivered, plus **$0.005 per run start**. A start is charged once per GB of memory, so the default 512 MB run is one start. Videos with no transcript, or that otherwise fail, incur no transcript charge; the run-start charge still applies. Platform usage is included in these prices.

# Actor input Schema

## `sources` (type: `array`):

Add video URLs or IDs, channel URLs or @handles, and playlist URLs. Videos are deduplicated across all sources. Use a playlist URL to collect a playlist; a watch URL collects only that video.

## `maxVideos` (type: `integer`):

Total limit across every source, including videos with unavailable captions. Channel collections include the Videos tab, not Shorts or live streams.

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

Language codes such as en, es or pt-BR. For each preferred language, human captions are chosen before automatic captions. If none match, report LANGUAGE_UNAVAILABLE unless translation is enabled.

## `allowTranslation` (type: `boolean`):

When no preferred language track exists, request YouTube's translation into the first preferred language, if available. Disabled by default.

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

Residential proxy is the default. One sticky session is used for each video's player and caption requests; blocked sessions are retried up to three times on the configured route. Explicit overrides are respected; there is no route fallback.

## `subtitleFormat` (type: `string`):

Optionally include an SRT or WebVTT text field alongside the structured transcript.

## Actor input object example

```json
{
  "sources": [
    "https://www.youtube.com/watch?v=jNQXAC9IVRw"
  ],
  "maxVideos": 100,
  "languages": [
    "en"
  ],
  "allowTranslation": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "subtitleFormat": "none"
}
```

# Actor output Schema

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

No description

## `summary` (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 = {
    "sources": [
        "https://www.youtube.com/watch?v=jNQXAC9IVRw"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("barefoot_grade/youtube-transcripts-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 = { "sources": ["https://www.youtube.com/watch?v=jNQXAC9IVRw"] }

# Run the Actor and wait for it to finish
run = client.actor("barefoot_grade/youtube-transcripts-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 '{
  "sources": [
    "https://www.youtube.com/watch?v=jNQXAC9IVRw"
  ]
}' |
apify call barefoot_grade/youtube-transcripts-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,barefoot_grade/youtube-transcripts-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/wNm3ghauI1NPT4zU9/builds/0b6sCVj7A7qtB5liG/openapi.json
