X (Twitter) Transcript Scraper: Subtitles, SRT and Metadata avatar

X (Twitter) Transcript Scraper: Subtitles, SRT and Metadata

Pricing

from $0.80 / 1,000 transcripts

Go to Apify Store
X (Twitter) Transcript Scraper: Subtitles, SRT and Metadata

X (Twitter) Transcript Scraper: Subtitles, SRT and Metadata

Scrape X (Twitter) video transcripts and subtitles with timestamps as JSON, text, SRT or WebVTT, from captions X generated or the poster uploaded. Post metadata free on request. Pay only for videos that return a transcript.

Pricing

from $0.80 / 1,000 transcripts

Rating

0.0

(0)

Developer

Fetchrail

Fetchrail

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

0

Monthly active users

9 hours ago

Last modified

Categories

Share

📝 X (Twitter) Transcript Scraper: Subtitles, SRT and Metadata

Turn the videos in X posts into clean, timestamped text. Paste a list of post links, choose a language and a format, and get one result per video, ready for an LLM, a spreadsheet or a subtitle editor.

At a glance
Price$1.00 per 1,000 transcripts, platform usage included
Charged whenA video returns a transcript. Everything else is free.
FormatsTimestamped segments (JSON), plain text, SRT, WebVTT
CaptionsX's automatic captions and captions the poster uploaded, each labelled
Linksx.com and twitter.com posts, bare post ids, /video/2 for one video of several
MetadataPost text, account, duration, post date, likes, replies, hashtags: free on request
AI agentsCallable as a tool from Claude, Cursor and VS Code over MCP

🧾 What's in each result

  • The transcript in the format you chose. As segments, each line carries text, start and duration in seconds.
  • Which track you got, in language, and whether X generated it or the poster uploaded it, in isAsr.
  • What you asked for, in preferred, so a fallback is easy to spot.
  • Every caption track the video has, in captionTracks.
  • The post's metadata, when you turn it on: post text, account, video duration, post date, like and reply counts, hashtags and preview images.
  • A reason code for every post that has no transcript, instead of a crashed run.

📌 Which X posts have a transcript

This Actor returns the captions X already shows in its player; it does not run speech-to-text of its own. X generates captions for many videos with speech, and posters can upload their own. That leaves gaps:

  • Not every video has captions. In our own test of public videos in October 2026, about half did. Speech-led clips such as interviews, briefings and announcements mostly had them; music-led trailers and clips of a few seconds mostly did not. A video without captions returns NO_CAPTIONS, free of charge.
  • Live broadcast replays and Spaces have no transcripts here.
  • A post that links to a YouTube video returns that video's YouTube transcript, with site set to youtube, charged as one transcript.
  • A post with several videos needs one link per video: add /video/1, /video/2 and so on to the post URL.
  • A post with no video, or one that only links to another website, returns an error code instead, free of charge.

🧠 Who it's for

You are buildingSet this
Media monitoring of officials, brands or newsroomsincludeMetadata: true, then export to CSV or Excel
A summarizer, classifier or RAG indexformat: json, so every chunk keeps its timestamp
A prompt from a batch of postsformat: text, one string per video
Subtitles for re-editing a clipformat: srt or vtt
Quotes for an article, with the exact wordingDefault settings: uploaded captions come first
An agent that answers questions about postsThe MCP setup below

▶️ Run it in three steps

  1. Paste X post links into X post URLs.
  2. Optionally set a Preferred language, such as en, de or pt-BR.
  3. Press Start, then download the results as JSON, CSV or Excel.

⚙️ Input

FieldTypeDefaultWhat it does
urlslist of strings, requiredX post links or bare post ids. A link repeated in the list is dropped, so it is never charged twice in one run.
languagestringEnglish where there is onePreferred language code. A preference, not a filter: see below.
preferAutomaticCaptionsbooleanfalseWithin a language, return X's automatic track before the poster's own.
formatjson, text, srt, vttjsonSegments with timestamps, or the transcript as one string.
includeMetadatabooleanfalseAdd the post's metadata to each result, at no extra charge.

How the language is chosen

A video always returns a transcript when it has any caption track at all. Tracks are tried in this order:

  1. Your preferred language
  2. English
  3. The first track available

Within each language, a track the poster uploaded comes before X's automatic one, unless preferAutomaticCaptions is on. Most X videos have a single track, in the language spoken. preferred echoes what you asked for and language says what you got, so filtering out an unwanted fallback is one comparison.

Recipes

A batch of links and bare post ids

{
"urls": [
"https://x.com/NASA/status/1491475671058681863",
"2098842302278254990",
"https://twitter.com/SOME_ACCOUNT/status/POST_ID/video/2"
]
}

A bare id is the number at the end of a post link. /video/2 picks the second video of a post with several.

SRT subtitles, X's automatic captions first

{
"urls": ["https://x.com/NASA/status/1491475671058681863"],
"preferAutomaticCaptions": true,
"format": "srt"
}

This video has only the captions NASA uploaded, so that track is returned anyway, and isAsr says so.

Plain text plus post details, for a monitoring sheet

{
"urls": ["https://x.com/NASA/status/1491475671058681863"],
"format": "text",
"includeMetadata": true
}

🗂️ Output

One item per URL. Here is a transcript with metadata on, shortened. With metadata off, the fields from description to extra are left out:

{
"inputUrl": "https://x.com/NASA/status/1491475671058681863",
"site": "x",
"id": "1491441845964001288",
"url": "https://x.com/NASA/status/1491475671058681863",
"title": "NASA - 🎵 We’ve got it. On its way to touch the Sun, our Parker Solar Probe ...",
"preferred": { "language": null, "asr": false },
"language": "en",
"isAsr": false,
"captionTracks": [
{ "language": "en", "asr": false, "name": null }
],
"format": "json",
"segmentCount": 32,
"transcript": [
{ "text": "NASA has captured the first visible light images of the surface of Venus from space.", "start": 0.53, "duration": 6.19 },
{ "text": "The planet’s surface is normally shrouded from sight by a thick layer of clouds.", "start": 6.72, "duration": 6.68 }
],
"description": "🎵 We’ve got it. On its way to touch the Sun, our Parker Solar Probe took the 1st visible light images of Venus' surface from space! ...",
"uploader": {
"id": "11348282",
"name": "NASA",
"url": "https://twitter.com/NASA"
},
"durationSeconds": 204,
"uploadDate": "2022-02-09",
"viewCount": null,
"isLive": false,
"thumbnails": [
{ "url": "https://pbs.twimg.com/media/FLKqq-UXoAARGh8.jpg?name=thumb", "width": 150, "height": 150 },
{ "url": "https://pbs.twimg.com/media/FLKqq-UXoAARGh8.jpg?name=orig", "width": 720, "height": 405 }
],
"chapters": [],
"tags": ["VisionsOfVenus"],
"extra": {
"like_count": 3353,
"comment_count": 75
},
"error": null
}

id is the video's own id on X, which differs from the post id in the link. X does not show view counts to anonymous visitors, so viewCount is usually null.

When a post has no transcript

You still get an item, and it costs nothing:

{
"inputUrl": "https://x.com/CTVJLaidlaw/status/1600649710662213632/video/2",
"error": {
"code": "NO_CAPTIONS",
"message": "This media has no caption tracks.",
"requestId": "..."
}
}
CodeMeaning
NO_CAPTIONSThe video has no captions.
UNAVAILABLEThe post has no video, or it was deleted, made private or withheld.
BLOCKEDRefused to anonymous viewers: a post marked sensitive, a protected account, or a linked YouTube video behind an age check.
UPSTREAM_RATE_LIMITEDX is throttling requests. Try again later.
EXTRACTION_FAILEDA temporary failure. Try again later.
UNSUPPORTED_SITENot an X post, or a post that only links to another website.
INVALID_URLNot a valid URL, or a post with several videos: add /video/1, /video/2.
FREE_PLAN_LIMITSkipped: the Apify free plan's daily transcripts are used up.
CHARGE_LIMIT_REACHEDSkipped: the run reached the maximum charge you set.

None of these is charged.

🏷️ Pricing

One price, one event: $1.00 per 1,000 transcripts. You are charged once per video that returns a transcript, with or without metadata. Posts without one cost nothing, and platform usage is included.

Videos with a transcriptCost
100$0.10
1,000$1.00
10,000$10.00
100,000$100.00

Set a maximum charge for a run and it stops before going over, listing the URLs it did not process.

On the Apify free plan, you get 30 transcripts per day, across all your runs. The count resets at 00:00 UTC. Once it is reached, the run stops and returns the remaining URLs with the FREE_PLAN_LIMIT code, free of charge, and the run's status message says so. Posts without a transcript do not count. Paid Apify plans have no limit.

🔌 Plug it into your AI agent

Through Apify's MCP server, this Actor becomes a tool your agent can call. Use this server URL, with your Apify API token:

https://mcp.apify.com?tools=fetchrail/x-twitter-transcript
ClientHow to add it
Claude Codeclaude mcp add --transport http apify "https://mcp.apify.com?tools=fetchrail/x-twitter-transcript" --header "Authorization: Bearer YOUR_TOKEN"
Claude Desktop, claude.aiAdd a custom connector with the URL above and sign in to Apify
CursorIn .cursor/mcp.json, under mcpServers: the URL, plus an Authorization: Bearer YOUR_TOKEN header
VS CodeIn .vscode/mcp.json, under servers: "type": "http", the URL and the same header

Then just ask, for example: "What did they say in this post's video? Give me the two strongest quotes with timestamps."

🧑‍💻 From code and automations

One HTTP request. Runs the Actor and returns the results. Replace YOUR_TOKEN with your Apify API token:

curl -X POST "https://api.apify.com/v2/acts/fetchrail~x-twitter-transcript/run-sync-get-dataset-items?token=YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"urls": ["https://x.com/NASA/status/1491475671058681863"], "format": "text"}'

Python, with apify-client version 3 or later:

from apify_client import ApifyClient
client = ApifyClient("YOUR_TOKEN")
run = client.actor("fetchrail/x-twitter-transcript").call(
run_input={"urls": ["https://x.com/NASA/status/1491475671058681863"], "format": "text"}
)
for item in client.dataset(run.default_dataset_id).iterate_items():
print(item.get("title"), item.get("transcript") or item["error"])

No code. Apify's Make, n8n and Zapier integrations run the Actor and pass each result to your next step, such as a spreadsheet row or a Slack message. Schedules and webhooks are set up from Apify Console.

🙋 Questions people ask

Why did a video return NO_CAPTIONS when it has speech? X has not made captions for it and the poster did not upload any. This Actor returns the captions X already has and does not transcribe audio itself. You are not charged.

Why did I get a different language than I asked for? The video has no track in that language, so the fallback order chose another. Compare language with preferred in the result.

Do t.co links work? No. A t.co link can point anywhere, so open it and paste the x.com link it leads to.

Can I pass a profile, a search or a list? Not yet. Pass individual post links.

Is this affiliated with X? No. It is an independent tool that reads captions X shows publicly to any viewer.

Is it legal? How you use transcripts is your responsibility: they can be protected by copyright, and spoken content can contain personal data. If in doubt, consult a lawyer.

🛟 Support

Open an issue on this Actor's Issues tab. If a post failed, include the requestId from its result, which lets us trace exactly what happened.