X (Twitter) Tweets Scraper — Any Account's Timeline
Pricing
from $0.34 / 1,000 results
X (Twitter) Tweets Scraper — Any Account's Timeline
Collect everything a public X account has posted, starting from nothing but its handle. Every post arrives as one clean row: what was said, when it was said, and how far it travelled. Several accounts in one run, and no X login, cookie or account anywhere in it.
Pricing
from $0.34 / 1,000 results
Rating
0.0
(0)
Developer
The Netaji
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
X (Twitter) Tweets Scraper
Collect everything a public X account has posted, starting from nothing but its handle. Every post comes back as one clean row: what was said, when it was said, and how far it travelled. The author's follower count rides along on each row, so reach arrives with the post instead of costing a second lookup per account.
No X login, cookie or session is involved at any point in a run. Nothing has to be connected, and no account of anyone's is put at risk to collect a timeline.
Several accounts can be collected in one run. Bare handles, @handles and pasted profile links are all accepted, and a single list may mix them.
Accepted input
handles is required and takes one or more accounts. maxItems caps how many rows are saved across
the whole run rather than per account, and defaults to 100; setting it to 0 removes the cap.
resumeCursor continues a previous run instead of starting at the top of a timeline.
enrichParentPost attaches the post that each reply is answering, and is off by default.
{"handles": ["nasa", "https://x.com/SpaceX"],"maxItems": 200}
Response fields
One row per post, newest first.
| Field | Contents |
|---|---|
tweet_id, tweet_url | The post's identifier, and a link to it |
text | The post body; is_retweet and is_long_post qualify it |
is_long_post | true when text is a long post's complete body rather than its first 280 characters |
posted_at, lang | Publish time in X's own date format, and X's language guess |
author_id, author_handle, author_name | The account that posted it |
author_followers_count | That account's follower count, on every row |
author_verified, author_is_blue_verified | Legacy check and current mark |
author_profile_image_url | The author's avatar |
like_count, retweet_count, reply_count, quote_count | Engagement counts |
bookmark_count, view_count | Bookmarks and impressions, both as numbers |
is_retweet, retweeted_tweet | Whether the row is a retweet, and the original in full when it is |
is_quote, quoted_tweet | Whether the post quotes another, and that post in full |
conversation_id, reply_to_tweet_id, reply_to_handle | Position within a thread |
entities | X's own hashtags, urls, user_mentions, symbols and media structure |
possibly_sensitive | X's sensitive-content flag |
requested_handle | The handle the run asked for, before X re-cased it |
reply_to_tweet | The post this row replies to; present only when enrichParentPost is on |
{"tweet_id": "2087966752512561302","tweet_url": "https://x.com/NASA/status/2087966752512561302","text": "Yesterday's total solar eclipse in parts of Europe – and the partial eclipse in parts of North America – gave us spectacular views. @NASAHQPhoto was on location to capture the sights. See more: https://t.co/IQ6yWeVbXl","posted_at": "Thu Aug 13 18:17:21 +0000 2026","lang": "en","author_handle": "NASA","author_followers_count": 92309471,"like_count": 16970,"reply_count": 258,"view_count": 1792803,"bookmark_count": 1180,"is_retweet": false,"requested_handle": "nasa"}
Questions that come up
Why did a run save fewer posts than maxItems?
X treats a requested page size as a hint rather than an instruction, and it decides when to stop
handing out pages. Over eight consecutive pages of one account, asking for twenty posts each time,
X delivered 12, 17, 18, 14, 16, 16, 16 and 13: 122 posts, zero duplicates, and not one page returning
the number asked for. A run therefore ends when X stops offering the next position, which can happen
before maxItems is reached. It never ends merely because a page came back thin; a walk that treated
a short page as the end would stop on page one and report twelve posts for an account holding tens of
thousands.
How far back into a timeline does a run reach?
Further than any number I am willing to print. The measured walk above reached 122 posts and was
still being offered another page, so it was never run to exhaustion; the depth is a floor rather than
a ceiling. The tweet_count field on an account's profile record is how many posts X states the
account has made, not how many a timeline walk reaches, and it should not be used as a budget for a
run.
Can a long export be picked up where it stopped?
Yes. A resume token is printed to the run log on every page that offers one, and supplying the last
one as resumeCursor continues from that position rather than re-walking the top of the timeline. A
token is a position within one account's stream and carries no meaning in another's, so it applies to
the first account in the list and is ignored for the rest; resuming is done one account at a time.
Why does text start with RT @ on some rows?
Because the row is a retweet, and that truncated form is X's own. The original post is published
whole on the same row under retweeted_tweet, with its own author, engagement counts and untruncated
text, so nothing is lost. Quote posts behave the same way: text is the commentary and
quoted_tweet is the post being commented on. Both halves are published rather than one being
substituted for the other, because they answer different questions: the outer record is what the
account did, and the nested one is what it amplified.
Are long posts cut off at 280 characters?
No. When is_long_post is true, text is the complete body. X's ordinary post field would return
a 4,000-character post as its first 280 characters, with no ellipsis and no marker, which is a
truncation that reads exactly like a complete post; that is the failure the flag exists to prevent.
Where are the images and videos on a post?
Inside entities, in X's own structure, alongside hashtags, urls, user_mentions and symbols,
each carrying the character offsets into text where it appears. Anything already written against a
Twitter payload reads those keys as they stand. There is deliberately no flattened column of media
links beside them: the shape varies by attachment type, and publishing one column would mean guessing
at key names I have not measured.
Can search results, the replies under a post, follower lists or a media tab be collected?
No, and not by any setting. Each of those needs a logged-in X account, and this Actor holds none.
That is the trade for an Actor that asks for no login, no cookie and no session, and it is a measured
boundary rather than something still to be built. reply_count still reports how many replies X
counts on a post, entities still carries a post's media, and a thread is still walkable upward:
reply_to_tweet_id is an ordinary post identifier.
What does enrichParentPost actually cost?
One extra request per reply, billed once per reply whose parent is returned. Rows that are not
replies are untouched and cost nothing, so the option is close to free on an account that mostly
posts and roughly doubles the request count on an account that mostly replies. A parent that has been
deleted, or that sits on a protected account, leaves the reply row unchanged and is not billed. The
same hop can be taken by hand afterwards, since reply_to_tweet_id is a plain post identifier; the
option exists to do it inside one run rather than two.
What happens to a handle that is wrong, retired, or protected?
A handle outside X's own rule of 1 to 15 letters, digits or underscores is reported in the run log
and skipped before a request is spent on it. A handle that does not resolve to any account is
likewise reported and skipped, because X answers an unknown account as a normal empty response rather
than as an error and there is nothing to retry. A protected account returns an empty timeline, since
this Actor holds no X account and follows nobody. In every case the remaining accounts in the list
are still collected. Duplicates are collapsed before any request is made, so nasa, @NASA and
https://x.com/nasa in one list are one account and one set of charges.
Why is view_count empty on some posts?
Because X published no view count for them, which is normal on older posts. Null means that no count
was stated and is never collapsed to 0, so an average over the column is not quietly dragged
towards zero by posts that simply predate the metric. Views and bookmarks are both numbers rather
than text, so sorting on them orders 54752 above 9 instead of the reverse.
Are missing fields dropped from a row?
No. Every row has the same shape, and a field absent upstream is null rather than omitted. The
three flags a reader has to branch on are the exception in the other direction: is_retweet,
is_quote and is_long_post are always true or false, never null, so a check on one never has
to handle a third case.
Which column joins back to the input list?
requested_handle. X returns handles in its own casing, so a run asking for nasa produces NASA in
author_handle; requested_handle echoes the value supplied, reduced from an @handle or a pasted
profile link, so a join never depends on two sides agreeing about normalisation.
If a field returns null where a value is clearly present on x.com, the Actor's Issues tab is the fastest way to reach me; a post identifier in the report is usually enough to reproduce it.
Related Actors
X (Twitter) Profile Scraper returns the accounts themselves rather than their posts, including the bio, join date, and the full set of counts X publishes. X (Twitter) Post Scraper takes post links directly and returns the same row shape as this Actor, which makes it the better choice once specific posts are known; for a whole timeline this Actor is the cheaper one, because a single request returns a page of posts rather than one.