# YouTube Transcript Scraper - Captions to Notion, Slack & Jira. (`techforce.global/youtube-transcript-scraper`) Actor

Scrape any YouTube video transcript with timestamps in 13 languages. Auto-translate, export to JSON/CSV/Excel, or push to Notion, Slack & Jira.

- **URL**: https://apify.com/techforce.global/youtube-transcript-scraper.md
- **Developed by:** [Techforce Global](https://apify.com/techforce.global) (community)
- **Categories:** Agents, Social media, Videos
- **Stats:** 1 total users, 1 monthly users, 89.3% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.10 / actor start

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

## YouTube Transcript Scraper - Captions to Notion, Slack & Jira

[![Apify Actor](https://img.shields.io/badge/Apify-Actor-orange?logo=apify)](https://apify.com/techforce.global/youtube-transcript-scraper)
[![MCP Ready](https://img.shields.io/badge/MCP-Compatible-blue?logo=anthropic)](https://mcp.apify.com?tools=techforce.global/youtube-transcript-scraper)
[![Pricing](https://img.shields.io/badge/Pricing-Usage--based-green)](https://apify.com/techforce.global/youtube-transcript-scraper)
[![Public data only](https://img.shields.io/badge/Data-Public%20captions%20only-informational)](#-compliance--data-privacy)
[![Target](https://img.shields.io/badge/Target-YouTube.com-red)](https://www.youtube.com)

> **The YouTube captions API alternative for videos you don't own.** Google's `captions.download` endpoint requires OAuth as the video owner; this Actor extracts the full transcript of any *public* YouTube video as structured JSON — every segment with start time, duration and text, in 13 languages with automatic translation fallback. **No Google API key, no OAuth, no credentials stored, public captions only** — with GDPR/CCPA and speaker-identifiability guidance written into the docs, not buried. Delivery into Notion, Slack, Jira, GitHub, Linear or Airtable is built in over MCP.

#### 🔐 Trust & compliance at a glance

| | |
| :--- | :--- |
| **Credentials** | None accepted, none stored. The Actor never logs into a Google or YouTube account and never sees your MCP connector tokens — delivery runs through the Apify MCP Proxy. |
| **Data scope** | **Public caption tracks only.** Private, unlisted and age-gated videos are inaccessible by design; a request for one returns `status: "no_transcript"`, never a partial result. |
| **GDPR / UK GDPR / CCPA** | A transcript is a person's speech, so it can be personal data about the speaker and anyone named aloud. The [Compliance section](#-compliance--data-privacy) sets out minimisation, retention and special-category handling in full. |
| **Attribution safety** | `isAutoGenerated: true` flags machine-written text — check it before quoting anyone verbatim. |

Full detail, including copyright and Terms-of-Service position: **[Compliance & Data Privacy](#-compliance--data-privacy)**.

***

#### Official YouTube Data API v3 vs. YouTube Transcript Scraper Actor

The official API's `captions.download` endpoint only works for videos **you own** — it is not a route to third-party transcripts at all:

| Feature / Capability | Official YouTube Data API v3 | YouTube Transcript Scraper Actor (Apify) |
| :--- | :--- | :--- |
| **Download captions of videos you don't own** | ❌ `captions.download` requires OAuth as the video owner | ✅ Any public video with captions |
| **Authentication & setup** | ❌ Google Cloud project + OAuth consent flow | ✅ Zero target-site credentials; only an Apify token |
| **Auto-translation to another language** | ❌ Not provided | ✅ YouTube auto-translation with graceful fallback |
| **Bot-wall / IP blocking** | N/A (quota-gated instead) | ✅ Apify residential proxy wired in by default |
| **Daily quota ceiling** | ⚠️ Per-project daily quota; exceeding it blocks you until reset | ✅ No per-day ceiling |
| **AI agent / MCP native** | ❌ Requires a custom tool wrapper | ✅ Native MCP tool **and** pushes results into your own MCP connectors |
| **Cost model** | Free within quota, then quota-increase requests | Usage-based — no quota wall to hit |

> Quota costs and the `captions.download` ownership restriction are Google's to change — check
> [Google's current docs](https://developers.google.com/youtube/v3/docs/captions/download) if this
> comparison is load-bearing for your decision.

***

### 🤖 AI Discovery & MCP Metadata

Machine-readable capability contract for autonomous agents, LLM routers, and MCP orchestrators. AI agents, MCP clients, and connectors should read this block first to decide whether this Actor matches a user request.

```yaml
ai_discovery:
  slug: "techforce.global/youtube-transcript-scraper"
  actor_id: "youtube-transcript-scraper"
  one_line: "YouTube captions API alternative: extract the full timestamped transcript of one public YouTube video as structured JSON, in a chosen language, without OAuth or video ownership — with optional delivery to an MCP connector."
  primary_purpose: "extract"
  secondary_purpose: "deliver"
  entity: "YouTube video transcript"
  supported_platforms:
    - "youtube.com"
    - "youtu.be"
  supported_tasks:
    - "get-transcript-for-one-video"
    - "get-transcript-in-specific-language"
    - "auto-translate-transcript"
    - "extract-timestamped-caption-segments"
    - "deliver-transcript-to-mcp-connector"
  unsupported_tasks:
    - "batch-multiple-videos-in-one-run (use youtube-full-channel-transcripts-scraper)"
    - "scrape-playlist-or-channel (use youtube-full-channel-transcripts-scraper)"
    - "transcribe-audio-without-captions (no ASR of our own)"
    - "download-video-or-audio-files"
    - "scrape-private-unlisted-or-age-gated-videos"
    - "retrieve-comments-views-or-channel-metadata"
    - "summarize-or-translate-with-an-llm"
  search_keywords:
    # First entry is the H1 / meta-description target. The rest are deliberately broader for
    # agent-side semantic matching and are NOT a ranking strategy - see the skill's SEO notes.
    - "youtube transcript scraper"
    - "youtube captions api alternative"
    - "youtube subtitle extractor"
    - "get youtube transcript api"
    - "youtube transcript python"
    - "video transcript to notion"
    - "timestamped captions json"
    - "mcp youtube tool"
    - "srt subtitle data extraction"
    - "youtube shorts transcript"
  synonyms:
    - "youtube caption downloader"
    - "youtube subtitle scraper"
    - "video transcript extractor"
    - "youtube caption json export"
  related_concepts:
    ["transcripts", "captions", "subtitles", "localization", "accessibility", "content repurposing"]
  input_entities:
    - "youtube video URL or 11-character video ID"
    - "preferred language code (1 of 13 enum values)"
    - "translate toggle"
  output_entities: ["transcript", "segment"]
  input_rules:
    videoUrl_format:
      description: "Any public YouTube URL shape, or a bare 11-character video ID. Watch links, youtu.be short links, Shorts, embed, /v/, and /live/ URLs all resolve."
      examples:
        - "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
        - "https://youtu.be/dQw4w9WgXcQ"
        - "https://www.youtube.com/shorts/dQw4w9WgXcQ"
        - "dQw4w9WgXcQ"
    language_format:
      type: "enum"
      case_sensitive: false
      values: ["en", "hi", "gu", "pt", "es", "fr", "de", "it", "ja", "ko", "zh", "ru", "ar"]
  default_off_fields: []
  compliance:
    credentials_required: "none — the Actor never logs into a Google/YouTube account and stores no credential"
    data_scope: "public caption tracks only; private, unlisted and age-gated videos are inaccessible by design"
    connector_token_handling: "MCP delivery runs through the Apify MCP Proxy; the Actor never sees your connector tokens"
    personal_data_note: "a transcript is a person's speech and can be personal data about the speaker and anyone named aloud — GDPR / UK GDPR / CCPA-CPRA obligations sit with the caller"
    attribution_flag: "isAutoGenerated: true means the text is machine-written; do not attribute it as a verbatim quote"
    affiliation: "independent Actor; not affiliated with, endorsed by, or sponsored by YouTube or Google"
  limits:
    videos_per_run: 1
    dataset_items_per_run: 1
    languages_supported: 13
    requires_existing_captions: true
  pricing:
    model: "usage-based"
    billing_unit: "Apify platform usage consumed by the run"
    scope: "one video per run, regardless of transcript length"
    note: "Current rates are shown on the Actor's Apify Store page; the Console shows a live total mid-run. A hard per-run ceiling can be set with maxTotalChargeUsd."
    hard_ceiling_parameter: "maxTotalChargeUsd"
  when_to_use: >
    You need the timestamped transcript of a specific public YouTube video —
    for content repurposing, subtitle/localization work, research coding,
    accessibility text, or as retrieval context for an LLM or agent.
  when_not_to_use: >
    You need many videos, a whole playlist or channel, in one run; the video
    has no captions at all (this Actor does not run its own speech
    recognition); or you want video/audio files, comments, or channel stats.
  alternatives:
    - "Use techforce.global/youtube-full-channel-transcripts-scraper for transcripts of every video in a channel in one run"
    - "Use techforce.global/youtube-scraper for video/channel/comment metadata, hashtag and search discovery"
    - "Use techforce.global/youtube-shorts-scraper for a channel's Shorts with engagement metrics and optional video downloads"
    - "Loop this Actor once per video from your orchestration layer for a few specific unrelated videos"
    - "Use a dedicated ASR/Whisper service when the video has no caption track at all"
```

***

```json
{
  "@context": "https://schema.org/",
  "@type": "SoftwareApplication",
  "name": "YouTube Transcript Scraper - Captions to Notion, Slack & Jira",
  "alternateName": "YouTube Captions API Alternative",
  "applicationCategory": "DeveloperApplication",
  "operatingSystem": "Cloud (Apify platform)",
  "description": "The YouTube captions API alternative for videos you don't own: extracts the full timestamped transcript of any public YouTube video as structured JSON — every segment with start time, duration and text — in 13 languages with automatic translation fallback, without a Google API key, OAuth, or video ownership. Public caption tracks only: no account login, no stored credentials, and no access to private, unlisted or age-gated videos, with GDPR/CCPA and speaker-identifiability guidance included.",
  "url": "https://apify.com/techforce.global/youtube-transcript-scraper",
  "offers": {
    "@type": "Offer",
    "priceCurrency": "USD",
    "description": "Usage-based: billed for the Apify platform usage a run consumes. One video per run, any length, with no per-segment charge. Current rates are shown on the Actor's Apify Store page."
  }
}
```

***

### What this Actor does

> 💡 **Need a whole channel, not one video?**
> This Actor handles **one video per run**. For transcripts of every video in a channel (or its latest *N*), use **[YouTube Full Channel Transcripts Scraper](https://apify.com/techforce.global/youtube-full-channel-transcripts-scraper)** instead — same transcript quality, channel-wide in a single run. For views, likes, and comments rather than words, use **[YouTube Scraper](https://apify.com/techforce.global/youtube-scraper)**.

- Accepts **one YouTube video URL** (watch link, `youtu.be` short link, Shorts, embed, `/v/`, `/live/`, or a bare 11-character ID) and resolves the video ID for you.
- Resolves the caption track through **two independent paths**, trying the second automatically if the first is blocked — so one challenged request does not fail the run.
- Handles **every caption format YouTube serves**, normalising all of them to the same segment shape: `start`, `dur`, `text`.
- **Never hard-fails on language.** Native track in your language → used. No native track and `translate: true` → YouTube auto-translation. Translation unavailable or turned off → the video's own caption language is returned with `fallbackUsed: true` and a plain-English `message` explaining what happened.
- Routes traffic through **Apify Proxy** (residential group by default) because YouTube serves datacenter IPs a "Sign in to confirm you're not a bot" wall.
- Optionally **pushes the transcript to an authorized MCP connector** (Notion, Slack, Jira, GitHub, Linear, Airtable, or any MCP-compatible connector) in four delivery modes. The dataset is always saved regardless of delivery settings.
- **Never crashes on a bad video.** Unavailable, private, deleted, or region-blocked videos, caption-less videos, bot walls, and empty caption streams all end as a **successful** run carrying `status: "no_transcript"`, a machine-readable `errorCode`, and a plain-English `message` — so schedules don't alert and agents get a reason they can act on.
- Handles the platform `ABORTING` event so an aborted run exits promptly instead of burning time.

### When to use it — and when not to

| Scenario | Fit |
| --- | --- |
| Repurpose one video into a blog post, thread, or newsletter | ✅ Strong |
| Build SRT/subtitle workflows from timestamped segments | ✅ Strong |
| Feed transcript text to an LLM as retrieval context | ✅ Strong |
| Localize content — request `hi`, `gu`, `es`, `ja`, … with auto-translation | ✅ Strong |
| Accessibility text alternatives for audio/video | ✅ Strong |
| Push a transcript straight into Notion or Slack | ✅ Strong (native MCP delivery) |
| Research coding / qualitative analysis of a specific talk | ✅ Strong |
| Every video in a channel | ⚠️ One video per run — use [YouTube Full Channel Transcripts Scraper](https://apify.com/techforce.global/youtube-full-channel-transcripts-scraper) |
| A handful of specific, unrelated videos | ⚠️ Loop the Actor — one run per video |
| A language outside the 13-value enum | ⚠️ Not selectable — request `en` and translate downstream |
| Video with **no caption track at all** | ❌ No transcript — the run still succeeds, returning `status: "no_transcript"` and the reason |
| Private, unlisted, or age-gated videos | ❌ Not supported — public videos only |
| Views, likes, comments, or channel stats | ❌ Out of scope — use [YouTube Scraper](https://apify.com/techforce.global/youtube-scraper) |
| A channel's Shorts catalogue or video file downloads | ❌ Out of scope — use [YouTube Shorts Scraper](https://apify.com/techforce.global/youtube-shorts-scraper) |
| Summarization or LLM translation of the transcript | ❌ Not performed — returns raw caption text |

***

### ⚡ Execution Rules (Avoid Common Agent Failures)

1. **`videoUrl` is required and has no default.** Unlike some Actors, omitting it — or passing something with no recognisable video ID — no longer fails the run: the Actor reports `errorCode: "MISSING_VIDEO_URL"` or `"BAD_VIDEO_URL"` on a **`SUCCEEDED`** run with one dataset item explaining what to pass. It does not silently scrape a sample video either. The Console pre-fills a demo URL; API/MCP callers must pass their own.
2. **A bare 11-character video ID is accepted.** `dQw4w9WgXcQ` works exactly like a full URL. As a last resort the Actor also searches for any 11-character token in the string, so a malformed input can resolve to an unintended video — always pass a clean URL or ID and check the returned `videoId`.
3. **`language` is a fixed 13-value enum**, lowercase ISO-639-1: `en`, `hi`, `gu`, `pt`, `es`, `fr`, `de`, `it`, `ja`, `ko`, `zh`, `ru`, `ar`. There is no `en-US`, no `pt-BR`, and no free-text language input.
4. **The language you ask for is not guaranteed.** Read `language`, `isTranslated`, `fallbackUsed`, and `message` on the output item — not just your own request. A successful run can legitimately return a different language than requested.
5. **`translate: false` does not fail the run.** It disables auto-translation; the Actor then returns the video's available caption language with `fallbackUsed: true` and a `message` listing the available languages. Branch on `fallbackUsed`, not on run status.
6. **Captions must already exist.** This Actor reads YouTube's caption tracks; it performs no speech recognition. A video with no captions still produces a **successful** run carrying `status: "no_transcript"` and `errorCode: "NO_CAPTIONS"` — not a crash.
   6a. **Video-state problems never fail the run.** Unavailable, private, deleted, or region-blocked videos, bot walls, and empty caption streams all return `SUCCEEDED` with exactly one dataset item whose `status` is `"no_transcript"`, plus an `errorCode` and a plain-English `message`. **Always branch on `item["status"]`**, not on run status — a `SUCCEEDED` run does not guarantee a transcript. A missing or unparseable `videoUrl` is reported the same way, as `MISSING_VIDEO_URL` or `BAD_VIDEO_URL` — **no input mistake fails the run**.
7. **Keep the residential proxy on.** The default is `{ useApifyProxy: true, apifyProxyGroups: ["RESIDENTIAL"] }`. Datacenter IPs regularly hit YouTube's bot wall, which surfaces as a caption-retrieval failure, not a proxy error. Residential groups require a paid Apify plan.
8. **One run = one video = one dataset item.** The transcript is a nested `searchResult` array inside that single item, not one item per segment. `itemCount: 1` is a full result.
9. **`start` and `dur` are strings, not numbers** — e.g. `"3.200"`. Cast before doing arithmetic.
10. **`deliveryMode` defaults to `summary`, not `none`.** For dataset-only runs, either leave `mcpConnector` empty or set `deliveryMode: "none"` explicitly.
11. **`perSegment` delivery issues one connector call per segment.** A 20-minute video can be hundreds of calls — prefer `chunked` or `summary` unless you specifically want per-line records.

```yaml
dependencies:
  - rule: "delivery requires mcpConnector AND mcpTool"
    on_violation: "delivery step is skipped with a warning; the dataset item is still written"
  - rule: "mcpArguments must reference {message} for the rendered template to appear anywhere"
    on_violation: "message body is empty; the tool is still called"
  - rule: "mcpConnector only resolves when the Actor runs on the Apify platform"
    on_violation: "local runs log 'APIFY_MCP_PROXY_URL is not set' and skip delivery; the dataset is unaffected"
  - rule: "chunked mode exposes {part} and {partCount}"
    note: "put them in the page/message title or every part looks identical"
authentication:
  actor_input: none
  platform: "Apify API token required for API / SDK / MCP invocation; paid plan required for residential proxy groups"
  target_site: "no YouTube or Google account, API key, or OAuth app required (public captions only)"
```

***

### 📥 Input Contract

`.actor/input_schema.json` is authoritative — if any table on this page disagrees with the schema files, **the schema files win**. API and MCP clients should always pass `videoUrl` explicitly.

#### Scraping Parameters

| Field | Type | Required | Default | Allowed Values / Format | Example |
| :--- | :--- | :--- | :--- | :--- | :--- |
| `videoUrl` | string | ✅ Yes | none (Console prefill only) | Any public YouTube URL shape, or a bare 11-char ID | `https://youtu.be/dQw4w9WgXcQ` |
| `language` | string (enum) | ⬜ No | `en` | 13 lowercase codes (see below) | `hi` |
| `translate` | boolean | ⬜ No | `true` | `true` / `false` | `false` |
| `proxyConfiguration` | object | ⬜ No | `{ useApifyProxy: true, apifyProxyGroups: ["RESIDENTIAL"] }` | Apify proxy editor object | see below |

#### Accepted URL shapes

| Shape | Example |
| --- | --- |
| Standard watch | `https://www.youtube.com/watch?v=dQw4w9WgXcQ` |
| Short link | `https://youtu.be/dQw4w9WgXcQ` |
| Shorts | `https://www.youtube.com/shorts/dQw4w9WgXcQ` |
| Embed | `https://www.youtube.com/embed/dQw4w9WgXcQ` |
| Live | `https://www.youtube.com/live/dQw4w9WgXcQ` |
| Mobile / music / nocookie hosts | `https://m.youtube.com/watch?v=dQw4w9WgXcQ` |
| Bare video ID | `dQw4w9WgXcQ` |

#### Language reference — all 13 values

`en` English · `hi` हिन्दी / Hindi · `gu` ગુજરાતી / Gujarati · `pt` Português · `es` Español ·
`fr` Français · `de` Deutsch · `it` Italiano · `ja` 日本語 · `ko` 한국어 ·
`zh` 中文 · `ru` Русский · `ar` العربية

Region variants (`en-US`, `pt-BR`, `zh-Hans`) are **not** valid inputs — pass the base code.

#### Language resolution order

| Situation | What you get | `isTranslated` | `fallbackUsed` | `message` |
| :--- | :--- | :--- | :--- | :--- |
| Native caption exists in requested language | That track | `false` | `false` | `null` |
| No native track, `translate: true` | YouTube auto-translation to your language | `true` | `false` | explains the translation |
| Translation returns no text | Video's own caption language | `false` | `true` | explains the failed translation |
| No native track, `translate: false` | Video's own caption language | `false` | `true` | lists the available languages |
| No `language` passed at all | Video's default caption track | `false` | `false` | `null` |
| No caption tracks at all | `status: "no_transcript"`, `errorCode: "NO_CAPTIONS"` — run still succeeds | `false` | `false` | explains that the video has no captions |

#### MCP Delivery Configuration (Optional)

| Parameter | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `mcpConnector` | string | `""` | Authorized MCP connector on your Apify account (Notion, Slack, Jira, GitHub, Linear, Airtable, …). Leave empty for dataset-only runs. |
| `deliveryMode` | enum | `summary` | `summary` (one call, full transcript) · `chunked` (split long transcripts) · `perSegment` (one call per segment) · `none`. |
| `mcpTool` | string | `""` | Tool name on the connector: `create_page`, `send_message`, `create_issue`, `append_row`. Run once with a connector selected — the log lists the connector's available tools. |
| `mcpArguments` | object | `{}` | Arguments passed to the tool. String leaves support `{placeholders}`. |
| `mcpMessageTemplate` | string | `""` | Template rendered and exposed to `mcpArguments` as `{message}`. |

**Placeholders by mode:**

| Mode | Placeholders |
| :--- | :--- |
| `summary` | `{videoUrl}`, `{videoId}`, `{language}`, `{segmentCount}`, `{transcript}` (full text), `{message}` |
| `chunked` | the above, plus `{part}` and `{partCount}` — and `{transcript}` holds one part |
| `perSegment` | `{start}`, `{dur}`, `{text}`, plus `{videoUrl}`, `{videoId}`, `{language}`, `{segmentCount}`, `{message}` |

Delivery packs segments into paragraphs of ~1,800 characters and groups them into parts under ~72,000 characters, so services with per-request block or timeout caps (Notion in particular) never reject the call.

```json
{
  "videoUrl": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "language": "en",
  "translate": true,
  "mcpConnector": "<your-authorized-slack-connector>",
  "deliveryMode": "summary",
  "mcpTool": "send_message",
  "mcpMessageTemplate": "Transcript of {videoUrl} ({language}, {segmentCount} segments):\n\n{transcript}",
  "mcpArguments": { "channel": "#transcripts", "text": "{message}" }
}
```

Notion, chunked across pages (best for long videos):

```json
{
  "videoUrl": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "language": "hi",
  "mcpConnector": "<your-authorized-notion-connector>",
  "deliveryMode": "chunked",
  "mcpTool": "notion-create-pages",
  "mcpArguments": {
    "parent": { "page_id": "<your-page-id>" },
    "pages": [
      {
        "properties": { "title": "Transcript {videoId} (part {part}/{partCount})" },
        "content": "{transcript}"
      }
    ]
  }
}
```

***

### 📤 Output Contract & Data Structure

**Storage:** Apify Dataset — **exactly one item per run**, with the transcript nested in `searchResult`.
**Pagination:** `limit` & `offset` on the dataset items endpoint (rarely needed for a single item).
**Duplicates:** none within a run. Across runs, dedupe on `videoId` + `language`.

Every field below is **always present** on the item — the shape never changes. What varies is the
*value*, and the **Toggle / controlled by** column names the input that decides it. No output field is
toggled on or off by configuration.

| Field | Type | Nullable | Toggle / controlled by | Meaning |
| --- | --- | --- | --- | --- |
| `videoUrl` | string | No | `videoUrl` | Normalized `https://www.youtube.com/watch?v=<id>` URL — not necessarily the URL you passed |
| `videoId` | string | No | `videoUrl` | Extracted 11-character video ID — **verify this matches the video you meant** |
| `requestedLanguage` | string | No (may be `""`) | `language` | The language code you asked for, trimmed — echoed back verbatim |
| `language` | string | No (may be `""`) | `language` + `translate` | The language actually returned. `""` when no transcript was found |
| `isAutoGenerated` | boolean | No | — (source track) | `true` when the source track is YouTube's ASR (auto-generated) captions |
| `isTranslated` | boolean | No | `translate` | `true` when the text is a YouTube auto-translation |
| `fallbackUsed` | boolean | No | `language` + `translate` | `true` when the requested language could not be honoured |
| `message` | string | **Yes (`null`)** | `language` + `translate` | Plain-English note about any language/translation fallback, or why no transcript exists; `null` on a clean match |
| `segmentCount` | integer | No | — | Number of segments in `searchResult`; `0` when `status` is `"no_transcript"` |
| `searchResult` | array | No (may be `[]`) | — | Transcript segments, in playback order; `[]` when `status` is `"no_transcript"` |
| `searchResult[].start` | string | No (may be `""`) | — | Segment start in seconds, **as a string** — e.g. `"3.200"` |
| `searchResult[].dur` | string | No (may be `""`) | — | Segment duration in seconds, **as a string** — e.g. `"2.900"` |
| `searchResult[].text` | string | No | — | Segment text, HTML-unescaped, newlines collapsed to spaces. Empty segments are dropped. |
| `status` | string | No | — (video state) | `"ok"` when a transcript was extracted, `"no_transcript"` when none could be — **branch on this first** |
| `errorCode` | string | **Yes (`null`)** | — (video state) | `null` when `status` is `"ok"`; otherwise the machine-readable reason (see the matrix below) |

**Note for consumers:** `message` is `null` (not `""`) when nothing needed explaining, and `start`/`dur` are strings for compatibility with the widely-used `searchResult` transcript format. Cast them before arithmetic, and test `message` for `None`/`null`.

**Two item shapes.** Every run writes exactly one item, but it comes in two shapes distinguished by `status`:

| `status` | `errorCode` | `segmentCount` | `searchResult` | `message` |
| :--- | :--- | :--- | :--- | :--- |
| `"ok"` | `null` | ≥ 1 | populated | `null`, or a language-fallback note |
| `"no_transcript"` | one of `MISSING_VIDEO_URL` · `BAD_VIDEO_URL` · `VIDEO_UNAVAILABLE` · `NO_CAPTIONS` · `CAPTIONS_BLOCKED` · `CAPTIONS_UNREACHABLE` · `EMPTY_TRANSCRIPT` · `UNEXPECTED_ERROR` | `0` | `[]` | always populated — the human-readable reason |

#### Example output item

```json
{
  "videoUrl": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "videoId": "dQw4w9WgXcQ",
  "requestedLanguage": "en",
  "language": "en",
  "isAutoGenerated": false,
  "isTranslated": false,
  "fallbackUsed": false,
  "message": null,
  "segmentCount": 3,
  "searchResult": [
    { "start": "0.000", "dur": "3.200", "text": "Welcome back to the channel." },
    { "start": "3.200", "dur": "2.900", "text": "Today we're talking about web scraping." },
    { "start": "6.100", "dur": "4.000", "text": "Let's jump right into the setup." }
  ],
  "status": "ok",
  "errorCode": null
}
```

#### Example "no transcript" item

An unavailable, private, deleted, or caption-less video produces a **successful** run with this shape instead:

```json
{
  "videoUrl": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "videoId": "dQw4w9WgXcQ",
  "requestedLanguage": "en",
  "language": "",
  "isAutoGenerated": false,
  "isTranslated": false,
  "fallbackUsed": false,
  "message": "This video is not available on YouTube (YouTube says: \"Video unavailable\"). It may be private, deleted, region-blocked, or the ID may be wrong, so there is no transcript to extract.",
  "segmentCount": 0,
  "searchResult": [],
  "status": "no_transcript",
  "errorCode": "VIDEO_UNAVAILABLE"
}
```

Fallback example — Hindi requested, only English captions exist, `translate: false`:

```json
{
  "requestedLanguage": "hi",
  "language": "en",
  "isTranslated": false,
  "fallbackUsed": true,
  "message": "The requested language 'hi' is not available and translation is turned off. Returning the available caption language 'en'. Available languages: en, es."
}
```

#### What a successful run looks like

```json
{
  "status": "SUCCEEDED",
  "defaultDatasetId": "<datasetId>",
  "stats": { "itemCount": 1 }
}
```

`itemCount: 1` is the **complete** result — but it does **not** by itself mean a transcript was extracted. Read the item's `status`:

- `status: "ok"` → transcript present; `segmentCount` gives its length.
- `status: "no_transcript"` → the run worked, the video didn't yield captions. Read `errorCode` and `message` for the reason, and the run's terminal status message shows `No transcript (<CODE>): <message>`.

**Nothing about the input or the video fails the run any more.** A missing or unparseable `videoUrl`, and every fact about the video's own state, is reported as a `SUCCEEDED` run carrying an `errorCode` and a plain-English `message`. A `FAILED` run now indicates a platform-level problem — an aborted run, a timeout, or an exhausted spend cap — not something your input said.

***

### ▶️ Invocation & Integration

**Lifecycle:** Discover → Understand → Configure → Execute → Retrieve → Validate. Authenticate every call with `Authorization: Bearer <APIFY_TOKEN>`.

#### Apify Console

Open the Actor → **Input** tab → paste the **YouTube video URL** → pick a **Preferred language** → leave **Translate** on → *(optional)* choose an **MCP connector**, tool, and template → **Start** → read the **Dataset** tab (JSON, CSV, Excel, XML).

#### Model Context Protocol (MCP)

Add this Actor directly as an AI tool in Claude Desktop, Cursor, VS Code, or LangChain agents:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com/?tools=techforce.global/youtube-transcript-scraper"
```

#### Python (apify-client)

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.getenv("APIFY_TOKEN"))

run = client.actor("techforce.global/youtube-transcript-scraper").call(run_input={
    "videoUrl": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "language": "en",
    "translate": True,
    "deliveryMode": "none",
})

item = client.dataset(run["defaultDatasetId"]).list_items().items[0]

## Always branch on status: a SUCCEEDED run can legitimately carry no transcript.
if item["status"] != "ok":
    raise SystemExit(f'No transcript ({item["errorCode"]}): {item["message"]}')

print(item["language"], item["segmentCount"], "segments")
if item["fallbackUsed"]:
    print("Language fallback:", item["message"])

full_text = " ".join(seg["text"] for seg in item["searchResult"])
print(full_text[:400])
```

Batch several videos by looping the Actor (one video per run):

```python
urls = ["https://youtu.be/aaaaaaaaaaa", "https://youtu.be/bbbbbbbbbbb"]
for url in urls:
    run = client.actor("techforce.global/youtube-transcript-scraper").call(
        run_input={"videoUrl": url, "language": "en", "deliveryMode": "none"}
    )
    print(url, run["status"])
```

#### JavaScript / TypeScript (apify-client)

```typescript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });

const run = await client.actor('techforce.global/youtube-transcript-scraper').call({
  videoUrl: 'https://youtu.be/dQw4w9WgXcQ',
  language: 'hi',
  translate: true,
  deliveryMode: 'none',
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
const [transcript] = items;

if (transcript.status !== 'ok') {
  // Unavailable video, no captions, bot wall — the run succeeded, the video didn't.
  throw new Error(`No transcript (${transcript.errorCode}): ${transcript.message}`);
}

console.log(`${transcript.language}: ${transcript.segmentCount} segments`);
console.log(transcript.searchResult.map((s) => s.text).join(' ').slice(0, 400));
```

#### cURL — synchronous (returns the item directly; 300s limit)

```bash
curl -X POST \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <YOUR_APIFY_TOKEN>' \
  -d '{"videoUrl":"https://www.youtube.com/watch?v=dQw4w9WgXcQ","language":"en","deliveryMode":"none"}' \
  'https://api.apify.com/v2/acts/techforce.global~youtube-transcript-scraper/run-sync-get-dataset-items'
```

#### cURL — asynchronous with a spend cap (recommended for production)

```bash
## 1. Start execution with a hard spend limit
curl -X POST -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <YOUR_APIFY_TOKEN>' \
  -d '{"videoUrl":"https://youtu.be/dQw4w9WgXcQ","language":"en","deliveryMode":"none"}' \
  'https://api.apify.com/v2/acts/techforce.global~youtube-transcript-scraper/runs?maxTotalChargeUsd=1'

## 2. Poll
curl -H 'Authorization: Bearer <YOUR_APIFY_TOKEN>' \
  'https://api.apify.com/v2/actor-runs/%3CrunId%3E'

## 3. Retrieve when SUCCEEDED
curl -H 'Authorization: Bearer <YOUR_APIFY_TOKEN>' \
  'https://api.apify.com/v2/datasets/%3CDATASET_ID%3E/items?clean=true&format=json'
```

***

### ⚠️ Autonomous Agent Error Handling & Resolution Matrix

**Read this first:** every problem with the *video* is reported as a `SUCCEEDED` run whose single dataset item carries `status: "no_transcript"` and an `errorCode`. Caller mistakes are reported identically: a missing `videoUrl` as `MISSING_VIDEO_URL`, an unusable one as `BAD_VIDEO_URL`. **No input error fails the run.** An agent that checks run status alone will mistake "this video has no captions" for success with data; **check `item["status"]`**. Connector delivery is skipped entirely when there is no transcript, so a `no_transcript` outcome never posts an empty message to Slack or Notion.

| Error Code | Detection Condition | Underlying Cause | Deterministic Agent Action |
| :--- | :--- | :--- | :--- |
| `AUTH_INVALID` | HTTP `401` | Missing or malformed `APIFY_TOKEN` | **Abort & Prompt User:** request a valid API token. |
| `RATE_LIMITED` | HTTP `429` | Apify API rate / concurrency limits | **Retry:** exponential backoff (2s, 4s, 8s). |
| `SYNC_TIMEOUT` | HTTP `408` on the sync endpoint | Run exceeded the 300s synchronous ceiling | **Reconfigure:** switch to async `POST /runs` + polling. |
| `MISSING_VIDEO_URL` | `SUCCEEDED`, item `errorCode: "MISSING_VIDEO_URL"`, `status: "no_transcript"` | `videoUrl` omitted or blank (no default exists) | **Modify Input:** pass `videoUrl` explicitly. The `message` field says what shapes are accepted. |
| `BAD_VIDEO_URL` | `SUCCEEDED`, item `errorCode: "BAD_VIDEO_URL"`, `status: "no_transcript"`; the offending value is echoed in `videoUrl` | Not a YouTube URL / no 11-char ID present | **Modify Input:** pass a clean watch URL, `youtu.be` link, or bare ID. |
| `WRONG_VIDEO` | `SUCCEEDED` but `videoId` ≠ intended video | Last-resort 11-char token match latched onto the wrong substring | **Validate & Re-run:** always compare returned `videoId` against your input. |
| `VIDEO_UNAVAILABLE` | `SUCCEEDED`, item `errorCode: "VIDEO_UNAVAILABLE"` | Video is private, deleted, region-blocked, or the ID is wrong — both fetch paths report it unavailable | **Do not retry.** Verify the video opens in a browser; ask the user for a valid URL. |
| `NO_CAPTIONS` | `SUCCEEDED`, item `errorCode: "NO_CAPTIONS"` | Video exists but has no caption tracks at all | **Do not retry.** Route to an ASR/Whisper service; this Actor cannot transcribe audio. |
| `CAPTIONS_BLOCKED` | `SUCCEEDED`, item `errorCode: "CAPTIONS_BLOCKED"` | YouTube challenged the request (bot wall, sign-in, age gate) | **Reconfigure & retry once:** enable the `RESIDENTIAL` proxy group. |
| `CAPTIONS_UNREACHABLE` | `SUCCEEDED`, item `errorCode: "CAPTIONS_UNREACHABLE"` | Neither resolution path returned usable caption metadata | **Retry once** with a residential proxy; if it repeats, treat as unavailable. |
| `EMPTY_TRANSCRIPT` | `SUCCEEDED`, item `errorCode: "EMPTY_TRANSCRIPT"` | Caption track resolved but parsed to zero usable text | **Retry:** re-run with `translate: true` or a different `language`. |
| `UNEXPECTED_ERROR` | `SUCCEEDED`, item `errorCode: "UNEXPECTED_ERROR"` | An unhandled upstream shape or network fault; the log carries the stack trace | **Retry once**, then report the run ID. |
| `PROXY_UNAVAILABLE` | Log `Could not set up proxy configuration` or `Running without a proxy` | Residential groups require a paid Apify plan | **Prompt User:** upgrade the plan; expect bot-wall failures until then. |
| `LANGUAGE_FALLBACK` | `SUCCEEDED` with `fallbackUsed: true` | Requested language unavailable | **Not an error.** Read `language` and `message`; re-run with `translate: true` if a translation is acceptable. |
| `TRANSLATION_FALLBACK` | `SUCCEEDED` with `isTranslated: false` after requesting a foreign language | Translation attempt returned no text | **Not an error.** Consume the returned language or translate downstream. |
| `DELIVERY_SKIPPED` | Log `no tool name was provided` | `mcpConnector` set but `mcpTool` empty | **Modify Input:** set `mcpTool`; the dataset item is already saved. |
| `DELIVERY_TOOL_UNKNOWN` | Log `Tool 'x' is not available on this connector` | Wrong tool name for that connector | **Modify Input:** pick a name from the `Available tools:` list in the same log line. |
| `DELIVERY_EMPTY` | Connector called, body empty | `mcpArguments` omitted the `{message}` placeholder | **Modify Input:** map `{message}` inside `mcpArguments`. |
| `MCP_PROXY_MISSING` | Log `APIFY_MCP_PROXY_URL is not set` | Running locally instead of on the platform | **Reconfigure:** `apify push` and run on the platform. |
| `CHARGE_LIMIT` | Run aborted immediately, before any work | `maxTotalChargeUsd` set below what the run costs to start | **Prompt User:** raise the cap. Check the Store page for the current rate. |

> **Provenance of this table:** every `errorCode`, `status` value and log-line condition above is
> taken from this Actor's own source and verified against it. The four HTTP rows (`401`, `408`,
> `429`) describe the Apify **API transport layer**, not this Actor — confirm them against the
> [Apify API reference](https://docs.apify.com/api/v2) before wiring them into automated retry logic.

***

### 🗣️ Natural Language → Actor Mapping

| User says | Intent | Constructed Actor input |
| --- | --- | --- |
| "Get me the transcript of this YouTube video" | Transcript extraction | `{"videoUrl":"<url>","language":"en"}` |
| "What is said in this video?" | Content Q\&A / retrieval context | `{"videoUrl":"<url>"}` then read `searchResult[].text` |
| "Summarize this YouTube video" | Summarization *input* | `{"videoUrl":"<url>"}` → summarize `searchResult` text yourself (the Actor returns raw captions) |
| "Give me the Hindi transcript of this talk" | Localized transcript | `{"videoUrl":"<url>","language":"hi","translate":true}` |
| "Transcript in Spanish, but only if it's a real Spanish caption" | Strict language | `{"videoUrl":"<url>","language":"es","translate":false}` then check `fallbackUsed` |
| "Pull the captions of this Short" | Shorts extraction | `{"videoUrl":"https://www.youtube.com/shorts/%3Cid%3E"}` |
| "I need timestamps so I can cut clips" | Subtitle / clipping workflow | `{"videoUrl":"<url>"}` then read `start` + `dur` per segment |
| "Turn this video into SRT subtitles" | Localization | `{"videoUrl":"<url>","language":"en"}` then format segments as SRT downstream |
| "Save this video's transcript to Notion" | Pipeline/delivery | `{"videoUrl":"<url>","mcpConnector":"notion","deliveryMode":"chunked","mcpTool":"notion-create-pages"}` |
| "Post the transcript to Slack #content" | Pipeline/delivery | `{"videoUrl":"<url>","mcpConnector":"slack","deliveryMode":"summary","mcpTool":"send_message","mcpArguments":{"channel":"#content","text":"{message}"}}` |
| "Open a Jira ticket with this transcript attached" | Pipeline/delivery | `{"videoUrl":"<url>","mcpConnector":"jira","deliveryMode":"summary","mcpTool":"create_issue"}` |
| "Get transcripts for these 12 videos" | Batch extraction | 12 sequential runs, one `videoUrl` each — then merge on `videoId` |
| "Get transcripts for every video on this channel" | Channel-wide extraction | ➡️ Route to [YouTube Full Channel Transcripts Scraper](https://apify.com/techforce.global/youtube-full-channel-transcripts-scraper) |
| "How many views and comments does this video have?" | Metadata | ➡️ Route to [YouTube Scraper](https://apify.com/techforce.global/youtube-scraper) |
| "List all the Shorts on this channel" | Shorts catalogue | ➡️ Route to [YouTube Shorts Scraper](https://apify.com/techforce.global/youtube-shorts-scraper) |
| "Transcribe this video — it has no captions" | ASR request | ❌ Out of scope — route to a speech-recognition service |

**Should NOT route here:** videos with no caption track (needs ASR) · playlists, channels, or batch lists in a single run (→ [Full Channel Transcripts](https://apify.com/techforce.global/youtube-full-channel-transcripts-scraper)) · comments, views, likes, or channel statistics (→ [YouTube Scraper](https://apify.com/techforce.global/youtube-scraper)) · a channel's Shorts or video-file downloads (→ [Shorts Scraper](https://apify.com/techforce.global/youtube-shorts-scraper)) · private, unlisted, or age-gated videos · LLM summarization or translation of the text · non-YouTube video platforms (Vimeo, TikTok, Twitch).

***

### 🧭 Agent Execution & Routing Logic

```text
[Input User Query]
       │
       ▼
1. Target is a YouTube video (youtube.com / youtu.be)?  NO  → Route to the correct platform Actor
       │ YES
       ▼
2. Wants transcript/caption text (not audio, stats, comments)?  NO  → Route elsewhere / abort
       │ YES
       ▼
3. Is the video public (not private/unlisted/age-gated)?        NO  → Reject
       │ YES
       ▼
4. Does the video have captions?                    UNKNOWN → Try; if errorCode == NO_CAPTIONS route to an ASR service
       │
       ▼
5. Can a video ID be extracted from the input?      NO  → Prompt user for a clean URL or 11-char ID
       │ YES
       ▼
6. Is the requested language in the 13-value enum?  NO  → Use "en" and translate downstream
       │ YES
       ▼
7. Is a non-requested language acceptable?          NO  → set translate:false and check fallbackUsed
       │ YES → keep translate:true (default)
       ▼
8. More than one video needed?                      YES → Whole channel? Route to Full Channel Transcripts Scraper
       │ NO                                                 A few videos? Loop: one run per video
       ▼
9. Deliver to a connector?                          YES → set mcpConnector + mcpTool + {message} in mcpArguments
       │ NO  → set deliveryMode "none"
       ▼
[Execute Apify Actor]
       │
       ├──► run "SUCCEEDED" ──► item.status == "ok"?
       │                          ├── YES → verify videoId, read language / isTranslated / fallbackUsed / message → consume searchResult
       │                          └── NO  → read item.errorCode → Error Handling Matrix (retry, re-route to ASR, or ask the user)
       └──► run "FAILED"    ──► platform-level only (abort, timeout, spend cap) → not an input problem
```

***

### 💰 Cost Control & Pricing Transparency

**Billing model: usage-based.** You pay for the Apify platform usage a run consumes. Current
rates are on the [Actor's Apify Store page](https://apify.com/techforce.global/youtube-transcript-scraper),
and the Console shows a live total while a run is in progress.

- **One run = one video, any length.** There is no per-segment or per-character charge, so a
  3-minute clip and a 3-hour lecture are the same shape of run.
- **Transcript retrieval is short and bounded.** The expensive variable is not video length — it
  is whether the request is challenged and has to fall back.
- **Autonomous spend caps:** pass `maxTotalChargeUsd` on the run endpoint for a hard per-execution
  ceiling. It is the only hard stop; input validation bounds the work, not the spend.

#### Worked examples — relative run shape

Multiply by your account's usage rate from the Store page to get a currency figure.

| Scenario | Runs | Relative cost per run | Notes |
| :--- | :--- | :--- | :--- |
| One video, captions present | 1 | **Baseline** | The normal case. |
| One video, 3-hour lecture | 1 | ≈ Baseline | Length barely matters — the caption track is one fetch. |
| One video, primary path challenged | 1 | Slightly above baseline | Fallback path adds one request. |
| One video, no captions at all | 1 | Below baseline | Ends early with `NO_CAPTIONS`; no parsing work. |
| Same video in 3 languages | 3 | 3 × baseline | Each language is a separate run. |
| 50 unrelated videos | 50 | 50 × baseline | Loop from your orchestrator. |
| A whole channel | 1 | — | ➡️ Use [Full Channel Transcripts Scraper](https://apify.com/techforce.global/youtube-full-channel-transcripts-scraper) instead of 200 runs here. |

**Cost-control tips:** validate the URL before spending a run; check the video's transcript panel
in a browser first if you are unsure captions exist; cache results by `videoId` + `language` so a
repeat request costs nothing; and remember each language is a separate run — so for multi-language
work, decide the set up front.

***

### 🛠️ Troubleshooting

Most failures are **caption availability or proxy issues**, not scraper bugs. Work through this table top to bottom — the first three rows account for the large majority of support tickets.

| # | Symptom you see | Most likely cause | Fix |
| :-- | :--- | :--- | :--- |
| 1 | `SUCCEEDED` but `errorCode: "VIDEO_UNAVAILABLE"` — *This video is not available on YouTube…* | The video is private, deleted, region-blocked, or the ID is wrong. Both fetch paths agreed | Open the URL in a browser. If it doesn't play for you either, no scraper can reach it — retrying and changing proxy will not help. |
| 2 | `SUCCEEDED` but `errorCode: "CAPTIONS_BLOCKED"` / `"CAPTIONS_UNREACHABLE"` | YouTube challenged the request, or returned no caption metadata on either path | Enable `RESIDENTIAL` Apify Proxy (the default) and retry once. Datacenter/local IPs are blocked routinely. |
| 3 | `SUCCEEDED` but `errorCode: "NO_CAPTIONS"` | The video genuinely has no caption track | Expected. This Actor reads captions; it does not transcribe audio. Use an ASR/Whisper service. |
| 3a | `SUCCEEDED` but `errorCode: "EMPTY_TRANSCRIPT"` | Caption track resolved but every segment parsed empty | Re-run with `translate: true`, or request a different `language`. |
| 3b | Your pipeline treats a `no_transcript` run as a transcript | You checked run status instead of `item["status"]` | Branch on `item["status"] == "ok"` before reading `searchResult`. |
| 4 | Wrong language in the output | Requested language had no native track; you got a translation or a fallback | Read `language`, `isTranslated`, `fallbackUsed`, `message`. Set `translate: false` if only a native track will do. |
| 5 | `translate: false` but the run still succeeded in another language | By design — it disables translation, it does not fail the run | Branch on `fallbackUsed: true`, not on run status. |
| 6 | `en-US` / `pt-BR` rejected or ignored | Only the 13 base enum codes are valid | Pass `en`, `pt`, … Region variants are not selectable. |
| 7 | Text reads like speech-to-text with no punctuation | The source track is YouTube's auto-generated ASR caption | Check `isAutoGenerated: true`. Nothing to fix — that is the only track available. |
| 8 | `videoId` is not the video you meant | A last-resort regex matched an 11-char token in a malformed input | Always pass a clean URL or bare ID, and assert the returned `videoId`. |
| 9 | Only one dataset item | By design — one run = one video = one item, transcript nested in `searchResult` | Read `segmentCount` for length; unwind `searchResult` for a per-segment table. |
| 10 | `start` / `dur` arithmetic fails | They are strings (`"3.200"`), not numbers | Cast to float before arithmetic. |
| 11 | `message` is `null` in your consumer | `null` means "nothing needed explaining" | Test for `None`/`null`, not for `""`. |
| 12 | Log: *Running without a proxy* | No proxy configured or unavailable on the plan | Enable Apify Proxy residential group; a paid plan is required. |
| 13 | Log shows the primary resolution path failing, but the run succeeded | Normal — the fallback path took over | No action needed. |
| 14 | Need many videos and each run is a separate charge | One video per run by design | For a whole channel use [Full Channel Transcripts Scraper](https://apify.com/techforce.global/youtube-full-channel-transcripts-scraper); otherwise loop and cache by `videoId` + `language`. |
| 15 | Slack / Notion / Jira received nothing | Delivery needs **both** `mcpConnector` and `mcpTool`; otherwise it is skipped with a warning | Set both. The dataset item is still saved — your data is never lost. |
| 16 | Log: *APIFY\_MCP\_PROXY\_URL is not set* | You ran locally with `apify run`; connectors only resolve on the platform | Deploy with `apify push` and run on the platform. |
| 17 | Connector fired, but the message body is empty | `mcpArguments` did not reference `{message}` | Map `{message}` in `mcpArguments` and populate `mcpMessageTemplate`. |
| 18 | Notion rejects or truncates a long transcript | Single-call payload exceeded block/timeout caps | Use `deliveryMode: "chunked"` and put `{part}`/`{partCount}` in the page title. |
| 19 | Every Notion page looks identical | `{part}`/`{partCount}` missing from the title template | Add them: `"Transcript {videoId} (part {part}/{partCount})"`. |
| 20 | Hundreds of connector calls fired | `deliveryMode: "perSegment"` sends one call per segment | Switch to `chunked` or `summary` unless per-line records are the goal. |
| 21 | Delivery fired when you only wanted a dataset | `deliveryMode` defaults to `summary`, **not** `none` | Set `deliveryMode: "none"` or leave `mcpConnector` empty. |
| 22 | `HTTP 401` / `403` | Missing, expired, or malformed `APIFY_TOKEN` | Regenerate in **Apify Console → Settings → API & Integrations**, send as `Authorization: Bearer <token>`. |
| 23 | `HTTP 429` | Apify account concurrency / rate limits — not YouTube blocking | Retry with exponential backoff (2s → 4s → 8s). |
| 24 | `HTTP 408` on `run-sync-get-dataset-items` | The synchronous endpoint has a hard **300-second** ceiling | Switch to async: `POST /runs` → poll `/actor-runs/<runId>` → fetch dataset. |

#### Diagnostic checklist before opening an issue

1. **Open the video in a browser** and check the transcript panel (⋯ → *Show transcript*). If YouTube shows nothing there, the video has no captions and no scraper can produce them.
2. **Confirm the proxy.** Residential group on, paid plan active. This is the single most common cause of caption-retrieval failures.
3. **Baseline run.** `videoUrl` only, `language: "en"`, `translate: true`, `deliveryMode: "none"`.
4. **Check the resolved `videoId`** in the output or the run log against the video you meant.
5. **Read the run log** — it names which resolution path succeeded and which caption language was selected.

If the issue survives all five steps, open an **Issues** ticket on the Actor page (or email support) with the **run ID**, the **video URL**, and what you expected. Run IDs let us replay the exact execution.

### ❓ FAQ

#### Setup & access

**Do I need a YouTube or Google account, API key, or OAuth app?**

No. The Actor reads publicly available caption tracks. You need an **Apify** account and API token; you need no Google credentials whatsoever.

**Is this the official YouTube API?**

No. This is an independent Actor and is not affiliated with, endorsed by, or sponsored by YouTube or Google.

**Why can't I just use the official YouTube Data API?**

`captions.download` only works for videos you own, via OAuth. For any third-party video it is not an option — which is the main reason this Actor exists.

**Do I need to configure proxies?**

The residential Apify Proxy is on by default and you should leave it on — YouTube serves datacenter IPs a bot-check wall. Residential groups require a paid Apify plan.

**Can I plug it into Claude, Cursor, or a LangChain agent?**

Yes — it is a native MCP tool:

`claude mcp add --transport http apify "https://mcp.apify.com/?tools=techforce.global/youtube-transcript-scraper"`

#### Input & languages

**Can I pass a playlist, channel, or list of videos?**

No — one video per run. For a whole channel, use [YouTube Full Channel Transcripts Scraper](https://apify.com/techforce.global/youtube-full-channel-transcripts-scraper), which takes a channel URL and processes up to 50 videos concurrently. For a few specific unrelated videos, loop this Actor from your orchestration layer (each video is a separate run, and a separate charge).

**Which languages are supported?**

13: `en`, `hi`, `gu`, `pt`, `es`, `fr`, `de`, `it`, `ja`, `ko`, `zh`, `ru`, `ar`. Region variants like `en-US` are not valid inputs.

**What happens if my language isn't available?**

With `translate: true` (default) you get a YouTube auto-translation. With `translate: false` you get the video's own caption language, `fallbackUsed: true`, and a `message` listing what was available. The run does **not** fail for language reasons.

**Does it work on Shorts?**

Yes — Shorts, embed, `/live/`, `youtu.be`, mobile and `youtube-nocookie` hosts, and bare 11-character IDs all resolve.

#### Output & data

**Why is the whole transcript in one dataset item?**

One run handles one video, so the transcript is a nested `searchResult` array on a single item. Use the Console's **Transcript** view (or `unwind=searchResult` on the API) for a per-segment table.

**Why are `start` and `dur` strings?**

For compatibility with the widely-used `searchResult` transcript format. Cast them to float before doing arithmetic.

**Does it summarize or translate with an LLM?**

No. It returns YouTube's caption text — raw, timestamped, and unmodified apart from HTML unescaping. Summarize downstream.

**Can it transcribe a video that has no captions?**

No. There is no speech recognition in this Actor. Use an ASR service for caption-less videos. The run itself still succeeds and tells you so, with `status: "no_transcript"` and `errorCode: "NO_CAPTIONS"`.

**Why did my run succeed but return no transcript?**

Because the video, not the Actor, was the problem: it may be private, deleted, region-blocked, caption-less, or YouTube may have challenged the request. Rather than crashing, the Actor writes one dataset item with `status: "no_transcript"`, a machine-readable `errorCode`, and a plain-English `message` — so scheduled runs and agent pipelines get a readable reason instead of an alert storm from failed runs. Read `item["status"]` first, always.

**Which outcomes still fail the run?**

None of them. A missing `videoUrl` (`MISSING_VIDEO_URL`), an unusable one (`BAD_VIDEO_URL`) and every fact about the video's own state all end as a **`SUCCEEDED`** run whose single item carries an `errorCode` and a validation `message` — so API and MCP callers get a readable reason instead of a stack trace. A `FAILED` run now means a platform-level problem: an abort, a timeout, or an exhausted spend cap.

#### Delivery

**What is the MCP connector for?**

It pushes the transcript straight into a tool you have already authorized on your Apify account — Notion, Slack, Jira, GitHub, Linear, Airtable, or any MCP-compatible connector — with no glue code. Credentials stay private: delivery runs through the **Apify MCP Proxy**, so the Actor never sees your connector tokens.

**Which delivery mode should I use?**

`summary` for short videos, `chunked` for long transcripts into services with request caps (Notion), `perSegment` for per-line records (logs, row-per-segment sheets), `none` for dataset-only runs.

**Does delivery work when running locally?**

No. Connectors resolve through the Apify MCP Proxy, which only exists on the platform. Locally you'll see `APIFY_MCP_PROXY_URL is not set` and the dataset item is saved as normal.

**Does it deliver anything when there is no transcript?**

No. A `no_transcript` outcome skips delivery entirely, so your Slack channel or Notion database never receives an empty transcript. The reason is in the dataset item and the run's status message.

#### Limits & pricing

**What does it cost?**

Usage-based: you pay for the platform usage a run consumes. One video per run, any length, with no
per-segment or per-character fee. Current rates are on the Actor's Apify Store page, and the
Console shows a live total while a run is in progress.

**How do I guarantee an agent never overspends?**

Pass `maxTotalChargeUsd` on the run endpoint — it is a hard per-run ceiling — and cache transcripts
by `videoId` + `language` so repeats cost nothing.

***

### 🔍 Companion machine-readable files

| File | Purpose |
| --- | --- |
| `.actor/actor.json` | Identity, version, storage and dockerfile wiring |
| `.actor/input_schema.json` | Authoritative typed input contract — `videoUrl`, `language` enum, `translate`, proxy editor, `deliveryMode` enum |
| `.actor/dataset_schema.json` | Output field metadata and both Console views (Overview + unwound Transcript) — consumed by the Apify MCP server to describe results to agents |
| `.actor/output_schema.json` | Declares where results are stored |
| `my_actor/main.py` | Video-ID extraction, caption resolution and parsing, language fallback, MCP delivery |
| `my_actor/connector.py` | MCP connector session handling and `{placeholder}` rendering |

If any table on this page disagrees with the schema files, **the schema files win**.

***

### 🔗 Related Actors

#### Other YouTube Actors

This Actor is the **single-video specialist**: one URL in, one timestamped transcript out. Use these siblings when the job is bigger or different.

| Actor | Best for | Why pick it over this one |
| :--- | :--- | :--- |
| [YouTube Full Channel Transcripts Scraper](https://apify.com/techforce.global/youtube-full-channel-transcripts-scraper) | **Transcripts for every video in a channel** — accepts `@handle`, `/channel/ID`, `/c/name`, `/user/name`, processes up to 50 videos concurrently, and returns video + channel metadata alongside the transcript | You want a whole channel (or its latest *N* videos) in one run instead of orchestrating one run per video. |
| [YouTube Scraper: Videos, Channels & Comments](https://apify.com/techforce.global/youtube-scraper) | Video, channel, and comment **metadata** from search queries, channel URLs, video URLs, or hashtags — up to 500 videos per input and 1,000 comments per video | You need views, likes, descriptions, channel stats, or comment threads — none of which this transcript Actor returns. |
| [YouTube Shorts Scraper](https://apify.com/techforce.global/youtube-shorts-scraper) | Every **Short** from a channel with engagement metrics (views, likes, comments, hashtags) and optional video downloads | You're working through a channel's Shorts catalogue, or you need the video files themselves. |

**Pick by intent:** one video's words → **this Actor** · a channel's words → **Full Channel Transcripts** · numbers, comments, and search discovery → **YouTube Scraper** · a channel's Shorts and their files → **Shorts Scraper**.

#### Enrichment & downstream pipeline

| Actor | Use it for |
| :--- | :--- |
| [Advanced Website Crawling Actor](https://apify.com/techforce.global/advanced-website-crawling-actor) | Crawl the creator's own site or show notes for clean HTML/Markdown/text context alongside the transcript |
| [Google Maps Scraper – Business Data](https://apify.com/techforce.global/google-maps-scraper) | Enrich businesses or venues mentioned in a transcript into full business records |
| [GOV.UK Business Scraper](https://apify.com/techforce.global/gov-uk-business-scraper) | Match UK company names heard in a video against Companies House records |

#### Suggested pipeline patterns

**✍️ Content repurposing**
`YouTube Transcript Scraper` → transcript text into your LLM of choice → blog draft, thread, newsletter → publish via a Notion/Slack connector.

**🌍 Localization**
One run per target language (`hi`, `gu`, `es`, …) → format `start`/`dur`/`text` as SRT downstream → upload as subtitle tracks.

**🔎 Video knowledge base**
[YouTube Full Channel Transcripts Scraper](https://apify.com/techforce.global/youtube-full-channel-transcripts-scraper) for the channel backfill → this Actor for each new upload → store by `videoId` + `language` → index `searchResult` text for semantic search over everything you've published.

**📊 Transcript + engagement analysis**
[YouTube Scraper](https://apify.com/techforce.global/youtube-scraper) for views, likes, and comments → this Actor for the words of the videos that over-performed → correlate topics against engagement.

**🤖 Agent retrieval tool**
Register the Actor as an MCP tool so an agent can pull a video's transcript on demand and answer questions with timestamps.

> Browse all Actors by Techforce Global at **[scraper.techforce.global](https://scraper.techforce.global/)**.

***

### 🔐 Compliance & Data Privacy

**Trademark & affiliation.** This is an independent Actor. It is **not** affiliated with, endorsed by, or sponsored by YouTube or Google. YouTube™ is a trademark of Google LLC; all trademarks are the property of their respective owners.

**Public data only — and no credentials, ever.** This Actor reads **publicly available** caption tracks. It does not log into any account, does not accept or store any Google/YouTube credential, and does not bypass authentication. Private, unlisted and age-gated videos are inaccessible to it by design, and a request for one returns `status: "no_transcript"` rather than a partial result.

**GDPR / CCPA — what you are taking on.** The Actor itself collects no personal data *about you* and requires no account linkage. But a transcript is **speech**, and speech is about people:

- **The speaker is identifiable.** A transcript of an interview, vlog or conference talk is that person's words. Under **GDPR / UK GDPR** and **CCPA/CPRA**, storing it can constitute processing personal data about them — and about any third party named aloud in the recording.
- **Special-category content cannot be filtered in advance.** Health, politics, religion and sexuality come up in spoken content, and you cannot know which transcripts contain it until after retrieval. Keep retention short and access controlled.
- **Auto-generated transcripts are not verbatim quotes.** When `isAutoGenerated: true`, a machine wrote the text; mis-transcribed names and inverted negations are common. Attributing it to a speaker as their exact words is a misattribution — and potentially a defamation — risk. **Check the flag before quoting.**
- **Minimise.** If your question is about *topics* rather than *people*, you need the text, not the identity of everyone in it. Aggregate and discard early.

**Copyright.** Transcripts are the creator's copyrighted expression. Internal research, search indexing and accessibility use sit on far safer ground than republishing transcripts as your own content, or using them as a substitute for watching the video. Using them to **train a model** is a legally distinct act from analysing them — take advice rather than relying on "it was public".

**Terms of Service.** YouTube's terms restrict automated access. This Actor does not remove that risk; running it means accepting it. The [official Data API](https://developers.google.com/youtube/v3) is the sanctioned route — but its `captions.download` endpoint only serves videos you own, which is the gap this Actor exists to fill.

***

### 🆘 Support & Custom Pipeline Engineering

Whole-channel coverage is already a product — see [YouTube Full Channel Transcripts Scraper](https://apify.com/techforce.global/youtube-full-channel-transcripts-scraper). Need something beyond it: scheduled multi-channel monitoring, playlist coverage, or a full content pipeline into your CMS?

- **Email**: bhavin.shah@techforceglobal.com
- **Custom Enterprise Integrations**: [Book a 15-Minute Technical Consultation](https://calendly.com/techforce-global/intro-meeting)
- **Maintained by**: [Techforce Global](https://www.techforceglobal.com) — Specialists in High-Performance Web Scrapers and Agentic Workflows.

***

Made with ❤️ by **[Techforce Global](https://www.techforceglobal.com)**
Specialists in High-Performance Video & Transcript Data Extraction and AI Automation.

***

### 🏷️ Structured data for search & AI discovery

Two blocks: `SoftwareApplication` describing the Actor, and `FAQPage` wrapping the FAQ above so those answers are eligible for rich results. The `FAQPage` entries are generated from the visible FAQ and match it question-for-question.

```json
{
  "@context": "https://schema.org/",
  "@type": "SoftwareApplication",
  "name": "YouTube Transcript Scraper - Captions to Notion, Slack & Jira",
  "alternateName": "YouTube Captions API Alternative",
  "applicationCategory": "DeveloperApplication",
  "operatingSystem": "Cloud (Apify platform)",
  "description": "The YouTube captions API alternative for videos you don't own: extracts the full timestamped transcript of any public YouTube video as structured JSON — every segment with start time, duration and text — in 13 languages with automatic translation fallback, without a Google API key, OAuth, or video ownership. Public caption tracks only: no account login, no stored credentials, and no access to private, unlisted or age-gated videos, with GDPR/CCPA and speaker-identifiability guidance included.",
  "url": "https://apify.com/techforce.global/youtube-transcript-scraper",
  "offers": {
    "@type": "Offer",
    "priceCurrency": "USD",
    "description": "Usage-based: billed for the Apify platform usage a run consumes. One video per run, any length, with no per-segment charge. Current rates are shown on the Actor's Apify Store page."
  }
}
```

```json
{
  "@context": "https://schema.org/",
  "@type": "FAQPage",
  "mainEntity": [
    {
      "@type": "Question",
      "name": "Do I need a YouTube or Google account, API key, or OAuth app?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "No. The Actor reads publicly available caption tracks. You need an Apify account and API token; you need no Google credentials whatsoever."
      }
    },
    {
      "@type": "Question",
      "name": "Is this the official YouTube API?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "No. This is an independent Actor and is not affiliated with, endorsed by, or sponsored by YouTube or Google."
      }
    },
    {
      "@type": "Question",
      "name": "Why can't I just use the official YouTube Data API?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "captions.download only works for videos you own, via OAuth. For any third-party video it is not an option — which is the main reason this Actor exists."
      }
    },
    {
      "@type": "Question",
      "name": "Do I need to configure proxies?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "The residential Apify Proxy is on by default and you should leave it on — YouTube serves datacenter IPs a bot-check wall. Residential groups require a paid Apify plan."
      }
    },
    {
      "@type": "Question",
      "name": "Can I plug it into Claude, Cursor, or a LangChain agent?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Yes — it is a native MCP tool: claude mcp add --transport http apify \"https://mcp.apify.com/?tools=techforce.global/youtube-transcript-scraper\""
      }
    },
    {
      "@type": "Question",
      "name": "Can I pass a playlist, channel, or list of videos?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "No — one video per run. For a whole channel, use YouTube Full Channel Transcripts Scraper, which takes a channel URL and processes up to 50 videos concurrently. For a few specific unrelated videos, loop this Actor from your orchestration layer (each video is a separate run, and a separate charge)."
      }
    },
    {
      "@type": "Question",
      "name": "Which languages are supported?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "13: en, hi, gu, pt, es, fr, de, it, ja, ko, zh, ru, ar. Region variants like en-US are not valid inputs."
      }
    },
    {
      "@type": "Question",
      "name": "What happens if my language isn't available?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "With translate: true (default) you get a YouTube auto-translation. With translate: false you get the video's own caption language, fallbackUsed: true, and a message listing what was available. The run does not fail for language reasons."
      }
    },
    {
      "@type": "Question",
      "name": "Does it work on Shorts?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Yes — Shorts, embed, /live/, youtu.be, mobile and youtube-nocookie hosts, and bare 11-character IDs all resolve."
      }
    },
    {
      "@type": "Question",
      "name": "Why is the whole transcript in one dataset item?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "One run handles one video, so the transcript is a nested searchResult array on a single item. Use the Console's Transcript view (or unwind=searchResult on the API) for a per-segment table."
      }
    },
    {
      "@type": "Question",
      "name": "Why are start and dur strings?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "For compatibility with the widely-used searchResult transcript format. Cast them to float before doing arithmetic."
      }
    },
    {
      "@type": "Question",
      "name": "Does it summarize or translate with an LLM?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "No. It returns YouTube's caption text — raw, timestamped, and unmodified apart from HTML unescaping. Summarize downstream."
      }
    },
    {
      "@type": "Question",
      "name": "Can it transcribe a video that has no captions?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "No. There is no speech recognition in this Actor. Use an ASR service for caption-less videos. The run itself still succeeds and tells you so, with status: \"no_transcript\" and errorCode: \"NO_CAPTIONS\"."
      }
    },
    {
      "@type": "Question",
      "name": "Why did my run succeed but return no transcript?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Because the video, not the Actor, was the problem: it may be private, deleted, region-blocked, caption-less, or YouTube may have challenged the request. Rather than crashing, the Actor writes one dataset item with status: \"no_transcript\", a machine-readable errorCode, and a plain-English message — so scheduled runs and agent pipelines get a readable reason instead of an alert storm from failed runs. Read item[\"status\"] first, always."
      }
    },
    {
      "@type": "Question",
      "name": "Which outcomes still fail the run?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "None of them. A missing videoUrl (MISSING_VIDEO_URL), an unusable one (BAD_VIDEO_URL) and every fact about the video's own state all end as a SUCCEEDED run whose single item carries an errorCode and a validation message — so API and MCP callers get a readable reason instead of a stack trace. A FAILED run now means a platform-level problem: an abort, a timeout, or an exhausted spend cap."
      }
    },
    {
      "@type": "Question",
      "name": "What is the MCP connector for?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "It pushes the transcript straight into a tool you have already authorized on your Apify account — Notion, Slack, Jira, GitHub, Linear, Airtable, or any MCP-compatible connector — with no glue code. Credentials stay private: delivery runs through the Apify MCP Proxy, so the Actor never sees your connector tokens."
      }
    },
    {
      "@type": "Question",
      "name": "Which delivery mode should I use?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "summary for short videos, chunked for long transcripts into services with request caps (Notion), perSegment for per-line records (logs, row-per-segment sheets), none for dataset-only runs."
      }
    },
    {
      "@type": "Question",
      "name": "Does delivery work when running locally?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "No. Connectors resolve through the Apify MCP Proxy, which only exists on the platform. Locally you'll see APIFY_MCP_PROXY_URL is not set and the dataset item is saved as normal."
      }
    },
    {
      "@type": "Question",
      "name": "Does it deliver anything when there is no transcript?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "No. A no_transcript outcome skips delivery entirely, so your Slack channel or Notion database never receives an empty transcript. The reason is in the dataset item and the run's status message."
      }
    },
    {
      "@type": "Question",
      "name": "What does it cost?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Usage-based: you pay for the platform usage a run consumes. One video per run, any length, with no per-segment or per-character fee. Current rates are on the Actor's Apify Store page, and the Console shows a live total while a run is in progress."
      }
    },
    {
      "@type": "Question",
      "name": "How do I guarantee an agent never overspends?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Pass maxTotalChargeUsd on the run endpoint — it is a hard per-run ceiling — and cache transcripts by videoId + language so repeats cost nothing."
      }
    }
  ]
}
```

# Actor input Schema

## `videoUrl` (type: `string`):

URL of the YouTube video to scrape the transcript from. Accepts standard watch URLs, youtu.be short links, Shorts, and embed URLs.

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

Preferred transcript language. If a native caption in this language exists it is used. If not, and 'Translate if needed' is on, YouTube auto-translates to this language.

## `translate` (type: `boolean`):

If the preferred language has no native caption track, auto-translate the transcript to it using YouTube's translation. If turned off, the run still succeeds: it returns the video's own caption language with 'fallbackUsed': true and a message listing the available languages.

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

Proxy settings. YouTube blocks datacenter/cloud IPs with a 'Sign in to confirm you're not a bot' wall, so RESIDENTIAL proxies are strongly recommended and enabled by default.

## `mcpConnector` (type: `string`):

Optionally deliver the transcript into a connector you have authorized — Notion, Slack, Linear, Airtable, Jira, GitHub, or any MCP-compatible connector. Leave empty to only save the result to the dataset.

## `deliveryMode` (type: `string`):

How to deliver to the connector: 'summary' (one call with the full transcript), 'chunked' (split a long transcript across a few calls/pages so services like Notion never time out), 'perSegment' (one call per transcript segment), or 'none' (save to dataset only).

## `mcpTool` (type: `string`):

Name of the tool to call on the connector (e.g. 'create\_page' for Notion, 'send\_message' for Slack, 'create\_issue' for Jira/GitHub). If unsure, run once with a connector selected — the log lists the connector's available tools.

## `mcpArguments` (type: `object`):

Arguments passed to the connector tool. String values support {placeholders}. In 'summary' mode: {videoUrl}, {videoId}, {language}, {segmentCount}, {transcript} (full text) and {message} (the rendered template below). In 'chunked' mode: same as summary but {transcript} holds one part of the transcript and {part}/{partCount} give the 1-based part number and total (put these in the page title, e.g. 'Transcript {videoId} (part {part}/{partCount})'). In 'perSegment' mode: {start}, {dur}, {text}, {videoUrl}, {videoId}, {language} and {message}. Example for Slack: {"channel": "#transcripts", "text": "{message}"}.

## `mcpMessageTemplate` (type: `string`):

Optional template rendered and exposed as the {message} placeholder in the tool arguments. Summary example: 'Transcript of {videoUrl} ({language}, {segmentCount} segments):\n\n{transcript}'. Per-segment example: '\[{start}] {text}'.

## Actor input object example

```json
{
  "videoUrl": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "language": "en",
  "translate": true,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "deliveryMode": "summary",
  "mcpTool": "",
  "mcpArguments": {},
  "mcpMessageTemplate": ""
}
```

# Actor output Schema

## `results` (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 = {
    "videoUrl": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "language": "en",
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("techforce.global/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 = {
    "videoUrl": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "language": "en",
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("techforce.global/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 '{
  "videoUrl": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "language": "en",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call techforce.global/youtube-transcript-scraper --silent --output-dataset

```

## MCP server setup

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