X (Twitter) Profile & Tweets Scraper — Bio, Engagement Data
Pricing
from $0.30 / 1,000 tweet scrapeds
X (Twitter) Profile & Tweets Scraper — Bio, Engagement Data
Scrape X (Twitter) profiles and tweets by handle, URL or user ID — bio, follower counts, likes, retweets, replies, views. Filter tweets by likes or views, run incrementally, look up single tweets by URL. Profiles and tweet lookups need no login; full timelines use your cookies. Pay only for results.
Pricing
from $0.30 / 1,000 tweet scrapeds
Rating
0.0
(0)
Developer
Nicholas Cheng
Maintained by CommunityActor stats
0
Bookmarked
72
Total users
32
Monthly active users
4.6 days
Issues response
15 days ago
Last modified
Categories
Share
X (Twitter) Profile & Tweets Scraper
Give it a handle, get back that account's profile and tweets as clean JSON. Profiles and single tweets need no login, no cookies, no API key and no proxy configuration. X now shows logged-out visitors only the latest few posts of an account, so for full timelines you add the cookies of a logged-in X session — optional, encrypted, and used for nothing else. Point it at nasa, https://x.com/openai, or a numeric user ID, and it returns structured records you can drop straight into a database or feed to an agent.
You are charged only for results that actually came back. No mock rows, no placeholder padding, no minimum charge on a run that found nothing. If an account is suspended, protected, or does not exist, the run reports why and charges you nothing for it.
| Input | X / Twitter handles, profile URLs, or numeric user IDs |
| Output | Tweets (text, metrics, media, mentions, links) + enriched author profiles |
| Price | $0.005 per account + $0.0003 per tweet ($0.30 per 1,000) |
| Login required | Not for profiles or single tweets. Full timelines need optional login cookies — X shows logged-out visitors only an account's latest few posts |
| Single tweets | Yes — by URL or ID, $0.005 each, author profile embedded |
| Incremental runs | Yes — pick up where the last run left off, for 15-minute schedules |
| Filters | Likes, reposts, replies, views, text length, keywords, regex — filtered-out tweets are not charged |
| Keyword search | Not supported. See What this Actor cannot do |
What you get
Tweets
Every tweet record carries the text (including full long-form posts, which many scrapers truncate at 280 characters), timestamps, all six engagement counters, and fully expanded entities:
{"itemType": "tweet","type": "tweet","id": "2090487779268407625","url": "https://x.com/NASA/status/2090487779268407625","text": "Our Artemis II crew is one step closer to the Moon.","isLongform": false,"lang": "en","createdAt": "2026-08-19T14:02:11.000Z","source": null,"replyCount": 412, "retweetCount": 3105, "quoteCount": 188,"likeCount": 24907, "bookmarkCount": 601, "viewCount": 1840233,"conversationId": null,"isRetweet": false, "isQuote": false, "isReply": false,"hashtags": ["Artemis"],"cashtags": [],"mentions": [{ "userName": "NASAArtemis", "userId": "1050393...", "name": "NASA Artemis" }],"urls": ["https://www.nasa.gov/artemis-ii/"],"media": [{ "type": "photo", "url": "https://pbs.twimg.com/media/...jpg", "altText": "Four astronauts in orange flight suits" }],"authorUserName": "NASA","author": { "...full profile, see below..." }}
Retweets and quote tweets keep their lineage — retweetedTweetId, retweetedAuthor, quotedTweetId, quotedText — so you can tell original content from amplification without a second pass.
Profiles
The profile record goes past the usual follower-count-and-bio. These fields come straight from X's own account metadata; none of them are guessed, inferred, or generated by a language model:
| Field | Why it matters |
|---|---|
verifiedType | Government, Business, or blank — separates official accounts from paid blue checks |
verifiedSince | Exact ISO timestamp the account was verified |
parodyCommentaryFanLabel | X's mandatory label for parody / commentary / fan accounts — a direct impersonation signal |
affiliatesCount | How many sub-accounts a business account has under it |
professionalCategory | Self-declared business category for professional accounts |
tipjar | Bitcoin, Ethereum, PayPal, Venmo, Cash App, Patreon, GoFundMe and Bandcamp handles the account has published |
birthdate | Publicly published birthdate, with its visibility setting |
isProtected, hasGraduatedAccess, possiblySensitive | Account state flags |
Plus the standard set: userId, name, description, descriptionUrls (expanded, not t.co), website, location, createdAt, followers, following, tweetsCount, mediaCount, favouritesCount, isBlueVerified, profilePicture (full resolution), coverPicture.
Since X rebuilt its web app in October 2026, some of these are no longer sent to logged-out visitors and come back as null: verifiedSince, verifiedReason, isIdentityVerified, parodyCommentaryFanLabel, professionalCategory, tipjar, mediaCount, favouritesCount, canDm, hasGraduatedAccess. On tweets, source and conversationId are null for the same reason. The fields stay in the output so existing pipelines keep working; nothing is filled in by guesswork.
Input
{"twitterHandles": ["nasa", "openai"],"startUrls": [{ "url": "https://x.com/elonmusk" }],"maxTweetsPerProfile": 200,"includeRetweets": false,"since": "2026-01-01"}
| Option | Default | Notes |
|---|---|---|
twitterHandles | — | With or without @ |
startUrls | — | Any x.com/<handle> or twitter.com/<handle> URL. A tweet link pasted here is looked up as a single tweet |
userIds | — | Numeric IDs, immune to username changes |
tweetUrls | — | Single tweets by link (x.com/<handle>/status/<id>) or numeric ID — see Looking up single tweets |
maxTweetsPerProfile | 100 | 0 fetches the profile only |
maxItems | 0 | Hard cap across the whole run — bounds your spend |
scrapeProfilesOnly | false | Cheapest mode for bulk account enrichment |
cookies | — | Optional. auth_token and ct0 of a logged-in X session, for timelines beyond the latest few posts. Secret input |
includeReplies | true | |
includeRetweets | true | |
onlyWithMedia | false | |
since / until | — | ISO dates; since also stops pagination early, so it makes runs cheaper |
minLikes / minRetweets / minReplies / minViews | 0 | Engagement floors — see Filtering |
minTextLength | 0 | Drop one-word replies |
textContains / excludeTextContains | — | Keyword lists, case-insensitive |
textRegex | — | JavaScript regex, case-insensitive |
incremental | false | Fetch only tweets newer than the previous run — see Incremental runs |
stateStoreName | x-scraper-state | Where the per-account watermark is kept |
sinceId | — | Manual watermark: skip tweets at or below this ID |
includeAuthorProfile | true | Turn off for a slimmer dataset |
Pricing
Pay-per-event. You pay for what came back, nothing else.
| Event | Price | What it covers |
|---|---|---|
profile-scraped | $0.005 per account | Looking the account up and returning its enriched profile. Charged once per account. |
tweet-scraped | $0.0003 per tweet | Each tweet returned from that account's timeline — $0.30 per 1,000. |
tweet-looked-up | $0.005 per tweet | One tweet fetched on its own by URL or ID, with its author profile embedded. Same price as an account lookup, because it is the same amount of work: one targeted request, one record back. |
Two events instead of one flat per-row price, because the work is shaped that way: resolving an account costs the same whether you then pull 10 tweets from it or 2,000. Splitting it out is what lets the per-tweet rate stay at $0.30/1K instead of being marked up to subsidise account lookups.
What it works out to, against the closest comparable Actor on the Store, which charges $0.016 per profile plus $0.0004 per tweet:
| You want | Here | Closest comparable |
|---|---|---|
| 1 account, 10 tweets (monitoring) | $0.008 | $0.016 |
| 1 account, 100 tweets | $0.035 | $0.040 |
| 1 account, 2,000 tweets (archive) | $0.605 | $0.800 |
| 10 accounts, 200 tweets each | $0.650 | $0.920 |
Cheaper than that one at every volume, not just at the volume that makes the table look good.
Being straight about the rest of the field: a few Actors run a flat per-tweet rate slightly below this one and come out ahead on very large single-account pulls. They are also doing keyword search, which means they are driving a pool of logged-in accounts. That is a different tool with a different failure mode — see What this Actor cannot do.
Three commitments:
- Real results only. Every charged row is data that came back from X. Padding a thin result set with placeholder rows and billing for them is a thing that happens in this category; it does not happen here.
- Failures are free. Suspended, deleted, protected, or misspelled accounts produce an explanatory row and cost nothing — including the account lookup fee.
- No plan discrimination. Free-plan runs get the same code path, the same data, and the same reliability as everyone else.
There is a $0.01 floor on the run budget you can set. That is a floor on the budget cap, not a minimum charge — a run that returns one profile still costs you $0.005.
What you are not charged for
Those three commitments are not abstract. Each one names a specific thing that this Actor does not do, and every item below is checkable against a run log:
| What happens here | |
|---|---|
| Empty or placeholder rows | A tweet that arrives with an ID but no text, no timestamp, and no metrics is not a result. It is dropped before it reaches the dataset, so it is never billed. |
| Runs that find nothing | A suspended, deleted, protected, or misspelled account produces one explanatory row marked chargedForThis: false. The account fee is not charged either. |
| The free plan | Same code path, same fields, same page depth. No demo mode, no 10-row cap, no fields blanked out for free users. |
| Plan-tiered pricing | $0.005 per account and $0.0003 per tweet on every Apify plan — Free, Bronze, Silver, Gold, Platinum, Diamond. Check the Pricing tab: all six columns are identical. |
That last row is worth checking on any Actor you are comparing, including this one. The Pricing tab shows the per-plan breakdown, and the number a listing advertises in its title is not always the number a free-plan run pays.
Many accounts, few tweets each
If you are monitoring a set of accounts rather than archiving one, you want a cap that applies per account, in a single run. That is the default here:
{"twitterHandles": ["NASA", "NASAHubble", "NASAWebb", "NASAPersevere", "NASAMars"],"maxTweetsPerProfile": 3,"since": "2026-06-01"}
One run, five accounts, at most three tweets each, nothing older than the cutoff. Real output:
NASA profile + 3 tweetsNASAHubble profile + 3 tweetsNASAWebb profile + 3 tweetsNASAMars profile + 3 tweetsNASAPersevere profile + 0 tweets ← nothing past the cutoff, so nothing billedcharged: 12 tweets, 5 accounts = $0.0286
since / until filter on the tweet timestamp and stop paging once the timeline passes the
cutoff, so a narrow window costs less than a wide one rather than the same.
Filtering by engagement or text
Filters run before a tweet is written to the dataset, and a tweet that does not pass is not charged. Paging keeps going until the per-profile cap is met with tweets that do pass, or the timeline runs out.
{"twitterHandles": ["NASA", "SpaceX", "ESA"],"maxTweetsPerProfile": 10,"minLikes": 1000,"minViews": 100000,"excludeTextContains": ["giveaway"],"textRegex": "\\b(launch|liftoff|landing)\\b"}
| Filter | What it does |
|---|---|
minLikes, minRetweets, minReplies, minViews | Engagement floors. 0 means off. |
minTextLength | Drops one-word replies and bare links. |
textContains | Keep if the text contains any of the phrases (case-insensitive). |
excludeTextContains | Drop if the text contains any of the phrases. |
textRegex | JavaScript regex, case-insensitive, against the full text (long-form posts included). |
The run summary reports filteredOut so you can see how many tweets were read and dropped.
Incremental runs: pick up where you left off
If you run this on a schedule — every 15 minutes, every hour — you do not want to re-fetch and
re-pay for the same timeline each time. Turn on incremental:
{"twitterHandles": ["NASA", "SpaceX", "ESA"],"maxTweetsPerProfile": 50,"incremental": true,"stateStoreName": "space-agencies"}
The first run behaves normally and records, per account, the ID of the newest tweet it saw. Every
later run stops paging the moment it reaches that tweet, so an account with nothing new costs one
account lookup and zero tweets. The watermark lives under the key LAST_SEEN in a key-value store
named stateStoreName in your own Apify account — you can read it, edit it, or delete it to
start over. Use a different store name per watchlist so schedules do not interfere.
The watermark is the tweet ID, not a timestamp. Tweet IDs increase with time, and pinned tweets (which sit at the top of a timeline regardless of age) are ignored for the watermark, so a pinned post from 2019 does not fool the resume logic.
If you would rather manage state yourself, every run writes newestTweetIdByAccount to its
SUMMARY record. Pass the value back as sinceId on the next run and you get the same behaviour
without the store.
Looking up single tweets by URL or ID
Sometimes you have tweet links, not accounts — a list of posts to fact-check, a set of URLs
collected from somewhere else, a tweet someone sent you. Pass them as tweetUrls:
{"tweetUrls": ["https://x.com/NASA/status/2090487779268407625","1234567890123456789"]}
Each tweet is fetched individually — no timeline walk — and returned as a normal tweet record with
lookedUp: true and a compact author profile embedded (X's post page carries the author's name,
bio, counts and verification, but not location, website or join date — use twitterHandles for those). This is the same data shape as timeline
tweets, so the two mix in one dataset. A deleted, protected, or withheld tweet produces a row marked
chargedForThis: false and costs nothing.
What it does not do: fetch the replies under a tweet, the accounts that liked it, or the quote tweets pointing at it. X does not expose those to logged-out clients.
What this Actor cannot do
Being straight about this up front so you do not buy the wrong tool:
- No keyword or hashtag search and no followers / following lists. Not offered.
- Full timelines without login. X shows logged-out visitors only an account's latest few original posts, with no way to page further. Pass
cookiesfrom a logged-in session to get the rest. Timelines come from the profile's Posts tab (the account's original posts). - No reply threads, likers, or bookmarkers of a tweet. A single tweet by URL or ID — yes (see above); the conversation under it — no.
- Protected accounts return their profile only. Their tweets are not public, and no scraper can change that.
Profiles and single tweets work with no login at all. Timeline pages beyond the first few posts use the cookies you supply and nothing else — the Actor never logs in on its own and never uses anyone else's account.
How it stays working
In October 2026 X rebuilt its web app: profile and post pages are now rendered on X's servers with the data embedded in the page. This Actor reads that embedded data directly — one page per account or tweet, no API key, no guest token, no browser.
The internal query identifiers needed to page timelines are read from the same page on every run, so when X rotates them the Actor picks up the new ones by itself instead of failing silently. That repair path is plain parsing — deterministic, auditable, and with no language model anywhere in the data path, so your output cannot drift between runs.
FAQ
Do I need a Twitter/X API key? No. There is no key, no developer account, and no OAuth app to register.
Do I need to supply cookies or a logged-in session?
Not for profiles or single tweets. For full timelines, yes: X shows logged-out visitors only an account's latest few posts. Paste the auth_token and ct0 cookies of a logged-in session into cookies (secret input, stored encrypted, used only for timeline requests). Use a secondary account — X can rate-limit or lock accounts that read heavily.
Do I need proxies? No. Proxy configuration is not an input because the Actor does not need one.
Can I run this every 15 minutes and only get new tweets?
Yes. Turn on incremental. Each account stops at the newest tweet the previous run saw, so an account with nothing new costs one lookup and no tweets. See Incremental runs.
Can I filter by likes or views before paying?
Yes. minLikes, minViews and the other filters are applied before a tweet is written, and tweets that do not pass are not charged.
Can I scrape one tweet by its URL?
Yes — pass it in tweetUrls. $0.005 per tweet returned, with the author profile embedded.
How many tweets can I get from one account?
The timeline pages back roughly 15–20 tweets at a time and keeps going. Set maxTweetsPerProfile to whatever you need; use since when you only care about a recent window.
Can I get tweets older than the timeline goes back? X's own timeline is the ceiling. For deep historical archives you want a data provider, not a scraper.
What happens if X changes something? Query identifier rotation is handled automatically (see above). Structural changes are not, and would need an update — if output looks wrong, open an issue on the Actor and it gets fixed.
Is scraping X legal? Scraping publicly accessible pages has been treated differently from accessing a computer without authorization — see hiQ Labs v. LinkedIn, where the Ninth Circuit held that scraping public profiles did not violate the Computer Fraud and Abuse Act. That said, this is general background and not legal advice: platform terms, your jurisdiction, and what you do with the data all matter, and personal data carries its own obligations under GDPR and similar regimes. Get advice for your own situation.
Integrating
Every Apify Actor exposes a REST API, so you can start a run and read results from any language:
curl -X POST "https://api.apify.com/v2/acts/<ACTOR_ID>/run-sync-get-dataset-items?token=<TOKEN>" \-H 'Content-Type: application/json' \-d '{"twitterHandles":["nasa"],"maxTweetsPerProfile":50}'
Official clients exist for JavaScript and Python, and results can be exported as JSON, CSV, or Excel, or pushed onward through Apify integrations.