Video Transcript API — YouTube, TikTok, Instagram, X, Facebook avatar

Video Transcript API — YouTube, TikTok, Instagram, X, Facebook

Pricing

from $3.00 / 1,000 transcript from captions

Go to Apify Store
Video Transcript API — YouTube, TikTok, Instagram, X, Facebook

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

Mustafa

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

3 days ago

Last modified

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

PlatformsYouTube (videos, Shorts, playlists, channels), TikTok, Instagram, X, Facebook, and your own files
CaptionsAny language YouTube publishes, human-written preferred over auto-generated
No captions?Falls back to speech recognition
Outputjson, text, llm, srt, vtt — request any combination
ErrorsTyped errorCode on every failed item, never a silent empty result
BillingPer 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

FormatFieldUse it for
jsontranscriptJsonTimestamped segments [{start, end, text}]
texttranscriptTextThe words, joined
llmtranscriptLlmRAG and summarisation — [Music], (laughter) and hesitation tokens removed
srttranscriptSrtSubtitle files for players and editors
vtttranscriptVttWebVTT 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 runRepeat
Media downloadedyesno
Speech-recognition quota usedyesno
Can fail on a download or a busy provideryesno
Typical waitseconds to minutesimmediate

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

CodeMeaningWhat to do
INVALID_VIDEO_IDThe URL contains no recognisable video IDCheck the URL
UNSUPPORTED_URLNot a URL for the platform it was routed toCheck the URL
UNSUPPORTED_PLATFORMNo adapter for that hostUse a supported platform
PLATFORM_NOT_IMPLEMENTEDRecognised platform, not built yetSee the live platform list in the message
LANGUAGE_UNAVAILABLECaptions exist, but not in your languagesavailableLanguages lists what exists
SOURCE_NOT_FOUNDPlaylist or channel rejected by the platformCheck the ID and that it is public
MEDIA_NOT_AUDIOThe URL serves a web page, not mediaLink the media file itself
MEDIA_NOT_FOUNDThe media URL returned 404Check the link
MEDIA_FORBIDDENThe media URL needs authenticationUse a public link, or upload the file
MEDIA_TOO_LARGEFile above the upload limitRe-encode at a lower bitrate — recognition downsamples to 16 kHz mono anyway
PLAYLIST_EMPTYNo videos found in the listPrivate, deleted, or region-locked
CHANNEL_NOT_RESOLVEDThe handle could not be resolved to a channelTry the /channel/UC… form

The video

CodeMeaningWhat to do
NO_CAPTIONSNo caption track, and AI fallback is offTurn on asrFallback
EMPTY_TRANSCRIPTCaption track exists but has no readable textUse asrFallback
EMPTY_CAPTION_BODYThe caption endpoint returned nothingUsually transient; asrFallback covers it
CAPTION_PARSE_FAILEDCaption payload was not in the expected formatReport it
NO_AUDIO_STREAM / NO_VIDEO_STREAMNo downloadable mediaOften age-restricted, or an HLS-only tweet
NOT_A_VIDEOThe post is a photo or textNothing to transcribe, nothing charged
POST_NOT_AVAILABLEDeleted, private or age-restricted postCheck the link is public
ASR_VIDEO_TOO_LONGLonger than maxAsrDurationSecondsRaise the limit
ASR_REQUIREDDirect media has no captions to readEnable asrFallback
AUDIO_TOO_LARGEMedia above the upload limitNot transcribable in one pass

Budget

CodeMeaningWhat to do
BUDGET_EXHAUSTEDYour spending limit was reached mid-runRaise maxTotalChargeUsd and re-run

Remaining URLs stop immediately rather than doing unpaid work.

Platform or infrastructure — not your input, and not charged

CodeMeaningWhat to do
TIKTOK_CHALLENGETikTok served a challenge page repeatedlyRate limiting — retry later, lower maxConcurrency
INSTAGRAM_BLOCKEDInstagram refused the requestRate limiting or IP reputation — retry later
TWITTER_BLOCKEDX refused the requestRate limiting — retry later
FACEBOOK_BLOCKEDFacebook rejected the requestUsually a non-public link
FACEBOOK_LOGIN_REQUIREDFacebook served a login wallThe video is not public
VISITOR_DATA_UNAVAILABLECould not establish a sessionTransient; retry
AUDIO_FORBIDDENMedia URL rejected mid-runTransient; retry
AUDIO_INCOMPLETEDownload truncatedDeliberately refused rather than returning a partial transcript
AUDIO_DOWNLOAD_FAILED / VIDEO_DOWNLOAD_FAILEDMedia download failed after retriesRetry later
AUDIO_EMPTY / MEDIA_EMPTYMedia stream returned no bytesRetry later
MEDIA_FETCH_FAILEDThe media URL could not be fetchedCheck the link is reachable
ASR_FAILED / ASR_EMPTY / ASR_RATE_LIMITEDSpeech recognition failed or returned nothingRetry later
ASR_AUTH_FAILEDTranscription service rejected its keyReport it
EXPANSION_HTTP_ERROR / EXPANSION_FAILEDListing a playlist or channel failedRetry later
NETWORK_ERRORProxy or transport failureTransient — retry
ASR_QUOTA_EXHAUSTEDSpeech recognition quota spent for nowRetry later; caption transcripts are unaffected
UNEXPECTED_ERRORUnhandled error on that itemReport it — other items continue

One bad URL never aborts a run.

Input reference

OptionDefaultNotes
startUrlsVideos, playlists, channels. Bare 11-character YouTube IDs accepted.
maxVideosPerSource50Caps each playlist/channel. Direct video URLs are never capped.
languagesautoCaption languages in priority order
outputFormats["json","text"]Any of json, text, llm, srt, vtt
maxConcurrency5Higher is faster, more likely to be rate-limited
asrFallbacktrueTranscribe audio when no caption track exists
asrModelwhisper-large-v3-turboPrimary-provider model. Ignored when the fallback provider runs.
maxAsrDurationSeconds3600Skip AI on longer videos instead of running up a bill
forceAsrLanguageauto-detectISO 639-1 code handed to the speech recognition model
proxyCountryCodeTwo-letter country for the residential proxy
captionEgressautoCaption download route; auto uses the cheaper one and falls back
reuseSessiontrueReuse 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 asr rows can be wrong even when the text is right. Set forceAsrLanguage when it matters.
  • TikTok rate-limits aggressively; the Actor retries, but large batches will see TIKTOK_CHALLENGE on some items.

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.