YouTube Transcript API: Text, Timestamps, SRT & VTT avatar

YouTube Transcript API: Text, Timestamps, SRT & VTT

Pricing

$4.00 / 1,000 transcripts

Go to Apify Store
YouTube Transcript API: Text, Timestamps, SRT & VTT

YouTube Transcript API: Text, Timestamps, SRT & VTT

Turn YouTube video links into clean transcript text, timestamps, SRT and VTT subtitles. Supports available manual and automatic captions, batch URLs and language selection. Pay only for successful transcripts.

Pricing

$4.00 / 1,000 transcripts

Rating

0.0

(0)

Developer

Himanshu Agrawal

Himanshu Agrawal

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

21 hours ago

Last modified

Share

Get the captions that already exist on YouTube videos as plain text, timestamped segments, SRT or WebVTT. Paste video URLs or IDs and get one dataset item per video.

This Actor reads existing captions only. Both manual captions (uploaded by the creator) and automatic captions (generated by YouTube) are supported. If a video has no captions, this Actor cannot transcribe it. There is no AI speech recognition in this version.

Quick start

  1. Add one or more video URLs or IDs to Video URLs or IDs.
  2. Optionally set Preferred languages, for example en or es.
  3. Click Start. Each video with captions becomes one dataset item.
  4. Open the SUMMARY record in the key-value store to see what happened to every input, including videos that had no captions.

Supported inputs

Input typeExample
Video IDjNQXAC9IVRw
Watch URLhttps://www.youtube.com/watch?v=jNQXAC9IVRw
Short linkhttps://youtu.be/jNQXAC9IVRw
Shortshttps://www.youtube.com/shorts/VIDEO_ID
Livehttps://www.youtube.com/live/VIDEO_ID
Embedhttps://www.youtube.com/embed/VIDEO_ID

Accepted hosts are youtube.com, www.youtube.com, m.youtube.com, music.youtube.com, youtu.be and www.youtube-nocookie.com (embed). Playlists, channels, search pages and other websites are rejected and recorded in SUMMARY. A watch URL that also has a playlist parameter is read as that single video.

Input

FieldTypeDefaultDescription
videoUrlsarray of stringsrequiredVideo URLs or 11 character IDs. Up to 1000 entries. Duplicates are removed before any request.
languagesarray of strings["en"]Caption languages in order of preference. en also matches regional tracks such as en-US, and pt-BR also matches pt. Within a language, manual captions are preferred over automatic ones.
fallbackToOriginalbooleantrueIf none of your languages exist, return the video's original caption language. The item then has matchedRequestedLanguage: false. If off, such videos are skipped with code LANGUAGE_NOT_AVAILABLE. Captions are never translated.
includeSegmentsbooleantrueAdd the segments array with timestamps.
includeSrtbooleanfalseAdd an srt string.
includeVttbooleanfalseAdd a vtt string.
maxVideosinteger100Maximum unique videos to process, from 1 to 1000. Extra inputs are skipped before any request.
proxyConfigurationobjectresidentialProxy settings. Residential proxies are recommended for reliability.

Output

Only videos with a non-empty transcript are saved to the dataset.

FieldDescription
videoId11 character video ID.
urlCanonical watch URL.
titleVideo title as reported by YouTube, or null if not provided.
channelNameChannel name as reported by YouTube, or null.
languageLanguage code of the returned caption track.
availableLanguagesCaption tracks the video offers: languageCode, name, isGenerated.
isGeneratedtrue for automatic captions, false for manual, null if unknown.
matchedRequestedLanguagetrue if the track matches one of your languages, false if the original language was used as a fallback.
textFull transcript as one string.
segmentCountNumber of caption segments.
durationSecondsVideo length in seconds as reported by YouTube, or null.
segmentsOptional. [{ text, start, duration, end }], all times in seconds.
srtOptional. SubRip subtitle text.
vttOptional. WebVTT subtitle text.
scrapedAtISO timestamp.

Example item

This example is illustrative and shortened. Real values come from the video.

{
"videoId": "jNQXAC9IVRw",
"url": "https://www.youtube.com/watch?v=jNQXAC9IVRw",
"title": "Example video title",
"channelName": "Example channel",
"language": "en",
"availableLanguages": [
{ "languageCode": "en", "name": "English", "isGenerated": false }
],
"isGenerated": false,
"matchedRequestedLanguage": true,
"text": "First caption line. Second caption line.",
"segmentCount": 2,
"durationSeconds": 19,
"segments": [
{ "text": "First caption line.", "start": 1.2, "duration": 2.16, "end": 3.36 },
{ "text": "Second caption line.", "start": 4.12, "duration": 3.5, "end": 7.62 }
],
"scrapedAt": "2026-10-27T12:00:00.000Z"
}

SUMMARY record

The default key-value store gets a SUMMARY record with:

  • status: SUCCEEDED, NO_RESULTS or FAILED, plus a message.
  • Counts: requested, unique, invalid, duplicates, processed, succeeded, failed, skippedDueToLimit, skippedDueToSpendingLimit.
  • spendingLimitReached, startedAt, finishedAt.
  • outcomes: one entry per input with inputIndex, videoId, status and, for failures, a stable code and message.
CodeMeaningRetried
INVALID_INPUTNot a valid video URL or ID.No
UNSUPPORTED_URLPlaylist, channel, other website or other URL type.No
VIDEO_UNAVAILABLEVideo deleted, unavailable or not found.No
VIDEO_RESTRICTEDPrivate, age restricted or sign in required.No
NO_CAPTIONSThe video has no captions.No
LANGUAGE_NOT_AVAILABLENo requested language and fallback is off.No
BLOCKEDThe source refused or rate limited the request.Yes
SOURCE_ERRORThe source returned a server error.Yes
EMPTY_CAPTIONSThe caption track was empty or unreadable.Yes
TIMEOUT, NETWORK_ERRORConnection problems.Yes
RESPONSE_TOO_LARGE, INTERNAL_ERRORUnexpected response or error.No
OUTPUT_ERRORSaving the result could not be confirmed. Check the dataset before retrying the video.No

Retryable errors are tried up to 3 times in total, each time with a new proxy session.

If every processed video fails because of source or network errors, the run is marked as failed. If videos simply have no captions, the run finishes normally with zero items and status NO_RESULTS.

Pricing

$0.004 per successful transcript, or $4 per 1,000, on every Apify plan. No Actor start fee. The charge covers one video, regardless of caption length and whether you include timestamps, SRT or VTT.

You pay per saved transcript (event transcript). Duplicates, invalid inputs, failed extractions, videos without captions and skipped inputs are not charged. Current prices are shown on the Actor's pricing tab. If your run reaches its spending limit, the Actor stops starting new videos and records the rest as skippedDueToSpendingLimit.

Limitations

  • Only existing captions are returned. No speech recognition and no translation.
  • Private, members only, age restricted and deleted videos cannot be read.
  • Playlists and channels are not expanded in this version. Pass video URLs.
  • Automatic captions may contain recognition mistakes made by YouTube.
  • YouTube can change its pages at any time, which may temporarily cause failures.

API usage

Replace APIFY_TOKEN with your API token. Keep it out of source code.

Synchronous run (small batches)

Returns dataset items directly. Synchronous runs time out after 300 seconds, so keep batches small.

curl -X POST "https://api.apify.com/v2/acts/himanshu-data~youtube-transcript-api/run-sync-get-dataset-items" \
-H "Authorization: Bearer $APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"videoUrls": ["https://www.youtube.com/watch?v=jNQXAC9IVRw"], "languages": ["en"]}'

Python

import os
import requests
resp = requests.post(
"https://api.apify.com/v2/acts/himanshu-data~youtube-transcript-api/run-sync-get-dataset-items",
headers={"Authorization": f"Bearer {os.environ['APIFY_TOKEN']}"},
json={"videoUrls": ["https://www.youtube.com/watch?v=jNQXAC9IVRw"], "languages": ["en"]},
timeout=320,
)
resp.raise_for_status()
for item in resp.json():
print(item["videoId"], item["language"], item["segmentCount"])

JavaScript (Node.js 18+)

const res = await fetch(
'https://api.apify.com/v2/acts/himanshu-data~youtube-transcript-api/run-sync-get-dataset-items',
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.APIFY_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ videoUrls: ['https://www.youtube.com/watch?v=jNQXAC9IVRw'], languages: ['en'] }),
},
);
if (!res.ok) throw new Error(`Request failed with status ${res.status}`);
const items = await res.json();
console.log(items.map((i) => `${i.videoId} ${i.segmentCount}`));

Large batches (asynchronous)

For many videos, start a run and poll it instead of waiting on one request:

  1. Start: POST https://api.apify.com/v2/acts/himanshu-data~youtube-transcript-api/runs with the input as JSON body. The response contains data.id (run ID) and data.defaultDatasetId.
  2. Poll: GET https://api.apify.com/v2/actor-runs/RUN_ID?waitForFinish=60 until data.status is SUCCEEDED, FAILED, ABORTED or TIMED-OUT.
  3. Fetch items: GET https://api.apify.com/v2/datasets/DATASET_ID/items?format=json.
  4. Fetch the summary: GET https://api.apify.com/v2/actor-runs/RUN_ID/key-value-store/records/SUMMARY.

Send the same Authorization: Bearer APIFY_TOKEN header on every request.