X (Twitter) Transcript Scraper: Subtitles, SRT and Metadata
Pricing
from $0.80 / 1,000 transcripts
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
Maintained by CommunityActor 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 when | A video returns a transcript. Everything else is free. |
| Formats | Timestamped segments (JSON), plain text, SRT, WebVTT |
| Captions | X's automatic captions and captions the poster uploaded, each labelled |
| Links | x.com and twitter.com posts, bare post ids, /video/2 for one video of several |
| Metadata | Post text, account, duration, post date, likes, replies, hashtags: free on request |
| AI agents | Callable 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,startanddurationin seconds. - Which track you got, in
language, and whether X generated it or the poster uploaded it, inisAsr. - 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
siteset toyoutube, charged as one transcript. - A post with several videos needs one link per video: add
/video/1,/video/2and 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 building | Set this |
|---|---|
| Media monitoring of officials, brands or newsrooms | includeMetadata: true, then export to CSV or Excel |
| A summarizer, classifier or RAG index | format: json, so every chunk keeps its timestamp |
| A prompt from a batch of posts | format: text, one string per video |
| Subtitles for re-editing a clip | format: srt or vtt |
| Quotes for an article, with the exact wording | Default settings: uploaded captions come first |
| An agent that answers questions about posts | The MCP setup below |
▶️ Run it in three steps
- Paste X post links into X post URLs.
- Optionally set a Preferred language, such as
en,deorpt-BR. - Press Start, then download the results as JSON, CSV or Excel.
⚙️ Input
| Field | Type | Default | What it does |
|---|---|---|---|
urls | list of strings, required | X post links or bare post ids. A link repeated in the list is dropped, so it is never charged twice in one run. | |
language | string | English where there is one | Preferred language code. A preference, not a filter: see below. |
preferAutomaticCaptions | boolean | false | Within a language, return X's automatic track before the poster's own. |
format | json, text, srt, vtt | json | Segments with timestamps, or the transcript as one string. |
includeMetadata | boolean | false | Add 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:
- Your preferred language
- English
- 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": "..."}}
| Code | Meaning |
|---|---|
NO_CAPTIONS | The video has no captions. |
UNAVAILABLE | The post has no video, or it was deleted, made private or withheld. |
BLOCKED | Refused to anonymous viewers: a post marked sensitive, a protected account, or a linked YouTube video behind an age check. |
UPSTREAM_RATE_LIMITED | X is throttling requests. Try again later. |
EXTRACTION_FAILED | A temporary failure. Try again later. |
UNSUPPORTED_SITE | Not an X post, or a post that only links to another website. |
INVALID_URL | Not a valid URL, or a post with several videos: add /video/1, /video/2. |
FREE_PLAN_LIMIT | Skipped: the Apify free plan's daily transcripts are used up. |
CHARGE_LIMIT_REACHED | Skipped: 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 transcript | Cost |
|---|---|
| 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
| Client | How to add it |
|---|---|
| Claude Code | claude mcp add --transport http apify "https://mcp.apify.com?tools=fetchrail/x-twitter-transcript" --header "Authorization: Bearer YOUR_TOKEN" |
| Claude Desktop, claude.ai | Add a custom connector with the URL above and sign in to Apify |
| Cursor | In .cursor/mcp.json, under mcpServers: the URL, plus an Authorization: Bearer YOUR_TOKEN header |
| VS Code | In .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 ApifyClientclient = 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.