Tweet Scraper by URL or ID
Pricing
from $0.20 / 1,000 tweet returneds
Tweet Scraper by URL or ID
Turn any tweet URL or ID into a full JSON record: text, likes, retweets, quotes, bookmarks, replies, media, author. No login, no cookies. Same price on every Apify plan and empty results are never charged.
Pricing
from $0.20 / 1,000 tweet returneds
Rating
0.0
(0)
Developer
Renzo Madueno
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
9 days ago
Last modified
Categories
Share
Give it a tweet URL or a tweet ID. Get back the whole tweet as clean JSON: text, timestamp, language, likes, retweets, replies, quotes, bookmarks, media, links, hashtags, mentions, and the author's public profile numbers.
One tweet or a hundred thousand. No login, no cookies, no browser, no account farm.
The two things that make this different
The price is the same on every Apify plan. $0.20 per 1,000 tweets on the free plan, on Starter, on Scale, on Business. There is no free-tier surcharge, no reduced result cap for free users, and no version of this actor that costs 100x more because of which plan you happen to be on. This sounds like it should go without saying. It does not: the largest Twitter scraper on this Store charges $0.40/1k on paid plans and $40/1k on the free plan, and roughly half of its one-star reviews are people who found that out from an invoice.
You are never charged for an empty result. If a tweet is deleted, if the account was suspended, if the ID is malformed, if your date filter excludes it — no row is written and no charge is made. There is no {"noResults": true} object padding your dataset. The run log tells you exactly how many results were suppressed and why.
Those two sentences are the entire product thesis. Everything below is detail.
Input
Paste tweets in whatever form you already have them:
{"urls": ["https://x.com/jack/status/20","https://twitter.com/elonmusk/status/1349129669258448897","1519480761749016577"]}
Full x.com URLs, legacy twitter.com URLs, /statuses/ URLs, URLs with tracking parameters, and bare numeric IDs all work. So does mixing them in one list.
The field is called urls, but url, tweetUrls, tweetUrl, ids, id, tweetIds, tweetId and startUrls are all accepted as aliases. You should not have to read documentation to find out what a field is called this week.
| Field | Type | What it does |
|---|---|---|
urls | array | Tweet URLs or IDs. Aliases: url, tweetUrls, ids, tweetIds, startUrls. |
maxItems | integer | Hard ceiling on delivered rows. Aliases: maxResults, limit, maxTweets. |
postedAfter | string | Only tweets posted on or after this. Alias: since. |
postedBefore | string | Only tweets posted on or before this. Alias: until. |
preferSyndication | boolean | Advanced. Try the token-free endpoint first. Off by default. |
Dates accept YYYY-MM-DD, a full ISO timestamp, or a relative window like 7 days, 3 weeks, 6 months. An unparseable date raises an error naming the field and showing what you passed, rather than being silently dropped.
A single tweet is allowed
You can run this on exactly one tweet. There is no minimum batch, no clause anywhere telling you that retrieving a single tweet is "strictly forbidden", and nothing in the code that treats a one-item run as abuse. A one-tweet run costs $0.0002.
Output
One row per tweet. Every field below was observed in live responses, and the percentage is the measured fill rate across a verification sample run on 2026-08-22.
| Field | Fill | Notes |
|---|---|---|
id, url, text, createdAt, lang | 100% | createdAt is ISO 8601 UTC. |
likeCount | 100% | |
retweetCount, quoteCount, bookmarkCount | 100% | From the primary endpoint. See the honesty note below. |
replyCount | 100% | |
conversationId | 100% | The thread root, useful for grouping. |
isReply, isQuote, isRetweet, isLongform | 100% | Booleans, always present. |
authorHandle, authorName, authorId, authorUrl | 100% | |
authorFollowers, authorIsBlueVerified | 100% | |
authorProfileImageUrl | 100% | |
source | 100% | "Twitter for iPhone", "TweetDeck Web App", etc. |
media | ~40% | Array of {type, url, width, height, expandedUrl}. Empty when the tweet has no attachments. |
possiblySensitive | ~40% | |
inReplyToTweetId, inReplyToHandle | ~20% | Present only on replies, by definition. |
hashtags, mentions, links | varies | Arrays. Empty when the tweet contains none. |
dataSource | 100% | graphql or syndication. Tells you which surface answered. |
requestedId, scrapedAt | 100% | Your input echoed back, and the fetch time. |
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.
This one reads note_tweet and flags the row with isLongform: true. Measured on 2026-08-22: a post by @pmarca whose stub is 277 characters is delivered here at its full 2,841 characters; one by @ylecun at 2,434. If your dataset from another tool is full of posts that stop at 277 characters, this is why.
Data integrity, not just data presence
A field arriving is not the same as a field being right, so the counters are normalised before they reach you:
- Abbreviated display values (
"1.5M","428.9K") are converted to integers, so a column never mixes strings and numbers. - Negative counts are returned as
null. A 32-bit overflow on a very large account shows up as a negative like-576135159. That is not a measurement, and passing it through would silently poison any average computed over the column. - Dates are parsed from X's own format (
Wed Oct 10 20:19:24 +0000 2018) into ISO 8601. A value that cannot be read becomesnull, never a 1970 epoch date that would quietly slip through your date filter. t.coshortlinks are resolved:linksholds the real destination URLs andmediaholds the real media URLs. Thetextfield is left exactly as X returns it, shortlinks included, so it still matches the tweet you see on screen.
What is deliberately absent
View counts. X does not send view counts to logged-out clients on any endpoint. The views object comes back as {"state": "Enabled"} with no number in it. This actor therefore does not have a viewCount field at all, rather than shipping one that is always null or, worse, one filled with an estimate. If you need real view counts, you need the official paid X API.
Email addresses and phone numbers. X does not publish them. Nobody scraping X can produce them honestly.
How it works
Two public surfaces, tried in order:
-
GraphQL
TweetResultByRestIdwith a guest token. This is the same call x.com makes for a visitor who is not signed in. A guest token comes fromPOST /1.1/guest/activate.json, costs nothing, requires no account, and is refreshed automatically inside the run whenever X rotates or rejects it. This path carries the full metric set. -
cdn.syndication.twimg.com/tweet-result, the endpoint behind embedded tweets. It needs no token whatsoever, which makes it the resilient fallback when the guest-token path is being rate limited. It publishes likes and reply counts but not retweet, quote or bookmark counts.
When a row comes from the fallback, dataSource is "syndication" and retweetCount, quoteCount and bookmarkCount are null — not 0. A zero would look like a real measurement and would quietly corrupt any average you compute. A null says "not available from this surface", which is the truth. The run log reports how many rows came from which source.
The actor uses no browser, no proxy by default, and no Twitter account. Memory footprint is 512 MB, so compute cost is close to nothing next to the per-result price.
Errors go where you can act on them
Failed inputs are never written into the dataset. They go into a key-value record called FAILURES, alongside a summary of the run:
{"actor": "Tweet Scraper by URL or ID","targetsRequested": 4,"resultsDelivered": 2,"resultsCharged": 2,"emptyRowsSuppressed": 0,"failureCount": 2,"failures": [{ "target": "total nonsense", "error": "Not a tweet URL or numeric tweet ID" },{ "target": "1613611384712065024", "error": "Tweet is deleted, private, or from a suspended account - X returns nothing for it" }],"notes": ["1 tweet(s) no longer exist publicly. They were not charged."]}
Your dataset stays clean enough to load straight into a database without filtering out sentinel objects first.
A run that delivers nothing is marked FAILED. If every tweet you asked for is deleted, or your date window excludes all of them, the run does not finish green over an empty dataset. It fails, with a message naming the cause. You are still charged nothing.
Date filters actually filter
postedAfter and postedBefore are applied to every row before it is written and before it is billed. A tweet outside the window is not stored and not charged, and the count of excluded tweets appears in the run notes.
If you pass a window that cannot contain anything — postedAfter later than postedBefore — the run stops immediately with an explicit error rather than working through your whole input list to produce nothing.
Pricing
$0.20 per 1,000 tweets. Pay per result. No monthly subscription. No platform usage fee on top. Identical on every Apify plan.
| Tweets | Cost |
|---|---|
| 1 | $0.0002 |
| 1,000 | $0.20 |
| 10,000 | $2.00 |
| 100,000 | $20.00 |
Deleted tweets, suspended accounts, malformed inputs and date-filtered rows all cost $0.00.
Typical uses
- Reconstruct a link dump. You have 40,000 tweet URLs collected from newsletters, Slack, a research corpus or someone else's dataset, and you need the actual content and engagement behind them.
- Track a specific set of posts over time. Re-run the same ID list daily and diff the metrics to build an engagement curve.
- Rehydrate an academic dataset. Research corpora are distributed as bare tweet IDs for compliance reasons. This turns them back into tweets, and tells you honestly which ones have since been deleted rather than silently dropping them.
- Verify a screenshot. One URL in, the real numbers out.
- Feed a pipeline. Chain it after any actor that produces tweet links.
Limits worth knowing before you start
- No keyword search. This actor takes URLs and IDs. It does not search X by keyword or hashtag, because X blocks the search timeline for guests entirely, and the scrapers that offer it anyway are running pools of real accounts to get around that. That is the machinery behind most of the reliability complaints in this category. This actor does not have it and will not grow it.
- Protected accounts return nothing. X publishes nothing about them to logged-out clients. They land in
FAILURES. - Very large parallel runs may see the guest-token path rate limited. The actor renews tokens automatically and falls back to the token-free surface, but if you are pushing hundreds of thousands of tweets, set
preferSyndicationand accept null retweet/quote/bookmark counts, or spread the work across runs.
Verified
Every endpoint, field and fill rate in this README was tested live against X on 2026-08-22 from a datacenter IP with no cookies and no account. Nothing here is copied from documentation that may have gone stale.
Related actors
- X Profile Posts Scraper - No Cookies — a whole account's timeline, with a real per-account limit.
- X Profile Scraper - No Login — bulk handle-to-profile lookup.
- Tweet Replies Scraper - No Login — the replies X shows publicly on a tweet.
- X Trends Scraper by Country — trending topics for 62 countries and 400+ cities.
Same pricing philosophy across all of them: one price on every plan, and nothing charged for nothing.