X Profile Posts Scraper - No Cookies
Pricing
from $1.20 / 1,000 post returneds
X Profile Posts Scraper - No Cookies
Scrape the public post timeline of any X (Twitter) account without cookies or an account. Per-account limits, working date filters, full engagement metrics. Same price on every Apify plan.
Pricing
from $1.20 / 1,000 post returneds
Rating
0.0
(0)
Developer
Renzo Madueno
Maintained by CommunityActor stats
0
Bookmarked
3
Total users
1
Monthly active users
12 days ago
Last modified
Categories
Share
Point it at an X (Twitter) account. Get that account's public posts as clean JSON — text, timestamps, likes, retweets, replies, quotes, bookmarks, media, links — with a real per-account limit and date filters that actually filter.
No cookies. No session tokens. No Twitter account. No browser.
Three things competing scrapers get wrong, and what this does instead
1. A per-account limit exists here. maxItemsPerTarget caps how many posts come from each account, and it defaults to 100. Pass twenty handles and the run cannot silently balloon because one of them posts forty times a day. The developer of the largest scraper in this category has stated in writing that limiting posts per account is not supported by their actor. It is supported here, it shipped on day one, and it has a safe default.
2. The price is identical on every Apify plan. $0.30 per 1,000 posts on the free plan and on every paid plan. There is no free-tier multiplier, no reduced cap for free users, and no invoice surprise waiting for anyone who tried the actor before subscribing.
3. Nothing empty is ever charged. A suspended account, a protected account, a handle that no longer exists, a date window that excludes everything — none of these produce a dataset row and none of them produce a charge. They produce an entry in the FAILURES record explaining exactly what happened.
The one honest caveat, up front
X serves two different timelines to logged-out clients, and which one you get depends on the account. This actor labels every row with timelineMode so you always know which you are looking at.
chronological — a real reverse-chronological timeline with pagination cursors. You can page back through history, and date filters work properly, including an early stop once the actor pages past your window. Measured on @nasa, @apify and @X: 14 to 20 posts per page, cursors present, strictly newest-first.
curated — for some very large accounts, X hands logged-out clients a fixed set of roughly 100 popular posts, not in date order, with no pagination cursor at all. Measured on @elonmusk (99 posts spanning 2018 to 2026, unsorted, no cursor) and @jack (100 posts spanning 2006 to 2025, unsorted, no cursor).
For a curated account there is no page two. Not at a higher price, not with more retries, not with a proxy — the cursor does not exist in the response. When this actor detects it, it logs a clear warning, marks the rows curated, and stops. It does not loop the same page to inflate a result count.
This is a restriction X imposes, not a limitation of this actor. Scrapers that appear to page deeply into these accounts are doing it with logged-in accounts. This one does not use any.
Input
{"handles": ["nasa", "@apify", "https://x.com/naval"],"maxItemsPerTarget": 50,"postedAfter": "30 days","includeRetweets": false}
| Field | Type | Default | What it does |
|---|---|---|---|
handles | array | — | Handles or profile URLs. Aliases: handle, usernames, username, urls, url, profileUrls, startUrls. |
maxItemsPerTarget | integer | 100 | Per-account ceiling. Aliases: maxTweetsPerProfile, maxPostsPerProfile, perTargetLimit. |
maxItems | integer | none | Ceiling across the whole run. Aliases: maxResults, limit, maxTweets, maxPosts. |
postedAfter | string | — | Only posts on or after this. Alias: since. |
postedBefore | string | — | Only posts on or before this. Alias: until. |
includeReplies | boolean | false | Include posts that reply to someone else. |
includeRetweets | boolean | true | Include retweets. |
elonmusk, @elonmusk and https://x.com/elonmusk are all accepted. So are profile URLs with /with_replies or /media on the end. Duplicates are collapsed before anything is fetched, so the same account listed twice costs once.
Dates accept YYYY-MM-DD, a full ISO timestamp, or a relative window like 30 days, 6 weeks, 2 years. An unreadable date raises an error naming the field and echoing what you passed.
Output
One row per post. Fill rates below are measured, from a verification run on 2026-08-22.
| Field | Fill | Notes |
|---|---|---|
id, url, text, createdAt, lang | 100% | createdAt is ISO 8601 UTC. |
likeCount, retweetCount, replyCount, quoteCount, bookmarkCount | 100% | The complete public metric set. |
conversationId | 100% | Thread root, for grouping. |
isReply, isQuote, isRetweet, isLongform | 100% | |
authorHandle, authorName, authorId, authorUrl, authorFollowers, authorIsBlueVerified | 100% | |
source | 100% | The client the post was made from. |
timelineMode | 100% | chronological or curated. Read the caveat above. |
queriedHandle | 100% | What you asked for, echoed back. |
media | ~21% | Array of {type, url, width, height, expandedUrl}. |
mentions | ~21% | |
possiblySensitive | ~29% | |
links | ~7% | Outbound URLs, already un-shortened from t.co. |
hashtags | varies | |
scrapedAt | 100% |
Long posts come back whole
X stores a long post twice: a truncated ~277-character stub in the legacy field, and the real text in a separate note_tweet field. A scraper that reads the obvious field hands you the stub with the rest silently missing — and long posts are exactly the ones worth reading.
This actor reads note_tweet and sets isLongform: true. Measured across real timelines on 2026-08-22: @ylecun has long-form on 42 of 100 sampled posts (one stub of 277 chars is really 2,434), @balajis 21 of 99, @pmarca one at 2,841 characters. If a competitor's export is full of posts that stop dead at 277 characters, this is the reason.
Data integrity, not just data presence
Counters are normalised before they reach you: abbreviated display values ("1.5M") become integers, and negative counts become null rather than being passed through — a 32-bit overflow on a huge account arrives as something like -576135159, which is not a measurement and would poison any average. Dates are parsed from X's own format into ISO 8601, and an unreadable one becomes null instead of a 1970 epoch date that would slip past your date filter. t.co shortlinks are resolved into links and media; text stays exactly as X returns it so it still matches what you see on screen.
View counts are not included. X does not send them to logged-out clients on any endpoint — the views object arrives with a state and no number. Rather than ship a field that is permanently null, or fill it with an estimate, this actor does not have one.
How it works
The actor makes the same two GraphQL calls x.com makes for a visitor who is not signed in:
UserByScreenNameto turn a handle into a numeric user ID, and to detect suspended, renamed and protected accounts before any paging begins.UserTweets, paged through theBottomcursor, 20 posts per request.
Authentication is a guest token from POST /1.1/guest/activate.json — free, accountless, and refreshed automatically inside the run whenever X rotates or rejects it. Retired GraphQL query IDs are rotated automatically too, so the actor keeps working across X's periodic renames.
No browser is launched. Memory footprint is 1 GB, so platform compute is negligible next to the per-result price.
Date filters that pull their weight
On a chronological account, postedAfter does two jobs. It drops out-of-range posts before they are stored or billed, and it stops paging once the actor reaches posts older than your cutoff. Asking for the last 30 days of a ten-year-old account fetches a few pages, not a decade.
On a curated account the filter still applies correctly to every row, but there is no paging to stop — X returned one unordered block and that is all there is.
If you pass postedAfter later than postedBefore, the run stops immediately with an error rather than grinding through your whole handle list to produce nothing.
Errors go where you can act on them
Nothing that failed is written into the dataset. Everything lands in the FAILURES key-value record:
{"actor": "X Profile Posts Scraper","targetsRequested": 3,"resultsDelivered": 10,"resultsCharged": 10,"failureCount": 1,"failures": [{ "target": "someprotectedaccount", "error": "Account is protected - X publishes nothing about it to logged-out clients" }],"notes": ["X served the non-paginated \"curated\" timeline for: elonmusk. ..."]}
A run that delivers nothing is marked FAILED, not quietly succeeded over an empty dataset. You are charged nothing either way.
Pricing
$0.30 per 1,000 posts. Pay per result. No subscription. Same price on every Apify plan.
| Posts | Cost |
|---|---|
| 100 | $0.03 |
| 1,000 | $0.30 |
| 10,000 | $3.00 |
| 100,000 | $30.00 |
Suspended accounts, protected accounts, unresolvable handles and filtered-out posts cost $0.00.
A realistic run: 500 competitor accounts at 100 posts each is 50,000 posts, or $15.00.
Typical uses
- Competitor content audits. Pull the last 90 days from every account in your category, sort by engagement, and see what actually lands.
- Brand and executive monitoring. Re-run daily against a fixed handle list and diff for new posts.
- Building a training corpus in a specific voice or domain, with per-account caps so no single account dominates the mix.
- Influencer vetting. Post cadence, engagement relative to follower count, how much is original versus retweeted.
- Newsroom and research monitoring of official accounts — agencies, regulators, exchanges — which are exactly the accounts that get the
chronologicaltreatment and page cleanly.
Limits worth knowing before you start
- No keyword search. This actor takes accounts, not queries. X blocks the search timeline for guests, and the scrapers offering it anyway are running pools of real accounts — the machinery behind most reliability complaints in this category. Not built here, not planned.
- Protected accounts return nothing. X publishes nothing about them publicly.
- Curated accounts cap at roughly 100 posts. See the caveat above. If you need deep history from
@elonmusk, no cookie-free scraper can give it to you, and this README says so instead of taking your money to find out. - Replies are excluded by default. Set
includeReplies: trueif you want them.
Verified
Every endpoint, field, fill rate and timeline behaviour described here was tested live against X on 2026-08-22 from a datacenter IP with no cookies and no account. The chronological versus curated split is a measurement, not a guess.
Related actors
- Tweet Scraper by URL or ID — individual tweets, $0.20/1k.
- X Profile Scraper - No Login — bulk handle-to-profile lookup, $0.25/1k.
- Tweet Replies Scraper - No Login — publicly visible replies, $0.30/1k.
- X Trends Scraper by Country — 62 countries and 400+ cities, $0.40/1k.
Same rules across all of them: one price on every plan, and nothing charged for nothing.