Video Transcript API — YouTube, TikTok, Instagram, X, Facebook
Pricing
from $3.00 / 1,000 transcript from captions
Video Transcript API — YouTube, TikTok, Instagram, X, Facebook
Any video URL to a timestamped transcript — including videos with no captions, which are transcribed with AI. Playlists, channels, and your own files too. JSON, text, SRT, VTT or LLM-ready output.
Pricing
from $3.00 / 1,000 transcript from captions
Rating
0.0
(0)
Developer
Mustafa
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
3 days ago
Last modified
Categories
Share
Universal Transcript API — YouTube, TikTok, Instagram, X, Facebook
Turn a video URL into a timestamped transcript. Works on videos that have no captions at all, by transcribing the audio.
Paste a video, a playlist, or a whole channel. Get JSON, plain text, SRT, WebVTT, or LLM-ready text. No YouTube API key, no account, no cookies.
What it does
| Platforms | YouTube (videos, Shorts, playlists, channels), TikTok, Instagram, X, Facebook, and your own files |
| Captions | Any language YouTube publishes, human-written preferred over auto-generated |
| No captions? | Falls back to speech recognition |
| Output | json, text, llm, srt, vtt — request any combination |
| Errors | Typed errorCode on every failed item, never a silent empty result |
| Billing | Per successful transcript. Failures are free. |
Your own audio and video
mediaUrls takes uploaded files or direct links to media — MP3, MP4, M4A, WAV, WEBM and the rest.
No platform is involved, so nothing here can break when a website changes, and it is the cheapest
path per minute of content because it needs no proxy at all. Useful for podcast archives, meeting
recordings, and lecture audio that never went near a video platform.
{"mediaUrls": ["https://cdn.example.com/podcast/ep12.mp3"],"outputFormats": ["llm", "srt"]}
Billed per audio minute, measured from the audio itself.
Why the AI fallback matters
Most transcript tools read the caption track and stop. If the uploader never enabled captions,
they return an error — and that is a large share of podcasts, lecture recordings, and almost every
TikTok. This Actor downloads the audio and transcribes it instead, returning the same structure
with source: "asr" so you always know where the text came from.
Speech recognition runs on hosted models rather than one bundled into the image, which is why it
does not need 4 GB of memory and does not add minutes of model-loading time to your run. When the
primary provider is at capacity the Actor moves to a second one automatically — the price is the
same either way, and metadata.asrProvider records which one produced each transcript.
Quick start
{"startUrls": ["https://www.youtube.com/watch?v=aircAruvnKk"],"outputFormats": ["json", "llm"]}
Playlists and channels expand automatically:
{"startUrls": ["https://www.youtube.com/playlist?list=PLZHQObOWTQDNU6R1_67000Dx_ZCJB-3pi","https://www.youtube.com/@3blue1brown","https://www.tiktok.com/@nasa/video/1234567890"],"maxVideosPerSource": 50,"languages": ["en", "es"],"outputFormats": ["llm", "srt"]}
A watch?v=…&list=… URL is treated as one video, not the whole playlist — pasting a video
should never turn into a several-hundred-video bill. Use a /playlist?list=… URL when you want
the list.
Output
Every item carries metadata plus whichever formats you requested.
{"url": "https://www.youtube.com/watch?v=aircAruvnKk","platform": "youtube","videoId": "aircAruvnKk","succeeded": true,"source": "manual_captions", // manual_captions | auto_captions | asr"language": "en", // ISO 639-1, whatever the source"title": "But what is a neural network?","durationSeconds": 1120,"segmentCount": 286,"characterCount": 18430,"availableLanguages": ["ar", "de", "en", "es", "..."],"transcriptJson": [{ "start": 4.22, "end": 5.4, "text": "This is a 3." }],"transcriptText": "This is a 3. It's sloppily written…","transcriptLlm": "This is a 3. It's sloppily written…","transcriptSrt": "1\n00:00:04,220 --> 00:00:05,400\nThis is a 3.\n","metadata": { "channel": "3Blue1Brown", "viewCount": 23876902, "captionRoute": "direct" },"error": null,"errorCode": null}
Output formats
| Format | Field | Use it for |
|---|---|---|
json | transcriptJson | Timestamped segments [{start, end, text}] |
text | transcriptText | The words, joined |
llm | transcriptLlm | RAG and summarisation — [Music], (laughter) and hesitation tokens removed |
srt | transcriptSrt | Subtitle files for players and editors |
vtt | transcriptVtt | WebVTT for HTML5 <video> |
llm is deliberately conservative: it strips bracketed non-speech annotations, ♪ lyric markers,
and standalone hesitations (um, uh, erm, hmm). It does not strip words like like or
so, which are real words far more often than they are filler — removing them would corrupt the
transcript rather than clean it.
Languages
languages picks a caption track, in priority order, e.g. ["tr", "en"]. A bare code matches
regional variants, so pt accepts pt-BR. With no preference the Actor returns the video's own
language — not whichever translation happens to sort first alphabetically, which is a common bug
in this category.
forceAsrLanguage is separate: it is handed straight to the speech recognition model for videos
with no captions and skips its auto-detection window. Set it when you know the channel's language — detection
is unreliable on short or music-heavy clips.
Ask the price first
Set estimateOnly: true and the Actor tells you what the job would cost without doing it and
without charging anything:
{ "startUrls": ["https://www.youtube.com/watch?v=..."], "estimateOnly": true }
{"isEstimate": true,"durationSeconds": 221,"predictedSource": "asr","estimateAccuracy": "exact","priceBreakdown": [{ "event": "transcript-asr-minute", "count": 4, "unitPriceUsd": 0.01, "subtotalUsd": 0.04 }],"estimatedPriceUsd": 0.04}
The run summary carries quotedTotalUsd for the whole batch. Re-run with estimateOnly: false
to process.
Quotes for platform videos are exact: price turns on whether a caption track exists and how long the audio is, and both are in the metadata the Actor fetches anyway. Quotes for uploaded files are marked approximate, because a file exposes no duration until it is decoded — those are inferred from size at a deliberately low assumed bitrate, so the real charge usually lands below the quote rather than above it.
Prices in a quote come from the Actor's live configured pricing, not from numbers baked into the code.
Videos you have already transcribed
Speech-recognition results are remembered in a key-value store in your own account, and reused when the same video comes round again. Nothing is shared between users — there is no central pool of other people's transcripts, and your storage only ever answers your own runs.
A repeat costs the same as the first time, and skips everything that made the first time slow:
| First run | Repeat | |
|---|---|---|
| Media downloaded | yes | no |
| Speech-recognition quota used | yes | no |
| Can fail on a download or a busy provider | yes | no |
| Typical wait | seconds to minutes | immediate |
That matters most on a schedule. Pointing this Actor at a channel every morning re-reads the same back catalogue every time; with the cache, only genuinely new videos are transcribed, and a spent daily quota no longer takes the whole run down with it.
Only speech recognition is cached. Caption tracks are not: they are one cheap request that always returns the platform's current text, and a stored copy would just go stale.
Rows served this way carry metadata.fromCache: true, and estimateOnly says so before you run.
Set cacheTranscripts: false to force a fresh transcription.
Error codes
Failed items appear in the dataset with succeeded: false and a stable errorCode. Filter on the
code rather than parsing the message. No failed item is ever charged.
Your input
| Code | Meaning | What to do |
|---|---|---|
INVALID_VIDEO_ID | The URL contains no recognisable video ID | Check the URL |
UNSUPPORTED_URL | Not a URL for the platform it was routed to | Check the URL |
UNSUPPORTED_PLATFORM | No adapter for that host | Use a supported platform |
PLATFORM_NOT_IMPLEMENTED | Recognised platform, not built yet | See the live platform list in the message |
LANGUAGE_UNAVAILABLE | Captions exist, but not in your languages | availableLanguages lists what exists |
SOURCE_NOT_FOUND | Playlist or channel rejected by the platform | Check the ID and that it is public |
MEDIA_NOT_AUDIO | The URL serves a web page, not media | Link the media file itself |
MEDIA_NOT_FOUND | The media URL returned 404 | Check the link |
MEDIA_FORBIDDEN | The media URL needs authentication | Use a public link, or upload the file |
MEDIA_TOO_LARGE | File above the upload limit | Re-encode at a lower bitrate — recognition downsamples to 16 kHz mono anyway |
PLAYLIST_EMPTY | No videos found in the list | Private, deleted, or region-locked |
CHANNEL_NOT_RESOLVED | The handle could not be resolved to a channel | Try the /channel/UC… form |
The video
| Code | Meaning | What to do |
|---|---|---|
NO_CAPTIONS | No caption track, and AI fallback is off | Turn on asrFallback |
EMPTY_TRANSCRIPT | Caption track exists but has no readable text | Use asrFallback |
EMPTY_CAPTION_BODY | The caption endpoint returned nothing | Usually transient; asrFallback covers it |
CAPTION_PARSE_FAILED | Caption payload was not in the expected format | Report it |
NO_AUDIO_STREAM / NO_VIDEO_STREAM | No downloadable media | Often age-restricted, or an HLS-only tweet |
NOT_A_VIDEO | The post is a photo or text | Nothing to transcribe, nothing charged |
POST_NOT_AVAILABLE | Deleted, private or age-restricted post | Check the link is public |
ASR_VIDEO_TOO_LONG | Longer than maxAsrDurationSeconds | Raise the limit |
ASR_REQUIRED | Direct media has no captions to read | Enable asrFallback |
AUDIO_TOO_LARGE | Media above the upload limit | Not transcribable in one pass |
Budget
| Code | Meaning | What to do |
|---|---|---|
BUDGET_EXHAUSTED | Your spending limit was reached mid-run | Raise maxTotalChargeUsd and re-run |
Remaining URLs stop immediately rather than doing unpaid work.
Platform or infrastructure — not your input, and not charged
| Code | Meaning | What to do |
|---|---|---|
TIKTOK_CHALLENGE | TikTok served a challenge page repeatedly | Rate limiting — retry later, lower maxConcurrency |
INSTAGRAM_BLOCKED | Instagram refused the request | Rate limiting or IP reputation — retry later |
TWITTER_BLOCKED | X refused the request | Rate limiting — retry later |
FACEBOOK_BLOCKED | Facebook rejected the request | Usually a non-public link |
FACEBOOK_LOGIN_REQUIRED | Facebook served a login wall | The video is not public |
VISITOR_DATA_UNAVAILABLE | Could not establish a session | Transient; retry |
AUDIO_FORBIDDEN | Media URL rejected mid-run | Transient; retry |
AUDIO_INCOMPLETE | Download truncated | Deliberately refused rather than returning a partial transcript |
AUDIO_DOWNLOAD_FAILED / VIDEO_DOWNLOAD_FAILED | Media download failed after retries | Retry later |
AUDIO_EMPTY / MEDIA_EMPTY | Media stream returned no bytes | Retry later |
MEDIA_FETCH_FAILED | The media URL could not be fetched | Check the link is reachable |
ASR_FAILED / ASR_EMPTY / ASR_RATE_LIMITED | Speech recognition failed or returned nothing | Retry later |
ASR_AUTH_FAILED | Transcription service rejected its key | Report it |
EXPANSION_HTTP_ERROR / EXPANSION_FAILED | Listing a playlist or channel failed | Retry later |
NETWORK_ERROR | Proxy or transport failure | Transient — retry |
ASR_QUOTA_EXHAUSTED | Speech recognition quota spent for now | Retry later; caption transcripts are unaffected |
UNEXPECTED_ERROR | Unhandled error on that item | Report it — other items continue |
One bad URL never aborts a run.
Input reference
| Option | Default | Notes |
|---|---|---|
startUrls | — | Videos, playlists, channels. Bare 11-character YouTube IDs accepted. |
maxVideosPerSource | 50 | Caps each playlist/channel. Direct video URLs are never capped. |
languages | auto | Caption languages in priority order |
outputFormats | ["json","text"] | Any of json, text, llm, srt, vtt |
maxConcurrency | 5 | Higher is faster, more likely to be rate-limited |
asrFallback | true | Transcribe audio when no caption track exists |
asrModel | whisper-large-v3-turbo | Primary-provider model. Ignored when the fallback provider runs. |
maxAsrDurationSeconds | 3600 | Skip AI on longer videos instead of running up a bill |
forceAsrLanguage | auto-detect | ISO 639-1 code handed to the speech recognition model |
proxyCountryCode | — | Two-letter country for the residential proxy |
captionEgress | auto | Caption download route; auto uses the cheaper one and falls back |
reuseSession | true | Reuse the platform session between runs — cheaper on single-video calls |
Notes on cost
- Batch when you can. Session setup is a fixed cost per run, so 50 videos in one run is roughly three times cheaper per video than 50 single-video runs.
- Playlist and channel expansion has its own cost, fixed per source. Taking 4 videos from a channel costs far more per video than taking 50.
- TikTok is more expensive than YouTube — it has no audio-only stream, so a 10-second clip means downloading about 1.3 MB where a 10-minute YouTube video needs 3.6 MB.
- AI transcription is billed per audio minute and only runs when a video has no captions.
Limitations
- Public videos only. Nothing that needs a login, age verification, or a purchase.
- No translation — you get the language that was spoken or captioned.
- Live streams have no static transcript.
- The reported language on
asrrows can be wrong even when the text is right. SetforceAsrLanguagewhen it matters. - TikTok rate-limits aggressively; the Actor retries, but large batches will see
TIKTOK_CHALLENGEon some items.
Legal
This Actor reads publicly available captions and metadata — the same data any viewer sees via the player's transcript button. It does not bypass authentication or age gates, does not use an account, and collects no personal data. You are responsible for complying with each platform's terms and the law in your jurisdiction.