YouTube Transcript API – Captions & AI Transcription
Pricing
from $2.00 / 1,000 results
YouTube Transcript API – Captions & AI Transcription
Extract YouTube transcripts from videos and Shorts using existing captions first, with AI transcription fallback when needed. Supports batch processing, timestamps, SRT, WebVTT, language selection, and video metadata.
Pricing
from $2.00 / 1,000 results
Rating
0.0
(0)
Developer
LanceAPI
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
3 days ago
Last modified
Categories
Share
YouTube Transcript Scraper API – Captions & AI Transcription
Get structured YouTube video transcripts in bulk. The Actor checks existing YouTube captions first, then uses optional AI transcription only when no usable caption is available.
Paste URLs → run the Actor → get normalized transcripts, timestamps, and billing details.
- Caption-first: manual captions are preferred over auto captions within each language.
- AI only when needed: a usable YouTube caption uses 0 AI transcription minutes.
- Batch ready: process 1–500 supported YouTube URLs; duplicate video IDs are processed once.
- Select text, timestamped segments, SRT, or WebVTT.
- Every item reports AI usage and billed AI minutes.
- The normal successful-result fee still applies to a caption result; AI-minute fees apply only after media is submitted to speech recognition.
✨ Key features
- YouTube Transcript API for watch URLs, short links, mobile URLs, and YouTube Shorts.
- Manual-caption → auto-caption → AI-transcription fallback.
- Preferred language ordering and YouTube caption translation when supported.
- Structured metadata, transcript formats, error codes, and billing fields.
- Optional temporary signed media URLs.
- One normalized Dataset item per unique input, including failures for easy batch retry.
🚀 Quick start
{ "urls": ["https://www.youtube.com/watch?v=AbCdEfGhI12"] }
{"inputUrl": "https://www.youtube.com/watch?v=AbCdEfGhI12","normalizedUrl": "https://www.youtube.com/watch?v=AbCdEfGhI12","videoId": "AbCdEfGhI12","success": true,"status": "success","video": { "title": "Example video", "duration": 213 },"transcript": {"source": "manual_caption","language": { "code": "en", "name": "English" },"isGenerated": false,"text": "Example transcript text.","segments": [{ "start": 0, "end": 4.22, "duration": 4.22, "text": "Example text." }],"srt": null,"vtt": null},"videoTitle": "Example video","transcriptSource": "manual_caption","transcriptLanguage": "en","transcriptPreview": "Example transcript text.","media": null,"billing": { "aiTranscriptionUsed": false, "aiMinutesCharged": 0 },"error": null,"processedAt": "2026-08-21T12:00:00.000Z"}
Supported YouTube URLs
https://www.youtube.com/watch?v=VIDEO_IDhttps://youtu.be/VIDEO_IDhttps://www.youtube.com/shorts/VIDEO_IDhttps://m.youtube.com/watch?v=VIDEO_ID- The same URL types with tracking or timestamp parameters, for example
?si=...and&t=30
URLs are normalized to a watch URL. Different URLs for the same video are deduplicated within a run.
How transcript selection works
- Normalize and deduplicate submitted URLs.
- Discover eligible caption tracks.
- Apply
preferredLanguagesin the submitted order. - Within a language, try manual captions before auto-generated captions.
- Skip empty or broken tracks and continue to the next eligible track.
- Use AI only when no usable caption remains and AI fallback is enabled.
The same transcript structure is returned for manual_caption, auto_caption, and ai_transcription.
Input
| Field | Type | Required | Default | Limits | Description |
|---|---|---|---|---|---|
urls | string[] | Yes | — | 1–500 | Supported YouTube URLs. Duplicate video IDs are processed once. |
formats | string[] | No | ['text', 'segments'] | One or more of text, segments, srt, vtt | Choose only what your workflow needs. Fewer formats keep JSON smaller and easier for downstream tools: use text for reading/AI, segments for timestamps, SRT/WebVTT for subtitle files. |
preferredLanguages | string[] | No | [] | Non-empty language-code strings; order is priority | A caption-language priority, not a translation setting. For example ['es', 'en'] means try Spanish, then English; manual captions win over auto captions. If none of these languages has a usable caption, the Actor moves to AI fallback when enabled. Leave empty to accept the best available caption in any language. |
proxyConfiguration | object | No | Apify Proxy enabled | Apify Proxy settings | Keep enabled for YouTube. If datacenter exits are blocked, try RESIDENTIAL; it can add proxy charges. |
enableAiTranscription | boolean | No | true | — | Choose transcript coverage versus AI budget. When captions are unavailable, AI can transcribe audio; set false for caption-only processing and zero AI-minute charges. |
includeMetadata | boolean | No | true | — | Include title, description, channel, duration, views, thumbnail, and publishing data. Turn it off for transcript-only output to keep JSON smaller and simpler. |
includeMediaUrls | boolean | No | false | — | Include temporary signed audio and video URLs only for an immediate media task. Keep it off for smaller, cleaner JSON; URLs can expire. |
translate | boolean | No | false | — | Request translation for a selected YouTube caption when supported. |
targetLanguage | string | No | 'en' | Non-empty language code | Translation target when translate is true and YouTube supports it. It does not translate AI transcripts. |
maxAiMinutes | integer | No | 100 | Minimum 0; 0 = unlimited | Run-wide AI spending guard. Stop starting AI jobs when their billed minutes would exceed this budget. |
skipAiIfLongerThanMinutes | integer | No | 60 | Minimum 0; 0 = disabled | Per-video AI spending guard. Skip a captionless video above this length so one long podcast or stream cannot consume an unexpected share of budget. |
Budget and output controls
Use the controls intentionally:
- Want the widest transcript coverage? Keep
enableAiTranscriptionon, then setmaxAiMinutesto the maximum AI spend you are comfortable with for the whole run. - Want to protect against a single long video? Keep
skipAiIfLongerThanMinutesat a sensible threshold. A long captionless podcast will then be returned as a clear non-success item instead of silently consuming your AI budget. - Want caption-only, predictable processing? Set
enableAiTranscriptiontofalse; successful caption results still have the normal result fee, but AI minutes remain zero. - Want lean JSON for an LLM or database? Request only the needed
formats, and turn offincludeMetadataandincludeMediaUrlswhen those fields are unnecessary.
Input example
{"urls": ["https://youtu.be/AbCdEfGhI12","https://www.youtube.com/shorts/XyZaBcDeF34?si=example","https://m.youtube.com/watch?v=QrStUvWxY56&t=30"],"preferredLanguages": ["es", "en"],"formats": ["text", "srt"],"maxAiMinutes": 30}
Transcript formats
text— plain text for LLM, search, RAG, and NLP workflows.segments— second-based timestamps for navigation and citations.srt— subtitle output for editors and caption import.vtt— WebVTT for browser-video workflows.
Translation
Translation applies only to a selected YouTube caption track. When translate: true and the track supports YouTube translation, the Actor requests targetLanguage and reports it in transcript.language.
AI fallback transcripts are not translated. If a selected caption does not support translation, its available original caption text is returned. Translation has no separate charge: it uses the normal successful-result event and zero AI minutes unless AI fallback is needed.
Short output example
The Dataset Overview uses these top-level display fields:
{"success": true,"videoTitle": "Example video","transcriptSource": "ai_transcription","transcriptLanguage": "en","transcriptPreview": "A short preview of the returned transcript...","billing": { "aiTranscriptionUsed": true, "aiMinutesCharged": 4 }}
transcriptPreview is limited to 500 characters. Use transcript.text for the complete text.
Output
Every unique input creates one Dataset item. A failed item remains a normal JSON result, so other videos in a batch can still finish.
Complete output field reference
Core processing
| Field | Type | Nullable | Format / unit | Meaning |
|---|---|---|---|---|
inputUrl | string | Yes | URL | Submitted URL. |
normalizedUrl | string | Yes | URL | Canonical watch URL when available. |
videoId | string | Yes | YouTube ID | Normalized video identifier. |
success | boolean | No | — | Whether a transcript was returned. |
status | string | No | success, no_transcript, failed | High-level outcome. |
processedAt | string | No | ISO 8601 UTC | Time the item was finalized. |
Dataset display fields
| Field | Type | Nullable | Meaning |
|---|---|---|---|
videoTitle | string | Yes | Top-level title for the Dataset Overview table. |
transcriptSource | string | Yes | Top-level transcript source for the table. |
transcriptLanguage | string | Yes | Top-level language code for the table. |
transcriptPreview | string | Yes | First 500 characters of transcript text for the table. |
Video metadata
video is null when includeMetadata is false or metadata is unavailable.
| Field | Type | Nullable | Format / unit | Meaning |
|---|---|---|---|---|
video.title, video.description | string | Yes | — | Video title and description. |
video.duration | number | Yes | seconds | Video duration. |
video.publishedAt | string | Yes | ISO 8601 UTC | Published/uploaded time when available. |
video.thumbnailUrl | string | Yes | URL | Thumbnail URL. |
video.viewCount, video.likeCount | number | Yes | count | Available engagement values; likeCount is currently unavailable from this path. |
video.channel.id, video.channel.name, video.channel.avatarUrl | string | Yes | ID / text / URL | Channel metadata. |
Transcript
transcript is null on failure.
| Field | Type | Nullable | Format / unit | Meaning |
|---|---|---|---|---|
transcript.source | string | Yes | manual_caption, auto_caption, ai_transcription | Transcript origin. |
transcript.language.code, transcript.language.name | string | Yes | language code / name | Returned transcript language. |
transcript.isGenerated | boolean | Yes | — | true for auto, translated, and AI transcripts. |
transcript.availableLanguages | array | Yes | — | Caption tracks discovered from YouTube. |
transcript.text | string | Yes | — | Full text when text is requested. |
transcript.segments | array | Yes | — | Segments when segments is requested. |
transcript.segments[].start, end, duration | number | No | seconds | Segment timing. |
transcript.segments[].text | string | No | — | Segment text. |
transcript.srt, transcript.vtt | string | Yes | SRT / WebVTT | Output when requested. |
Media, billing, and error
| Field | Type | Nullable | Format / unit | Meaning |
|---|---|---|---|---|
media | object | Yes | — | null unless includeMediaUrls is true. |
media.audioUrl, media.videoUrl | string | Yes | temporary signed URL | May expire; do not treat as permanent links. |
billing.aiTranscriptionUsed | boolean | No | — | Whether media was submitted to AI transcription. |
billing.aiMinutesCharged | integer | No | started minutes | 0 for caption-only results and failures before AI submission. |
error | object | Yes | — | null on success. |
error.code, error.message | string | Yes | code / text | Machine-readable code and user-readable explanation. |
Status values
| Status | Meaning | Transcript | result fee | AI-minute fee |
|---|---|---|---|---|
success | Caption or AI transcript returned. | Present | Yes | Only when AI media was submitted. |
no_transcript | No usable caption returned and AI was not used. | null | No | No |
failed | Invalid input, unavailable video, blocked request, or processing failure. | null | No | Only possible after AI media submission. |
Error codes
| Code | Meaning |
|---|---|
INVALID_URL, UNSUPPORTED_URL | URL is malformed, unsupported, or has no valid YouTube video ID. |
VIDEO_NOT_FOUND, VIDEO_UNAVAILABLE, PRIVATE_VIDEO, AGE_RESTRICTED | Video cannot be processed through the current extraction path. |
LIVE_VIDEO | Currently live stream is unsupported. |
YOUTUBE_RATE_LIMITED, YOUTUBE_BLOCKED, NETWORK_ERROR | YouTube or the network rejected the request. |
NO_TRANSCRIPT | No usable caption was available and AI fallback was disabled. |
MEDIA_NOT_AVAILABLE | No usable stream was available for AI fallback. |
AI_LIMIT_EXCEEDED, AI_VIDEO_TOO_LONG | An AI cost guard prevented submission. |
AI_TRANSCRIPTION_FAILED | AI could not be configured, reached, process the media, or detect speech. |
INTERNAL_ERROR | Unexpected processing error. |
Complete output examples
Caption success
{"success":true,"status":"success","transcript":{"source":"manual_caption","text":"Example caption text."},"billing":{"aiTranscriptionUsed":false,"aiMinutesCharged":0},"error":null}
AI transcription success
{"success":true,"status":"success","transcript":{"source":"ai_transcription","text":"Example AI transcript."},"billing":{"aiTranscriptionUsed":true,"aiMinutesCharged":2},"error":null}
No transcript
{"success":false,"status":"no_transcript","transcript":null,"billing":{"aiTranscriptionUsed":false,"aiMinutesCharged":0},"error":{"code":"NO_TRANSCRIPT","message":"No usable YouTube captions were available and AI transcription was disabled."}}
AI processed but no speech
{"success":false,"status":"failed","transcript":null,"billing":{"aiTranscriptionUsed":true,"aiMinutesCharged":2},"error":{"code":"AI_TRANSCRIPTION_FAILED","message":"AI transcription completed but no speech was detected."}}
API usage
Actor slug: lance_api/youtube-transcripts-scraper.
JavaScript
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: process.env.APIFY_TOKEN });const run = await client.actor('lance_api/youtube-transcripts-scraper').call({urls: ['https://www.youtube.com/watch?v=AbCdEfGhI12'], formats: ['text', 'segments'],});const { items } = await client.dataset(run.defaultDatasetId).listItems();console.log(items);
Python
import osfrom apify_client import ApifyClientclient = ApifyClient(token=os.environ['APIFY_TOKEN'])run = client.actor('lance_api/youtube-transcripts-scraper').call(run_input={'urls': ['https://www.youtube.com/watch?v=AbCdEfGhI12'], 'formats': ['text', 'segments'],})items = client.dataset(run['defaultDatasetId']).list_items().itemsprint(items)
cURL
RUN_JSON=$(curl -sS -X POST \'https://api.apify.com/v2/actors/lance_api~youtube-transcripts-scraper/runs?waitForFinish=300' \-H "Authorization: Bearer $APIFY_TOKEN" -H 'Content-Type: application/json' \-d '{"urls":["https://www.youtube.com/watch?v=AbCdEfGhI12"],"formats":["text","segments"]}')DATASET_ID=$(printf '%s' "$RUN_JSON" | jq -r '.data.defaultDatasetId')curl -sS "https://api.apify.com/v2/datasets/$DATASET_ID/items?clean=true" \-H "Authorization: Bearer $APIFY_TOKEN"
💰 Clear, predictable pricing
Successful transcript processing starts from $1.00 per 1,000 results, and AI transcription starts from $4.00 per 1,000 started minutes with eligible Apify Store discounts.
Standard pricing:
- $0.002 per successful transcript result — $2.00 / 1,000 results.
- $0.006 per started AI transcription minute — $6.00 / 1,000 AI minutes.
A video with a usable YouTube caption uses 0 AI transcription minutes, regardless of length. The normal successful-result fee still applies.
AI minutes are ceil(durationSeconds / 60): a 61-second captionless video uses 2 started AI minutes. AI-minute fees begin only after media is submitted to speech recognition. If AI processes media but finds no speech, the AI-minute fee still applies; a failure before submission does not create one.
| Scenario | result fee | ai-minute fee |
|---|---|---|
| Usable YouTube caption found | Yes | No |
| Caption unavailable → AI succeeds | Yes | Yes |
| AI processes media but finds no speech | No | Yes |
| Invalid, private, unavailable, or blocked before AI | No | No |
Duplicate URLs resolving to the same video ID are processed once, so they create at most one Dataset item and one successful-result event in that run. Use enableAiTranscription, maxAiMinutes, and skipAiIfLongerThanMinutes to control AI usage. Check the Actor's Pricing tab for your current plan price.
Use cases
AI and LLM workflows
Use plain text or timestamped segments as context for downstream question-answering, extraction, classification, or comparison.
RAG and knowledge bases
Chunk returned segments, create embeddings, and load text with timestamps into a knowledge base or vector database.
Competitor and SEO research
Process public competitor videos in bulk, then use returned transcripts for topic, keyword, and content-gap analysis.
YouTube Shorts transcript analysis
Submit Shorts URLs directly and receive the same normalized transcript structure.
Subtitle workflows
Request SRT or WebVTT for editing, accessibility, or web-video workflows.
Interviews, lectures, and podcasts
Turn eligible public video speech into searchable text and time-based segments for research or notes.
Content repurposing and automation
Use the returned transcript as input to a separate blog, newsletter, social-writing, or automation workflow.
How it works
- Normalize and deduplicate URLs.
- Fetch video information and eligible caption tracks.
- Select captions by language priority and manual-over-auto rules.
- Use AI fallback only when captions are unavailable and AI is enabled.
- Generate requested transcript formats.
- Save one normalized result to the default Dataset.
🤖 Use with AI agents
This Actor can be called through the Apify MCP Server from MCP-compatible clients, including ChatGPT, Claude, Cursor, and Codex. MCP calls use the same Actor pricing as normal runs.
Use the Apify Actor
lance_api/youtube-transcripts-scraperto retrieve transcripts from these YouTube URLs. Prefer existing YouTube captions when available and use AI fallback only when captions are unavailable. Return transcript source, language, text, timestamped segments, and AI minutes charged.
FAQ
Why does a caption result show zero AI minutes?
The Actor found a usable YouTube caption, so it did not submit media to AI. The successful-result fee still applies.
Why can a failed item have AI minutes charged?
If media was already submitted for speech recognition, AI work occurred. This includes a no-speech result or a downstream AI failure.
Why did I receive YOUTUBE_BLOCKED?
YouTube can block an IP as automated traffic. Keep proxying enabled and, if necessary, select Apify's RESIDENTIAL proxy group. Residential traffic can create additional proxy charges.
Are media URLs permanent?
No. media.audioUrl and media.videoUrl are temporary signed YouTube delivery URLs and can expire.
Limitations
- Currently live streams return
LIVE_VIDEO; ended streams can be processed when YouTube exposes them as VODs. - Private, unavailable, and age-restricted videos can return explicit errors.
- YouTube can change response formats or block individual network exits, so some valid URLs can fail.
- Caption translation depends on YouTube and the selected track supporting it.
- AI fallback requires a usable media stream and respects configured AI-minute limits.
Privacy and logging
The Actor's result object exposes transcript text and temporary media URLs only in your Dataset. Do not share signed media URLs unnecessarily, and do not place API tokens, cookies, proxy credentials, or passwords in Actor input.
Public data and responsible use
Process only content you have the right to access and use. Follow YouTube and Apify terms, respect copyright and privacy, and do not use this Actor for harassment, spam, unlawful profiling, non-public data collection, or attempts to bypass access controls.
Support
For help, open an issue on the Actor page or email lanceapi2026@hotmail.com.
Please include the Run ID, a non-sensitive YouTube URL, status, error.code, and expected versus actual result. Do not send an Apify token, cookies, proxy credentials, API keys, or passwords.