Twitter (X) Tweets Scraper
Pricing
$19.99/month + usage
Twitter (X) Tweets Scraper
Scrapes posts from Twitter X profiles, hashtags, or searches, capturing tweet text, media, timestamps, metrics, authors, hashtags, and URLs. Ideal for trend research, competitor analysis, sentiment studies, influencer insights, and large-scale Twitter X post extraction.
Pricing
$19.99/month + usage
Rating
5.0
(3)
Developer
Scraper Engine
Maintained by CommunityActor stats
0
Bookmarked
49
Total users
4
Monthly active users
9 days ago
Last modified
Categories
Share
X (Twitter) Post Scraper — Text, Engagement, Media and Poll Data
Extract public X (Twitter) posts from any profile or single post URL and get one structured JSON row per post: full untruncated text, likes, replies, reposts, quotes, bookmarks and views, every photo and video, hashtags, mentions, quoted posts, and live poll results — plus the author's follower count, bio and verification status. No parsing, no HTML, no official Twitter API key. Point it at a handle or a post URL below and run it.
🐦 What is X (Twitter) Post Scraper?
X (Twitter) Post Scraper collects public posts from X profiles and individual post URLs and returns them as typed, normalized JSON — one row per post. It reads X's own GraphQL timeline directly rather than rendering pages in a browser. No X account or login is required for profile timelines or single post URLs; an X session (two cookie values) is only needed for the optional "profile's own replies" surface. It's built for social-media analysts, marketers, researchers and developers who need structured post data without maintaining scraping infrastructure themselves.
🔓 What X post data is publicly available to scrape?
A profile's own posts, reposts, quotes and pinned post are visible to anyone on x.com without logging in, and so is any single post reached by direct URL — including its poll results and the post it quotes. The one surface X gates behind a login is a profile's replies to other people's posts (the "Posts and replies" tab); everything else below is open.
| Data category | Public (no login) | Behind an X session |
|---|---|---|
| Profile's own posts, reposts and quotes | ✅ | |
| Single post by URL | ✅ | |
| Poll choices and live vote counts | ✅ | |
| Quoted post text, author and URL | ✅ | |
| Author profile stats (followers, bio, avatar) | ✅ | |
| Profile's replies to other accounts' posts | ✅ requires auth_token + ct0 | |
| A protected account's posts | ❌ not accessible to this scraper at all — only approved followers see them | |
| Who liked a given post | ❌ not exposed by X to any caller, logged in or not |
X (Twitter) Post Scraper only returns publicly visible data — what any visitor sees. Nothing behind a login wall, beyond the one optional replies surface above.
📦 What data can I extract with X (Twitter) Post Scraper?
Every post row carries the post's own content, its engagement and author metrics, and any attached media, poll or quoted post — 39 fields in total, listed below exactly as the actor writes them.
Post content and identity fields
| Field | Description |
|---|---|
type | "tweet" for a post row, "error" for an uncharged accounting row |
id | The post's X id |
url | Canonical post URL |
user_posted | Author's handle. On a repost, this is the original author, not the account that reposted it |
name | Author's display name |
description | Full post text, untruncated — long-form "note" posts are read in full, not the 280-character preview |
date_posted | ISO 8601 UTC timestamp |
lang | X's detected language code |
conversation_id | Id of the thread this post belongs to |
is_repost / reposted_by | Whether this timeline entry is a repost, and the handle that reposted it |
is_quote | Whether the post quotes another post |
is_reply / in_reply_to_user / in_reply_to_post_id | Whether the post is a reply, and its target |
is_pinned | true/false for a profile timeline row; null for a post fetched by URL, since pin state is a profile-level fact |
hashtags / tagged_users | Hashtag texts and mentioned handles used in the post |
biography / external_url / profile_image_link | Author's bio, website (expanded, not the t.co shortener) and avatar |
input | {"url": <canonical post URL>}, carried through for joining rows back to their source |
Engagement and author metrics
| Field | Description |
|---|---|
likes, replies, reposts, quotes, bookmarks | Engagement counts. null when X does not report one — never a fabricated 0 |
views | Integer view count, or null when X reports a view state without a number |
followers, following, posts_count | Author's follower count, following count and total post count |
is_verified / is_blue_verified | Both mirror X's paid blue-check flag (is_verified is kept for backward compatibility) |
verified_type | X's account class when set, e.g. "Government", "Business" — else null |
Media, polls and nested objects
| Field | Description |
|---|---|
photos | Every photo URL on the post, or null |
videos | Every video/GIF MP4 URL, best bitrate first, or null |
media | One object per attachment: type, image_url, video_url, alt_text, width, height, duration_ms, expanded_url |
quoted_post | The quoted post's data_posted (a shipped, intentionally-kept typo — not date_posted), description, post_id, profile_id, profile_name, profile_username, url, videos; null if nothing is quoted |
poll | choices (label + live vote count), total_votes, counts_are_final, duration_minutes, end_datetime_utc, last_updated_datetime_utc; null if the post has no poll |
link_preview | card_type, title, description, domain, url for a link card; null if none |
An uncharged error row replaces the post rows for a target that returned nothing: type: "error", id: null, target, errorReason (one of user_not_found, account_suspended, protected_account, user_unavailable, post_not_found, post_unavailable, no_posts_returned, auth_required, rate_limited, request_failed), message, input.
🤖 Add-on: Need additional X or cross-platform data?
If you also need X profile-centric fields beyond what a post row carries, pair this actor with Twitter (X.com) Tweets & Profiles Scraper. Monitoring the same conversation on Meta's Threads app in parallel? Threads Search Post Scraper: Posts, Quotes & Replies returns a comparable post-level shape for that platform.
⚖️ How does X (Twitter) Post Scraper differ from the official X API?
X does publish an official API (docs.x.com), but it requires creating a developer account and app at console.x.com and authenticating with a bearer token before any request — X (Twitter) Post Scraper needs none of that for public timelines and post URLs. As of the pricing page checked 2026-08-04, the X API runs on pay-per-usage credits with no free tier for new signups ("The X API uses pay-per-usage pricing. No subscriptions—pay only for what you use."); this actor is billed through Apify's per-result pricing instead.
| Feature | Official X API | X (Twitter) Post Scraper |
|---|---|---|
| Access setup | Developer account + app registration + bearer token | Provide profile URLs or handles in the Apify Console |
| Pricing model | Pay-per-usage credits (docs.x.com, checked 2026-08-04) | Apify pay-per-result (row_result event) |
| Approval required | Developer account creation | None |
| Rate limiting | Enforced by X per app/bearer token | Enforced by X per guest token; this actor reads the live headers and retries or reports a typed error |
| Output shape | Raw GraphQL/REST payload matching X's internal schema | Normalized flat JSON, one row per post, stable field names |
| Access to a profile's replies to others | Available with the right access level | Requires only your own X session cookies, no developer approval |
Use the official API when you need a contractual, developer-account-backed integration; use X (Twitter) Post Scraper when you want structured post data in the Apify Console without registering an app or managing credits.
▶️ How to use X (Twitter) Post Scraper
Run it from the Apify Console — no separate API signup, no browser to install.
- Open the actor's page on the Apify Store and click Try for free (or Run, if you already have it).
- Add at least one entry to the required
startUrlsfield — a profile URL,@handle, numeric user id, or a single post URL. - Optionally set
maxTweets(1–1000, default 10),sortOrder, or turn onincludeReplies(with an X session — see Input below). - Start the run.
- Download the results as JSON, CSV, Excel, XML or HTML from the dataset's Export panel, or stream them via the API.
Scaling to bulk post extraction
startUrls is an array — list any number of profiles and post URLs in one run, mixed freely, and each target is processed and charged independently. There is no separate "bulk mode" input: adding more lines to startUrls is the bulk mechanism. A target that returns nothing (suspended account, deleted post) gets its own uncharged error row rather than silently vanishing from the output.
💡 What can you do with X post data?
- 📊 Social media analysts tracking a brand's account use
likes,reposts,viewsanddate_postedto chart engagement per post over a campaign window without opening each thread by hand. - 🔬 Researchers studying how claims spread use
is_repost,reposted_byandquoted_postto trace an original post through the reposts and quote-posts that carried it further. - 🏛 Public affairs and compliance teams monitoring official accounts use
verified_typeandis_blue_verifiedto separate government or business accounts from unverified handles at scale. - 📈 Growth marketers benchmarking competitors use
followers,posts_countandbiographyto profile competing accounts before planning a campaign. - 🤖 AI engineers feed
description,hashtagsandpollresults directly into an LLM summarization or trend-detection pipeline — the output is typed JSON, so no scraping or HTML-parsing step sits between X and the agent.
🚦 How does X (Twitter) Post Scraper handle rate limits and blocking?
The actor discovers X's current public web bearer token and GraphQL query ids at runtime (no bundled credentials, no browser launched), then activates a keyless guest token for public timelines and single posts. It reads X's live x-rate-limit-* response headers on every call rather than assuming a fixed number. On an HTTP 429 for a keyless call, it rotates to a fresh guest token and retries once; if the rate-limit window resets within 90 seconds it waits that out and retries; otherwise the target gets a typed rate_limited error row instead of an empty result. A non-429 request failure gets one retry on a freshly rotated Apify Proxy exit, if proxy is enabled, before the target is recorded as failed. The actor does not solve CAPTCHAs — none is implemented in the source.
⬇️ Input
| Parameter | Required | Type | Description | Example value |
|---|---|---|---|---|
startUrls | Yes | array of strings | Profile URLs/handles/numeric ids, or single post URLs, one per line | ["https://x.com/NASA", "@NASAWebb"] |
maxTweets | No | integer (1–1000, default 10) | Posts to collect per profile. Ignored for single post URLs | 25 |
sortOrder | No | string enum: recent (default), oldest, popular | Re-orders the posts collected in this run; does not page further back | "recent" |
includeReplies | No | boolean, default false | Also collect the profile's replies to other accounts. Requires auth_token + ct0 | false |
auth_token | No | string (secret) | auth_token cookie value from an x.com session. Only used for includeReplies | "a1b2c3..." |
ct0 | No | string (secret) | ct0 cookie value (CSRF token) from the same session. Required together with auth_token | "d4e5f6..." |
proxyConfiguration | No | object, default {"useApifyProxy": false} | Apify Proxy configuration; off by default | {"useApifyProxy": false} |
Example input
{"startUrls": ["https://x.com/NASA","@NASAWebb","https://x.com/Interior/status/2083180599066374311"],"maxTweets": 25,"sortOrder": "recent","includeReplies": false,"auth_token": "","ct0": "","proxyConfiguration": { "useApifyProxy": false }}
⬆️ Output
Results are written to the Apify dataset as one typed JSON row per post, with a consistent field set across every run. Export directly to JSON, CSV, Excel, XML or HTML from the Console, or read the dataset through the API.
Example output
{"type": "tweet","id": "2083281738525028863","url": "https://x.com/NASALangley/status/2083281738525028863","user_posted": "NASALangley","name": "NASA Langley Research Center","description": "Full, untruncated post text describing today's launch window.","date_posted": "2026-08-02T18:41:03.000Z","likes": 702,"replies": 37,"reposts": 89,"quotes": 4,"bookmarks": 71,"views": 246519,"lang": "en","conversation_id": "2083281738525028863","is_repost": false,"reposted_by": null,"is_quote": false,"is_reply": false,"in_reply_to_user": null,"in_reply_to_post_id": null,"is_pinned": false,"is_verified": true,"is_blue_verified": true,"verified_type": "Government","followers": 3519786,"following": 63,"posts_count": 11542,"profile_image_link": "https://pbs.twimg.com/profile_images/1234567890/avatar.jpg","biography": "Exploring the universe and our home planet.","external_url": "https://nasa.gov/langley","hashtags": ["Artemis"],"tagged_users": ["NASA"],"photos": ["https://pbs.twimg.com/media/example123.jpg"],"videos": [],"media": [{ "type": "photo", "image_url": "https://pbs.twimg.com/media/example123.jpg", "alt_text": "Launch pad at sunrise", "width": 2048, "height": 1365, "video_url": null, "duration_ms": null, "expanded_url": "https://x.com/NASALangley/photo/1" }],"quoted_post": null,"poll": null,"link_preview": null,"input": { "url": "https://x.com/NASALangley/status/2083281738525028863" }}
Field-by-field descriptions are in the "What data can I extract" section above. Error rows use a different shape (type: "error", target, errorReason, message) and are pushed without a charged_event_name, so they never bill — filter them out with type == "tweet" when consuming the dataset.
⚙️ How does it work?
X (Twitter) Post Scraper calls X's own GraphQL API directly — the same endpoints x.com's web client uses — rather than rendering pages in a browser. It discovers the current public bearer token and operation ids from X's live client bundle at runtime, so it isn't tied to a hardcoded credential that goes stale when X ships a new frontend build. Public timelines and post lookups run on a keyless guest token; the optional replies surface uses the X session cookies you supply. Requests can optionally go through Apify Proxy. Only data visible to a logged-out (or, for replies, logged-in) visitor is ever returned, and the output field names stay the same run over run regardless of changes to X's own page layout.
🔌 Integrations
X (Twitter) Post Scraper runs on the Apify platform, so it works with anything that can call Apify's API or client libraries.
Calling it programmatically
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_API_TOKEN>")run = client.actor("twitter-x-tweets-scraper-scraper").call(run_input={"startUrls": ["https://x.com/NASA"],"maxTweets": 25,})for item in client.dataset(run["defaultDatasetId"]).iterate_items():print(item["url"], item["likes"])
Works in Go, Ruby, Node.js, cURL — any language that can make an HTTP request against the Apify API.
No-code tools (n8n, Make)
In n8n, use the official Apify node and point it at this actor's id to start a run and pull the resulting dataset into your workflow. In Make, the Apify app's "Run an Actor" module does the same, and you can chain the dataset output straight into a spreadsheet or CRM module.
⚖️ Is it legal to scrape X (Twitter) posts?
Scraping publicly available data on X is generally lawful in most jurisdictions, since X (Twitter) Post Scraper returns only what any logged-out visitor already sees on x.com. That said, post rows include personal data about identifiable people — the author's handle, display name, bio and follower count — so GDPR and CCPA still govern how you store and use it once collected: you need a lawful basis (e.g. legitimate interest) for holding personal data at scale, and CCPA gives California residents rights over data collected about them. Consult legal counsel if your use case involves bulk storage of personal data.
❓ Frequently asked questions
What X (Twitter) post fields does the scraper return?
The top fields are description (post text), likes, replies, reposts and views, plus the author's followers and biography. The full 39-field list is in the "What data can I extract" section above.
Does X (Twitter) Post Scraper require an X account or login?
No, not for profile timelines or single post URLs — those run on a keyless guest token. An X session (auth_token + ct0 cookies) is required only for includeReplies; without it, that option makes the run fail immediately with an explanation rather than return a partial timeline.
How many posts can I extract in one run?
Up to maxTweets (1–1000, default 10) per profile in startUrls, and any number of profiles or post URLs per run — each is processed and billed independently. A single post URL always returns exactly one row regardless of maxTweets.
What happens if a profile is suspended, protected, or doesn't exist?
That target produces an uncharged error row instead of post rows, with a machine-readable errorReason — user_not_found, account_suspended, protected_account, or user_unavailable — and a plain-language message. If every target in a run fails this way, the run itself ends with a failed status rather than a green empty dataset.
Can I scrape multiple X profiles or posts at once?
Yes — startUrls accepts any number of profile URLs, handles, numeric user ids and single post URLs mixed together in one run.
Does X (Twitter) Post Scraper work with Claude, ChatGPT and other AI agent tools?
It has no dedicated MCP server. It's callable as a standard HTTP endpoint through the Apify API or the apify_client SDK, so any agent framework that can make an HTTP call or use a tool-calling wrapper can invoke it and consume the JSON result directly.
What makes this actor's post data different from a generic X scraper?
It unwraps reposts to the original post's real engagement counts and author, rather than the zero-filled retweet wrapper X's own API structurally returns; it reads self-thread replies from timeline module entries that a simpler parser silently drops; and it reports live poll vote counts and an authoritative counts_are_final flag rather than treating polls as static text.
Does X (Twitter) Post Scraper return data in a format LLMs can use directly?
Yes. Every row is typed, normalized JSON with consistent field names across runs — no HTML, no selectors to write. Pass it straight to an LLM, index it into a vector store, or hand it to an agent tool.
What happens when X changes its layout or anti-bot system?
The actor discovers X's live bearer token and GraphQL operation ids from X's own client bundle at runtime instead of hardcoding them, so most frontend churn doesn't break it outright, and the output field contract documented above is designed to stay stable even when X's internal payload shape shifts. No specific update turnaround time is published.
Can I use X (Twitter) Post Scraper without managing proxies or browser infrastructure?
Yes. There is no browser in the request path at all, and Apify Proxy is optional — the actor was measured working from a bare Apify datacenter exit with proxy off.
Which fields work best for AI training data and RAG indexing?
For RAG, index description (post text), biography and link_preview.description as the high-information text fields. For training data, likes, replies, reposts, views, followers and posts_count are the most consistently structured numeric fields across every row.
🔗 Related scrapers
| Scraper | What it extracts |
|---|---|
| Twitter (X.com) Tweets & Profiles Scraper | X posts and profile-centric data on the same platform |
| Threads Search Post Scraper: Posts, Quotes & Replies | Posts, quotes and replies on Meta's Threads |
| Facebook Group Posts And Details Scraper | Posts, comments and engagement analytics from Facebook groups |
| LinkedIn Profile Post Scraper | Posts published from a LinkedIn profile |
| TikTok Data Scraper | Post and video data from TikTok |
💬 Your feedback
Found a bug, or missing a field you need from an X post? Let us know through the Issues tab on this actor's Apify Store page, or message us directly through the Apify Console — reports like this go straight into the next update.