X (Twitter) Scraper - Tweets, Threads & Engagement Rate
Pricing
from $9.30 / 1,000 results
X (Twitter) Scraper - Tweets, Threads & Engagement Rate
Scrape tweets from any X (Twitter) profile or tweet URL, no official API needed. Get text, engagement metrics, media, hashtags & author data as JSON, CSV or Excel.
Pricing
from $9.30 / 1,000 results
Rating
0.0
(0)
Developer
Techforce Global
Maintained by CommunityActor stats
0
Bookmarked
4
Total users
3
Monthly active users
a day ago
Last modified
Categories
Share
X / Twitter Scraper - Profile Timelines and Single Tweets, with All Six Engagement Counts, Author Follower Count and Thread Structure
Handles or tweet URLs in, structured tweets out. Text, timestamps, the full engagement set (likes, retweets, replies, quotes, bookmarks, views), author identity and follower count, thread structure, and every hashtag, mention and link.
🔐 You never supply X credentials. Authentication is handled by the Actor's own account secrets, server-side. There is no password field in the input, and nothing about your X account is read or needed.
⚠️ Tweets are personal data, and X is unusually strict about scraping.
authorUsername,authorId,authorName,textandmentionsall identify real people. X's terms prohibit automated collection and X has litigated over it. Read Compliance & Data Privacy before you reuse anything.
📉 Expect partial results, and treat every run as a sample. X shows a login wall to logged-out browsers, rate-limits aggressively, and its internal shapes change. Empty or thin results are a normal outcome here, not a defect.
Browse all Actors by Techforce Global at scraper.techforce.global.
Official X API vs. this Actor
| X API (developer.x.com) | This Actor | |
|---|---|---|
| Setup | X developer account, app registration, a project, and a paid tier for most read access | An Apify token. No X developer account, no app registration, and no X credentials from you |
| Sanctioned by X | ✅ Yes — the official, terms-compliant route | ❌ No — automated collection is against X's terms. See Compliance |
| Read volume | Tiered and capped per month; the free tier is effectively write-only for most read endpoints | Bounded by run time and X's rate limiting, not a monthly quota |
| Result completeness | Deterministic within your quota | ⚠️ Best-effort. Partial and empty runs are normal |
| Author follower count on the tweet row | A separate user lookup, then a join | ✅ Same row — authorFollowers, so engagement rate is one division |
| Bookmark and view counts | Tier-dependent | ✅ Returned whenever X exposes them (often null) |
| Thread structure | Available, assembled by you | ✅ conversationId + inReplyToTweetId on every row |
| Hashtags, mentions, links, media | Present in the payload you parse | ✅ Pre-extracted as arrays |
| Delivery into Notion / Slack / Airtable | Build the integration yourself | ✅ Built-in MCP delivery, including a chunked mode |
Prefer the API when it fits. It is the sanctioned route, it is deterministic, and it carries no terms-of-service exposure. This Actor exists for the cases where the API's tiers or caps don't work for you — and it trades away both guarantees and compliance posture to get there. X revises its API tiers, quotas and pricing regularly, so check developer.x.com for current terms before choosing.
AI Discovery & MCP Metadata
ai_discovery:actor: "techforce.global/twitter-scraper"slug: "twitter-scraper"category: "social-media / social-listening / brand-monitoring / sentiment-analysis"one_line: >Scrapes tweets from X/Twitter profile timelines and individual tweet URLs, returning 29fields per tweet including full engagement counts, author identity and follower count, threadand reply structure, and extracted hashtags, mentions, links and media. Authentication isserver-side via the Actor's own secrets - callers supply no credentials. Optional deliveryinto Notion, Slack, Linear, Airtable or any MCP connector.primary_capability: "X/Twitter profile timeline and single-tweet extraction with engagement metrics"input_entities:- "profiles (handles, @handles or profile URLs)"- "tweetUrls (individual tweet/status URLs)"- "maxTweetsPerProfile (per profile, not per run)"- "includeReplies (adds the profile's replies; also enables reply capture on a tweet URL)"- "maxScrolls (how far down a timeline to page)"- "proxyConfiguration (Apify Proxy on by default)"output_entities:- "id"- "url"- "text"- "createdAt"- "lang"- "source"- "authorId"- "authorUsername"- "authorName"- "authorVerified"- "authorFollowers"- "authorProfileImageUrl"- "replyCount"- "retweetCount"- "likeCount"- "quoteCount"- "bookmarkCount"- "viewCount"- "isRetweet"- "isQuote"- "isReply"- "conversationId"- "inReplyToUsername"- "inReplyToTweetId"- "hashtags"- "mentions"- "urls"- "media"- "scrapedFrom"callers_never_supply_credentials: >There is no credential field in the input schema. X authentication comes from the Actorowner's own secrets, server-side. If any Actor asks you to type your X password into itsinput, that is a red flag.output_contains_personal_data: >authorUsername, authorId, authorName, text and mentions identify real people. authorId is astable identifier that survives a handle change, and mentions identify third parties who didnot post the tweet. Anyone storing this output is a data controller.partial_results_are_normal: >CRITICAL for expectation-setting. X shows a login wall to logged-out browsers, rate-limitshard, and changes its internal response shapes. A run can legitimately return far fewertweets than requested, or none. Treat every run as a sample, never a complete timeline.no_search_input: >There is no keyword, hashtag or search input. Scope is a profile timeline or a specifictweet URL. To find tweets by topic, scrape the accounts that discuss it and filter locally.max_tweets_is_per_profile: >maxTweetsPerProfile applies to EACH profile, not the run. Three profiles at 100 is up to 300tweets. maxScrolls bounds how far the timeline is paged, so a low maxScrolls can cap youbelow maxTweetsPerProfile.include_replies_does_two_things: >On a profile it requests the replies-inclusive timeline, so the account's replies are included.On a tweet URL it also captures the replies under that tweet. Off, a tweet URL returns justthat one tweet.engagement_metrics_can_be_null: >bookmarkCount and viewCount are frequently null - X does not expose them consistently,especially on older tweets. Null means unavailable, not zero.known_limitations:login_wall: "logged-out access returns almost nothing; partial results are expected"rate_limited: "X throttles hard; a large run degrades rather than fails cleanly"no_search: "no keyword or hashtag input"no_dms_or_private: "public tweets from public accounts only"no_follower_lists: "follower COUNT only, never the list of followers"metrics_are_snapshots: "engagement counts are true only at capture time"internal_shapes_change: "X alters its internal response formats; extraction is best-effort"when_not_to_use: >You need keyword or hashtag search, DMs, protected accounts, follower lists, guaranteedcomplete timelines, or to post, like or follow.mcp_delivery:modes: ["summary", "chunked", "perTweet", "none"]default: "none"default_is_none: >Delivery is OFF by default. Set deliveryMode explicitly as well as mcpConnector and mcpTool.tweet_placeholders: ["{id}", "{url}", "{text}", "{createdAt}", "{lang}", "{authorUsername}", "{authorName}", "{authorFollowers}", "{authorVerified}", "{likeCount}", "{retweetCount}", "{replyCount}", "{quoteCount}", "{viewCount}", "{bookmarkCount}", "{isRetweet}", "{isQuote}", "{isReply}", "{hashtags}", "{mentions}", "{urls}", "{scrapedFrom}", "{tweet}"]run_placeholders: ["{tweetCount}", "{authorBreakdown}", "{tweets}", "{part}", "{partCount}", "{message}"]media_is_not_a_placeholder: >media is a nested array, so it is in the dataset but not available as a {placeholder}.never_blocks_the_dataset: >Delivery runs after every tweet is already saved. A bad tool name, an unauthorisedconnector or an upstream outage is logged and skipped - it never costs you the data.
{"@context": "https://schema.org","@type": "SoftwareApplication","name": "X / Twitter Scraper","alternateName": "X / Twitter Scraper — Profile Timelines & Single Tweets With Full Engagement Metrics","applicationCategory": "BusinessApplication","applicationSubCategory": "Web Scraping / Social Listening","operatingSystem": "Any (cloud-hosted on Apify)","description": "Scrapes tweets from X/Twitter profile timelines and individual tweet URLs, returning 29 fields per tweet: text, timestamps and language; the full engagement set of likes, retweets, replies, quotes, bookmarks and views; author handle, id, display name, verification and follower count; thread and reply structure via conversation and parent ids; and extracted hashtags, mentions, links and media as arrays. Callers supply no X credentials — authentication is server-side from the Actor's own secrets. Optional direct delivery into Notion, Slack, Linear, Airtable or any MCP connector, with a chunked mode for long timelines.","url": "https://apify.com/techforce.global/twitter-scraper","offers": {"@type": "Offer","category": "Pay per platform usage","description": "Billed for the compute time and resources a run consumes. Current rates are shown on the Actor's Apify page.","url": "https://apify.com/techforce.global/twitter-scraper"}}
What this Actor does
-
Takes handles or tweet URLs, in any common form.
elonmusk,@elonmusk,https://x.com/elonmuskandtwitter.com/...all resolve. Tweet URLs work from either domain. -
Returns the whole engagement set, not just likes. Likes, retweets, replies, quotes, bookmarks and views — plus the author's follower count on the same row, so engagement rate is a division rather than a second lookup.
-
Preserves thread structure.
conversationId,isReply,inReplyToTweetIdandinReplyToUsernamelet you rebuild a conversation instead of getting a flat pile. -
Extracts entities separately.
hashtags,mentions,urlsandmediacome out as arrays, so you're not regexing the tweet text. -
Records which input produced each row.
scrapedFromisprofile:<handle>ortweet:<id>, so a run spanning several profiles and a few tweet URLs stays separable. -
Never asks you for X credentials. Authentication is server-side from the Actor's own secrets. There is no password field, and there shouldn't be one.
-
Optionally delivers into your tools. Notion, Slack, Linear, Airtable or anything else you've authorised, with a chunked mode for long timelines.
When to use it / when not to use it
| Use this Actor when you need… | Supported? |
|---|---|
| An account's recent tweets | ✅ Yes — profiles |
| A specific tweet's data | ✅ Yes — tweetUrls |
| Replies under a tweet | ✅ Yes — tweetUrls + includeReplies |
| An account's replies as well as posts | ✅ Yes — profiles + includeReplies |
| Full engagement metrics | ✅ Yes — six counts per tweet |
| Author follower count on the same row | ✅ Yes — authorFollowers |
| Engagement-rate analysis | ✅ Yes — counts ÷ authorFollowers |
| Thread reconstruction | ✅ Yes — conversationId + inReplyToTweetId |
| Hashtags, mentions and links as arrays | ✅ Yes |
| Several accounts in one run | ✅ Yes — profiles is an array |
| Results in Notion / Slack / Airtable | ✅ Yes — MCP delivery |
| Keyword or hashtag search | ❌ No search input — scrape accounts, filter locally |
| A complete timeline, guaranteed | ❌ Best-effort; X rate-limits and walls |
| Protected / private accounts | ❌ Public accounts only |
| DMs | ❌ Never |
| Follower or following lists | ❌ Counts only |
| Historical tweets beyond what the timeline serves | ❌ Only what X paginates to |
| Posting, liking, following or any account action | ❌ Read-only |
| Deleted tweets | ❌ Not recoverable |
Execution Rules — Avoid Common Agent Failures
-
Supply
profiles,tweetUrls, or both. With neither, the run logs an error and exits. Neither is marked required, so validation won't catch it. -
There is no search input. No keywords, no hashtags. To cover a topic, scrape the accounts that discuss it and filter
textorhashtagsyourself. -
Partial or empty results are a normal outcome. X walls logged-out browsers, throttles hard, and changes its internal shapes. Never treat a run as a complete timeline. The log distinguishes the likely causes — read it before retrying.
-
maxTweetsPerProfileis per profile. Three profiles at100is up to 300 tweets. -
maxScrollscan cap you belowmaxTweetsPerProfile. It bounds how far the timeline is paged. If you ask for 500 tweets withmaxScrolls: 5, the scroll limit wins. Raise both together. -
includeRepliesdoes two different things. On a profile it switches to the with-replies timeline, so the account's own replies appear. On a tweet URL it also collects the replies beneath that tweet. Off, a tweet URL returns just the one tweet. -
bookmarkCountandviewCountare frequentlynull. X doesn't expose them consistently, especially on older tweets. Null means unavailable, not zero — exclude nulls from averages. -
Engagement counts are a snapshot. They were true at capture time and keep moving. For a growth curve, schedule the Actor and join runs on
id. -
A retweet is not original content. Filter
isRetweet: falsebefore measuring what an account actually says, or you'll attribute someone else's words and metrics to them. -
mentionsidentifies third parties. Those handles belong to people who didn't post the tweet. Treat that array with the same care as the author fields. -
mediais a nested array, so it's in the dataset but not available as a delivery placeholder. Use{urls}for links. -
deliveryModedefaults tonone. Delivery is off until you set it explicitly and providemcpConnectorandmcpTool. -
Keep the proxy enabled. It defaults to Apify Proxy. Direct platform IPs make the login wall and rate limits worse.
Input Contract
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
profiles | array<string> | see rule 1 | — | Handles, @handles or profile URLs. Reserved paths like home and explore are rejected. |
tweetUrls | array<string> | see rule 1 | — | Individual tweet/status URLs, x.com or twitter.com. |
maxTweetsPerProfile | integer | no | 100 | Tweets per profile. |
includeReplies | boolean | no | false | Profile: include the account's replies. Tweet URL: also collect replies under it. |
maxScrolls | integer | no | 25 | How far to page a timeline. Can cap you below maxTweetsPerProfile. |
proxyConfiguration | object | no | {"useApifyProxy": true} | Leave enabled. |
No credential fields. X authentication is server-side from the Actor's own secrets. You are never asked for your X email, password or tokens.
Delivery (optional)
| Parameter | Type | Default | Description |
|---|---|---|---|
mcpConnector | string | — | MCP connector to deliver into. The Console offers a picker of connectors you've authorised. |
mcpTool | string | "" | Tool to call — e.g. create_page, send_message. Required when a connector is set. |
deliveryMode | string | "none" | summary, chunked, perTweet, none. Off by default. |
mcpArguments | object | {} | Arguments for the tool. {placeholder} tokens substituted per call. |
mcpMessageTemplate | string | "" | Text rendered into {message}. |
Delivery placeholders
| Scope | Placeholders |
|---|---|
Per tweet (perTweet) | {id} {url} {text} {createdAt} {lang} {authorUsername} {authorName} {authorFollowers} {authorVerified} {likeCount} {retweetCount} {replyCount} {quoteCount} {viewCount} {bookmarkCount} {isRetweet} {isQuote} {isReply} {hashtags} {mentions} {urls} {scrapedFrom} {tweet} |
| Per run (all modes) | {tweetCount} {authorBreakdown} {tweets} {message} |
| Chunked only | {part} {partCount} |
{tweet} is a pre-formatted block for one tweet; {tweets} is the same for the whole set (or the
current chunk). {authorBreakdown} renders as @alice: 42, @bob: 7. media is not a
placeholder — it's a nested array.
Output Contract
One dataset item per tweet.
Tweet
| Field | Type | Nullable | Meaning |
|---|---|---|---|
id | string | yes | Tweet id. The stable key for joining runs. |
url | string | yes | Canonical link to the tweet. |
text | string | yes | Tweet body. Personal data — user-authored. |
createdAt | string | yes | When it was posted, as X reports it. |
lang | string | yes | Language code X assigned. |
source | string | yes | Posting client, where X still exposes it. |
scrapedFrom | string | yes | profile:<handle> or tweet:<id> — which input produced the row. |
Author
| Field | Type | Nullable | Meaning |
|---|---|---|---|
authorUsername | string | yes | @handle. Personal data. |
authorId | string | yes | Numeric account id. Personal data — stable, and it survives a handle change. |
authorName | string | yes | Display name, often a real name. Personal data. |
authorVerified | boolean | yes | Verification badge. |
authorFollowers | integer | yes | Follower count at capture time. |
authorProfileImageUrl | string | yes | Avatar URL. |
Engagement — all snapshots
| Field | Type | Nullable | Meaning |
|---|---|---|---|
likeCount | integer | yes | Likes. |
retweetCount | integer | yes | Retweets. |
replyCount | integer | yes | Replies. |
quoteCount | integer | yes | Quote tweets. |
bookmarkCount | integer | yes | Frequently null — not always exposed. |
viewCount | integer | yes | Frequently null, especially on older tweets. |
Structure and entities
| Field | Type | Nullable | Meaning |
|---|---|---|---|
isRetweet | boolean | yes | Filter this out to measure original content. |
isQuote | boolean | yes | Quotes another tweet. |
isReply | boolean | yes | Is a reply. Populated on profiles only with includeReplies. |
conversationId | string | yes | Thread id — group on this to rebuild a conversation. |
inReplyToUsername | string | yes | Handle being replied to. Personal data. |
inReplyToTweetId | string | yes | Parent tweet id. |
hashtags | array | no | Hashtags. [] when none. |
mentions | array | no | Handles mentioned. Personal data — third parties. |
urls | array | no | Expanded outbound links. |
media | array | no | Attached media descriptors. Nested — not a delivery placeholder. |
Missing values are genuine
null(or[]for arrays), not"N/A".
Example output object
{"id": "1900000000000000000","url": "https://x.com/exampledev/status/1900000000000000000","text": "Rewrote our ingestion in Rust. p99 went from 840ms to 62ms. Write-up in the thread 🧵","createdAt": "Wed Aug 19 09:41:12 +0000 2026","lang": "en","source": "Twitter Web App","authorId": "1234567890","authorUsername": "exampledev","authorName": "Example Dev","authorVerified": false,"authorFollowers": 18422,"authorProfileImageUrl": "https://pbs.twimg.com/profile_images/<image-path>.jpg","replyCount": 47,"retweetCount": 212,"likeCount": 1884,"quoteCount": 19,"bookmarkCount": 604,"viewCount": 142903,"isRetweet": false,"isQuote": false,"isReply": false,"conversationId": "1900000000000000000","inReplyToUsername": null,"inReplyToTweetId": null,"hashtags": ["rustlang"],"mentions": [],"urls": [],"media": [],"scrapedFrom": "profile:exampledev"}
Values illustrate shape and formatting — the account and tweet are invented. In a real run the author fields identify an actual person, which is why the compliance section matters.
What a successful run looks like
- Status:
SUCCEEDED - Dataset: one item per tweet, pushed as each profile or tweet page finishes
- Log: the auth-cookie count up front, then a per-source line with how many tweets were pushed — and an explicit warning naming the likely cause when a source yields nothing
- Zero results is also a success. A protected account, an expired session, a rate limit, or a
deleted tweet all return an empty dataset with
SUCCEEDED. The log warning tells you which — it distinguishes "protected/empty" from "cookies missing or expired".
Dataset views
| View | Shows |
|---|---|
| Tweets | Author, text, timestamp and the headline engagement counts |
| Engagement | All six counts plus authorFollowers — for reach and rate analysis |
| Threads & Replies | Conversation id, reply and quote structure |
| Hashtags, Mentions & Links | The entity arrays, plus which input produced the row |
Invocation & Integration
Apify Console
Open the Actor page, add handles or tweet URLs, leave the proxy on, click Start.
MCP — add as an agent tool
$claude mcp add apify --transport http https://mcp.apify.com/?actors=techforce.global/twitter-scraper
An agent must supply handles or tweet URLs. If a user asks "what's being said about X", the agent needs to pick accounts to scrape — there is no keyword search.
Python
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("techforce.global/twitter-scraper").call(run_input={"profiles": ["exampledev", "@anotherdev"],"maxTweetsPerProfile": 100, # per profile -> up to 200"maxScrolls": 30, # raise with maxTweetsPerProfile, or this caps you"includeReplies": False,"proxyConfiguration": {"useApifyProxy": True},})tweets = list(client.dataset(run["defaultDatasetId"]).iterate_items())# Check coverage first — partial results are normal on X.from collections import Counterprint(Counter(t["scrapedFrom"] for t in tweets))# Original content only; nulls excluded, not coerced to zero.original = [t for t in tweets if not t.get("isRetweet")]rated = [t for t in originalif t.get("likeCount") is not None and (t.get("authorFollowers") or 0) > 0]for t in sorted(rated, key=lambda t: t["likeCount"] / t["authorFollowers"], reverse=True)[:5]:rate = t["likeCount"] / t["authorFollowers"]print(f'{rate:6.2%} @{t["authorUsername"]} {t["text"][:60]}')
JavaScript / TypeScript
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });const run = await client.actor('techforce.global/twitter-scraper').call({tweetUrls: ['https://x.com/exampledev/status/1900000000000000000'],includeReplies: true, // also collect the replies under itmaxScrolls: 25,proxyConfiguration: { useApifyProxy: true },});const { items } = await client.dataset(run.defaultDatasetId).listItems();// Rebuild the conversation from conversationId + inReplyToTweetId.const byConversation = new Map();for (const t of items) {if (!byConversation.has(t.conversationId)) byConversation.set(t.conversationId, []);byConversation.get(t.conversationId).push(t);}for (const [id, thread] of byConversation) {console.log(`\nthread ${id} — ${thread.length} tweets`);for (const t of thread.filter((t) => !t.isReply)) console.log(` OP @${t.authorUsername}: ${t.text?.slice(0, 60)}`);for (const t of thread.filter((t) => t.isReply)) console.log(` ↳ @${t.authorUsername}: ${t.text?.slice(0, 60)}`);}
cURL — synchronous (waits, returns the dataset)
curl -X POST "https://api.apify.com/v2/acts/techforce.global~twitter-scraper/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \-H 'Content-Type: application/json' \-d '{"profiles": ["exampledev"],"maxTweetsPerProfile": 50,"maxScrolls": 15,"proxyConfiguration": { "useApifyProxy": true }}'
cURL — asynchronous with a spend cap
curl -X POST "https://api.apify.com/v2/acts/techforce.global~twitter-scraper/runs?token=<YOUR_APIFY_TOKEN>&maxTotalChargeUsd=3" \-H 'Content-Type: application/json' \-d '{ "profiles": ["a","b","c","d"], "maxTweetsPerProfile": 200, "maxScrolls": 50 }'
maxTotalChargeUsd is the ceiling worth having — timeline scrolling behind a proxy is the least
predictable part of the run.
One thread, in full
{"tweetUrls": ["https://x.com/exampledev/status/1900000000000000000"],"includeReplies": true,"maxScrolls": 40,"proxyConfiguration": { "useApifyProxy": true }}
includeReplies plus a generous maxScrolls is how you get a whole conversation. Group the
result on conversationId and order with inReplyToTweetId.
With MCP delivery — a timeline digest into Slack
{"profiles": ["exampledev"],"maxTweetsPerProfile": 25,"proxyConfiguration": { "useApifyProxy": true },"mcpConnector": "slack","mcpTool": "send_message","deliveryMode": "summary","mcpArguments": {"channel": "#social-watch","text": "{tweetCount} tweets ({authorBreakdown}):\n\n{tweets}"}}
deliveryModemust be set explicitly — it defaults tonone. Usechunkedfor long timelines;perTweeton a 200-tweet run is 200 connector calls.
Scheduling for engagement tracking
Engagement counts move constantly, so one run is a single point. Put a fixed input on a
schedule and join runs on id — that gives you a
growth curve per tweet, which X doesn't publish.
Autonomous Agent Error Handling & Resolution Matrix
Each row is a condition observable from the run's status, log or dataset.
| Condition | What you see | Cause | Deterministic action |
|---|---|---|---|
NO_SOURCES | Log: no valid profiles or tweet URLs; run exits | Both inputs empty, or every value invalid | Modify Input: supply a valid handle or tweet URL. |
INVALID_HANDLE_SKIPPED | Log: skipping invalid profile input | A reserved path (home, explore, i) or a malformed handle | Modify Input: use a real handle. |
INVALID_TWEET_URL_SKIPPED | Log: skipping invalid tweet URL | Not a /status/<id> URL | Modify Input: use a full tweet URL. |
NO_AUTH_COOKIES | Log: no usable authentication cookies | The Actor's session secrets are unset or expired | Not caller-fixable. Report it — the operator must refresh the Actor's credentials. |
PROFILE_EMPTY_OR_WALLED | Log names the account and says protected/empty or cookies missing/expired | Protected account, empty timeline, or an expired session | Read which cause the log names. Protected → stop. Session → report to the operator. |
PARTIAL_TIMELINE | Fewer tweets than maxTweetsPerProfile | Rate limiting, maxScrolls reached, or the timeline ran out | Expected. Raise maxScrolls, or accept the sample. |
SCROLL_LIMIT_BOUND_FIRST | Consistently short results across profiles | maxScrolls too low for the tweet target | Modify Input: raise maxScrolls alongside maxTweetsPerProfile. |
ZERO_RESULTS | SUCCEEDED, empty dataset | Any of the above | Read the log first, then retry once. Don't loop. |
NULL_VIEW_OR_BOOKMARK_COUNT | viewCount / bookmarkCount null | X doesn't expose them consistently | Expected. Null ≠ zero. Exclude from averages. |
RETWEETS_SKEWING_ANALYSIS | Metrics attributed to the wrong author | Retweets counted as original content | Fix Consumer: filter isRetweet: false. |
NO_REPLY_FIELDS | isReply / inReplyToTweetId unpopulated | includeReplies is false | Modify Input: enable it. |
DELETED_TWEET | Log: tweet may be deleted or protected | The tweet no longer exists | Expected. Not recoverable. |
MEDIA_PLACEHOLDER_EMPTY | {media} renders empty in a connector call | It's a nested array, not a placeholder | Modify Input: use {urls}. |
CONNECTOR_NOT_DELIVERING | Connector set, nothing delivered, no error | deliveryMode still none (the default) | Modify Input: set it explicitly. |
CONNECTOR_RATE_LIMITED | Connector errors partway through | perTweet on a large run | Modify Input: switch to chunked or summary. |
CONNECTOR_TOOL_NOT_FOUND | Log: tool not available, followed by the available tools | mcpTool name is wrong | Modify Input: copy an exact name from that log line. Dataset unaffected. |
CONNECTOR_UNREACHABLE | Log: could not reach the connector | Connector not authorised, or running outside the Apify platform | Authorise it in Apify, or set deliveryMode: "none". Data already saved. |
Verify any platform-level HTTP status behaviour against the Apify API reference before relying on it in automation — the rows above describe this Actor's observable behaviour, not Apify's transport layer.
Natural Language → Actor Mapping
| A user says… | Intent | Input |
|---|---|---|
| "Get this account's recent tweets" | Profile | {"profiles":["handle"],"maxTweetsPerProfile":100} |
| "What did they post and reply to?" | Profile + replies | {"profiles":["handle"],"includeReplies":true} |
| "Data for this specific tweet" | Single tweet | {"tweetUrls":["<url>"]} |
| "Show me the replies to this tweet" | Thread | {"tweetUrls":["<url>"],"includeReplies":true,"maxScrolls":40} |
| "Compare engagement across three accounts" | Multi-profile | {"profiles":["a","b","c"],"maxTweetsPerProfile":100} then group by authorUsername |
| "Which of their tweets performed best?" | Ranking | Profile run, filter isRetweet: false, sort likeCount |
| "What's their engagement rate?" | Rate | Counts ÷ authorFollowers, nulls excluded |
| "What hashtags do they use?" | Entities | Profile run, then aggregate hashtags |
| "Who do they talk to?" | Mentions | Profile run, then aggregate mentions |
| "Track this tweet's growth" | Scheduled monitor | tweetUrls on a schedule; join on id |
| "Just a quick sample" | Cheap probe | {"profiles":["handle"],"maxTweetsPerProfile":10,"maxScrolls":5} |
| "What's trending about topic X?" | ❌ No search | Pick accounts that discuss X, then filter text locally |
| "Post the digest to Slack" | Scrape + deliver | Above, plus mcpConnector, mcpTool, deliveryMode |
Should NOT route here: keyword or hashtag search — there is no search input · DMs · protected or private accounts · follower or following lists · a guaranteed complete timeline · deleted tweets · posting, liking or following · X Ads data · anything requiring the caller's X credentials.
Agent Execution & Routing Logic
Does the user want X / TWITTER data?├── NO → not this Actor.│ YouTube → YouTube Scraper│ Reddit → Reddit Scraper│ Facebook comments → Facebook Public Comment Tracker│ posting / liking → read-only Actor│└── YES│├── Do they have ACCOUNTS or a TWEET?│ ├── accounts → profiles (handle, @handle or profile URL)│ ├── a tweet → tweetUrls│ ├── a TOPIC → NO SEARCH INPUT. Pick accounts, scrape, filter text locally.│ └── nothing → ASK. Empty input exits immediately.│├── ALWAYS keep proxyConfiguration enabled│├── Do they want replies?│ ├── the account's replies too → includeReplies: true on a profile│ ├── replies under one tweet → includeReplies: true on a tweetUrl│ └── no → false (default)│├── How deep? (maxTweetsPerProfile is PER PROFILE)│ ├── a probe → 10, maxScrolls 5│ ├── normal → 100, maxScrolls 25-30│ └── deep → 200+, maxScrolls 50 + a spend cap│ RAISE maxScrolls WITH the tweet target, or it caps you│├── What is the actual question?│ ├── best performing → filter isRetweet == false, sort likeCount│ ├── engagement rate → count / authorFollowers, EXCLUDING nulls│ ├── thread structure → group conversationId, order inReplyToTweetId│ ├── who they talk to → aggregate mentions ⚠ third-party personal data│ └── growth over time → SCHEDULE it; join on id│├── EXPECT partial results. Read the log before retrying; never loop on empty.│└── Should results land in another app?├── yes → mcpConnector + mcpTool + deliveryMode (DEFAULT none — set it!)│ one digest → summary│ long timeline → chunked│ one per tweet → perTweet (watch the call count)└── no → leave mcpConnector empty; dataset only
Cost Control & Pricing Transparency
This Actor bills for platform usage — the compute time and resources a run consumes — not per row. Current rates are on the Actor's Apify page, and the Console shows the live total while a run is in progress. It drives a real browser through a proxy, so run time is the cost.
| Choice | Effect on cost |
|---|---|
Number of profiles | Each is a separate browser session and timeline walk. |
maxScrolls | The main driver of run time — each scroll waits for X to answer. |
maxTweetsPerProfile | Stops a walk early once hit, so a realistic target saves time. |
includeReplies | More tweets per page load, and a deeper walk on tweet URLs. |
| Rate limiting | Scrolls that return nothing still cost time. The most expensive failure mode. |
| Being walled | A run with an expired session burns time and returns nothing. |
Keeping runs predictable:
- First run: one profile,
maxTweetsPerProfile: 10,maxScrolls: 5. Confirm you're getting tweets at all before scaling — the login wall is the thing to rule out first. - Match
maxScrollsto your tweet target. A high target with a low scroll cap wastes the target; a high scroll cap with a low target wastes scrolls. - Prefer more scheduled runs over one deep run. X throttles within a session, so several modest runs usually collect more in total than one long one.
- Always set a spend cap on unattended and scheduled runs.
- Read the log before re-running. Retrying into a rate limit or an expired session just spends time.
Troubleshooting
| # | Symptom you see | Most likely cause | Fix |
|---|---|---|---|
| 1 | Run finishes in seconds with an empty dataset and an error line about no valid profiles or tweet URLs | Both profiles and tweetUrls were empty, or every value was rejected | Supply at least one valid handle or /status/ URL. Neither field is marked required, so the platform will not catch this for you. |
| 2 | A handle you passed is silently missing from the results | It failed normalisation — a reserved path (home, explore, i, search, messages, settings), or a handle longer than 15 characters or containing anything other than letters, digits and underscores | Check the log for Skipping invalid profile input. Pass the bare handle, @handle, or the profile URL. |
| 3 | A tweet URL was skipped | The URL had no /status/<id> segment — a profile URL, a search URL or a shortened link | Use the full tweet URL from X's share menu. Both x.com and twitter.com are accepted. |
| 4 | Every profile returns zero tweets and the log warns about authentication cookies | The Actor's own X session is unset or expired | Not caller-fixable. Report it — the operator has to refresh the Actor's login secrets. X shows a login wall to logged-out browsers, so no session means no data. |
| 5 | One account returns nothing while others work | That account is protected, suspended, or has an empty timeline | Confirm the profile loads in a normal browser while logged out. Protected accounts are out of scope. |
| 6 | Far fewer tweets than maxTweetsPerProfile | maxScrolls was reached first, X rate-limited the session, or the timeline genuinely ran out | Raise maxScrolls alongside the tweet target. If it is consistent across accounts, the scroll cap is binding; if it varies run to run, it is rate limiting. |
| 7 | Results shrink on a re-run of the same input | X throttles within a session, and harder the longer a run goes | Split the work across several smaller scheduled runs rather than one deep run. |
| 8 | viewCount or bookmarkCount is null on most rows | X does not expose these consistently, especially on older tweets | Expected. null means unavailable, not zero — exclude nulls from averages rather than coercing them. |
| 9 | An account's numbers look inflated | Retweets are being counted as the account's own content | Filter isRetweet: false before measuring what an account actually says. |
| 10 | Quoted tweets by other people are missing from a profile run | A profile run keeps only tweets authored by the target handle | Expected. To get the quoted tweet itself, pass its URL in tweetUrls. |
| 11 | A tweet URL returned exactly one row when you wanted the thread | includeReplies is false, which also caps the scroll depth at 2 | Set includeReplies: true, and raise maxScrolls for a long thread. |
| 12 | Two runs of the same tweet report different engagement numbers | Counts are snapshots true only at capture time | Expected. Record your capture time next to any figure you publish, and join runs on id for a growth curve. |
| 13 | Run takes far longer than expected | maxScrolls is the main driver of run time, and scrolls that return nothing still cost time | Lower maxScrolls, and set a spend cap on unattended runs. |
| 14 | Connector is configured but nothing is delivered | deliveryMode is still none, which is the default | Set deliveryMode explicitly, and make sure mcpTool is filled in. |
| 15 | {media} renders empty in a connector call | media is a nested array, so it is in the dataset but not exposed as a placeholder | Use {urls} for links. |
| 16 | Connector starts erroring partway through a long run | perTweet mode makes one call per tweet and trips the service's rate limit | Switch to chunked or summary. |
Diagnostic checklist before opening an issue
- Read the log first. It distinguishes the three causes of an empty result — invalid input, a missing session, and a protected or empty account — and they need different responses.
- Confirm the run's status.
SUCCEEDEDwith zero items is a valid outcome here, not a crash. - Probe with one account:
{"profiles":["<handle>"],"maxTweetsPerProfile":10,"maxScrolls":5}. If that returns tweets, the pipeline works and the issue is scale or throttling. - Check the proxy is still enabled. It defaults to Apify Proxy; a direct platform IP makes the wall and the rate limits worse.
- Compare against a logged-out browser. If the profile shows nothing to a logged-out visitor, the Actor will see nothing either.
If it survives all five, open an Issues ticket with the run ID, the exact input JSON, and what you expected.
FAQ
Setup & access
Do I need an X account, developer account, or API key?
No. You need an Apify account and API token only. X authentication is handled server-side from the Actor's own secrets — there is no credential field in the input, and there should not be one. If any Actor asks you to type your X password into its input, treat that as a red flag.
Is this the official X API?
No. This is an independent Actor, not affiliated with, endorsed by, or sponsored by X Corp. The sanctioned route is the X API.
Do I need a proxy?
It is on by default and you should leave it on. Apify Proxy is available on the free plan, and a direct platform IP makes both the login wall and the rate limiting noticeably worse.
Can I plug it into Claude, Cursor, or a LangChain agent?
Yes — it is a native MCP tool:
claude mcp add --transport http apify "https://mcp.apify.com?tools=techforce.global/twitter-scraper"
Scope & input
Can I search for a keyword or hashtag?
No. There is no search input of any kind. The scope is a profile timeline or a specific tweet URL. To cover a topic, scrape the accounts that discuss it and filter text or hashtags yourself.
What forms of handle does it accept?
elonmusk, @elonmusk, https://x.com/elonmusk and the twitter.com equivalents all resolve to the same account. Reserved paths like home, explore and i are rejected.
Is maxTweetsPerProfile per run or per profile?
Per profile. Three profiles at 100 is up to 300 tweets. Set 0 for unlimited, bounded by how far the timeline actually paginates.
Why did I get fewer tweets than I asked for?
Because maxScrolls bounds how far the timeline is paged, and it can bind before your tweet target. Raise the two together. Rate limiting and short timelines are the other two causes — the log tells you which.
What does includeReplies actually change?
Two different things. On a profile it loads the with-replies timeline, so the account's own replies appear. On a tweet URL it also collects the replies beneath that tweet — without it, a tweet URL returns just that one tweet.
Can I get protected accounts, DMs, or follower lists?
No. Public tweets from public accounts only. You get the follower count on each row, never the list of followers.
Output & data
How many fields do I get per tweet?
29, and they are declared with types in .actor/dataset_schema.json. Four Console views group them: overview, engagement, threads and entities.
Why is bookmarkCount or viewCount null?
X does not expose them consistently, particularly on older tweets. null means unavailable, not zero — exclude nulls from averages instead of coercing them to 0.
Are the engagement counts reliable?
They are accurate at capture time and then keep moving. Two runs minutes apart will differ. Publish your capture time alongside any figure, and don't present scraped counts as authoritative measurement.
How do I rebuild a thread?
Group on conversationId, then order by inReplyToTweetId. isReply and inReplyToUsername tell you where each row sits.
How do I calculate engagement rate?
Divide any count by authorFollowers on the same row — no second lookup needed. Exclude rows where the count is null.
Is a retweet marked as such?
Yes, isRetweet. Filter isRetweet: false before measuring an account's own output, or you will attribute someone else's words and metrics to them.
Do I get long-form (note) tweets in full?
Yes. Where X serves a long-form tweet, text carries the full note rather than the truncated version.
Cost & runtime
How is this billed?
Per platform usage — the compute time and resources a run consumes, not per row. It drives a real browser through a proxy, so run time is the cost. Current rates are on the Actor's Apify page.
What makes a run expensive?
maxScrolls, mostly — every scroll waits for X to answer, and scrolls that return nothing still cost time. The number of profiles multiplies it, since each is its own browser session.
How do I guarantee an agent never overspends?
Pass maxTotalChargeUsd as a query parameter on the run endpoint — a hard per-execution ceiling. Always set one on unattended and scheduled runs.
What happens if I abort a run?
Tweets are pushed to the dataset as each profile or tweet finishes, and the Actor handles the abort signal and exits promptly, so you keep everything collected before the abort.
Companion machine-readable files
| File | What it defines |
|---|---|
| .actor/input_schema.json | Every input, its type and default |
| .actor/dataset_schema.json | All 29 output fields with types, and the four Console views |
| .actor/output_schema.json | Where the run's output is exposed |
| .actor/actor.json | Actor identity, storage wiring and the authentication secrets |
If this page and the schema files disagree, the schema files win. They are what the platform validates against at run time; this page is documentation and can lag a release.
Related Actors by Techforce Global
| Actor | Best for |
|---|---|
| This Actor | X / Twitter profile timelines and single tweets with full engagement metrics |
| Reddit Scraper | Reddit posts, threaded comments, communities and users |
| YouTube Scraper | YouTube videos, channels and comment threads |
| Facebook Public Comment Tracker | Comment threads under a public Facebook page's posts |
| Pinterest Scraper | Pins from searches, boards, profiles and pin URLs |
For cross-platform social listening, pair this with the Reddit and YouTube Actors — three platforms, one question. All three return personal data, so the compliance guidance above applies to each.
Browse the full catalogue at scraper.techforce.global.
Compliance & Data Privacy
This is the section that matters most on this page. X is the strictest major platform on scraping, and tweets are personal data by any reading.
-
The output identifies real people.
authorUsername,authorId,authorName,text,inReplyToUsernameandmentionsare all personal data under GDPR / UK GDPR.authorIdis especially significant: it's a stable numeric identifier that links every tweet an account posts and survives a handle change, so it defeats the usual assumption that renaming an account breaks the link. You become a controller the moment you store any of it — lawful basis, retention period, and a route for access and erasure requests. CCPA/CPRA and India's DPDP Act 2023 impose parallel duties. -
mentionsimplicates people who didn't post the tweet. A mentioned handle is a third party whose identity you're now storing as a side effect of scraping someone else. There's no balancing test you've done for them at all. Drop the array unless you specifically need it. -
"It was a public tweet" is not a lawful basis. EU regulators have repeatedly rejected that argument. The basis has to survive a balancing test against the person's reasonable expectations — and someone posting to their followers does not expect to enter a third-party database.
-
Tweets carry special-category data you cannot filter in advance. People disclose political views, health conditions, religion and sexuality on X constantly. That's Article 9 data, normally requiring explicit consent you don't have, and you can't know which rows contain it until after collection. Keep retention short and access tight.
-
Drop the identity fields if you don't need them. For sentiment, topic or volume analysis you need
textand the counts — notauthorId,authorNameormentions. Discarding them on ingest removes most of the obligations above at no analytical cost. Do that unless you have a specific reason not to. -
X's terms prohibit automated collection, and X enforces this more aggressively than its peers. It restricted its API, priced the remaining tiers steeply, requires login for most content, and has litigated against scrapers. This Actor does not remove that risk — it is a risk you accept by running it. The X API is the sanctioned route.
-
Note what the authentication implies. The Actor scrapes while logged in as an account belonging to its operator, not to you. That account bears the ToS exposure and the suspension risk, and if it's rate-limited or locked, runs return nothing until it's restored. It also means results reflect what that account can see.
-
Using tweets to train a model is a separate act with separate risk. X licenses its corpus commercially and is explicit that unlicensed scraping for training is against its terms. Get advice rather than relying on "it was public".
-
Don't build a monitoring list of individuals. Tracking named private individuals' posts over time is surveillance, whatever the interface looks like. Monitoring your own brand's mentions, or public statements by organisations and public figures acting in that capacity, is a materially different proposition.
-
Engagement counts are snapshots and will not reconcile. Two runs minutes apart differ. Publish
createdAtand your capture time alongside any figure you quote, and don't present scraped metrics as authoritative measurement. -
Attribution over mirroring.
urlis on every row. Link back rather than republishing tweet text as your own content, and never republish someone's post alongside their handle in a way that could expose them to harassment.
🆘 Support & Custom Pipeline Engineering
Need automated multi-city scheduled feeds, webhook enrichment, or enterprise data warehouse syncs?
- Email: bhavin.shah@techforceglobal.com
- Custom Enterprise Integrations: Book a 15-Minute Technical Consultation
- Maintained by: Techforce Global — Specialists in High-Performance Web Scrapers and Agentic Workflows.
🏷️ Structured data for search & AI discovery
SoftwareApplication describes the Actor; FAQPage wraps the FAQ above so those answers are
eligible for rich results, matching it question-for-question.
{"@context": "https://schema.org","@type": "SoftwareApplication","name": "X / Twitter Scraper","alternateName": "X / Twitter Scraper — Profile Timelines & Single Tweets With Full Engagement Metrics","applicationCategory": "BusinessApplication","applicationSubCategory": "Web Scraping / Social Listening","operatingSystem": "Any (cloud-hosted on Apify)","url": "https://apify.com/techforce.global/twitter-scraper","description": "Scrapes tweets from X/Twitter profile timelines and individual tweet URLs, returning 29 fields per tweet: text, timestamps and language; the full engagement set of likes, retweets, replies, quotes, bookmarks and views; author handle, id, display name, verification and follower count; thread and reply structure via conversation and parent ids; and extracted hashtags, mentions, links and media as arrays. Callers supply no X credentials — authentication is server-side from the Actor's own secrets. Optional direct delivery into Notion, Slack, Linear, Airtable or any MCP connector, with a chunked mode for long timelines.","author": {"@type": "Organization","name": "Techforce Global","url": "https://www.techforceglobal.com"},"provider": {"@type": "Organization","name": "Apify","url": "https://apify.com"},"offers": {"@type": "Offer","category": "Pay per platform usage","description": "Billed for the compute time and resources a run consumes. Current rates are shown on the Actor's Apify page.","url": "https://apify.com/techforce.global/twitter-scraper"},"featureList": ["Profile timelines and individual tweet URLs as input","Handles accepted as bare, @-prefixed or full profile URLs","Full engagement set: likes, retweets, replies, quotes, bookmarks and views","Author follower count on the same row, so engagement rate is one division","Thread reconstruction via conversation id and parent tweet id","Hashtags, mentions, links and media extracted as arrays","Long-form note tweets returned in full rather than truncated","Optional reply capture on both profiles and single tweets","Per-row provenance showing which input produced each tweet","No caller credentials - authentication is server-side","Apify Proxy enabled by default to reduce walling and rate limiting","Tweets written to the dataset as each source completes, so aborted runs keep partial data","Optional MCP delivery to Notion, Slack, Linear, Airtable and more","Chunked delivery mode keeps long timelines under connector size and timeout caps","Four Console dataset views including engagement and thread structure"],"isAccessibleForFree": false,"softwareRequirements": "Apify account and API token","softwareHelp": {"@type": "CreativeWork","url": "https://apify.com/techforce.global/twitter-scraper"},"keywords": "X scraper, Twitter scraper, tweet data, engagement metrics, social listening, brand monitoring, thread reconstruction, sentiment analysis input, follower count, MCP connector"}
{"@context": "https://schema.org","@type": "FAQPage","mainEntity": [{"@type": "Question","name": "Do I need an X account, developer account, or API key?","acceptedAnswer": {"@type": "Answer","text": "No. You need an Apify account and API token only. X authentication is handled server-side from the Actor's own secrets - there is no credential field in the input, and there should not be one. If any Actor asks you to type your X password into its input, treat that as a red flag."}},{"@type": "Question","name": "Is this the official X API?","acceptedAnswer": {"@type": "Answer","text": "No. This is an independent Actor, not affiliated with, endorsed by, or sponsored by X Corp. The sanctioned route is the X API at developer.x.com."}},{"@type": "Question","name": "Do I need a proxy?","acceptedAnswer": {"@type": "Answer","text": "It is on by default and you should leave it on. Apify Proxy is available on the free plan, and a direct platform IP makes both the login wall and the rate limiting noticeably worse."}},{"@type": "Question","name": "Can I plug it into Claude, Cursor, or a LangChain agent?","acceptedAnswer": {"@type": "Answer","text": "Yes - it is a native MCP tool. Add it with: claude mcp add --transport http apify \"https://mcp.apify.com?tools=techforce.global/twitter-scraper\""}},{"@type": "Question","name": "Can I search for a keyword or hashtag?","acceptedAnswer": {"@type": "Answer","text": "No. There is no search input of any kind. The scope is a profile timeline or a specific tweet URL. To cover a topic, scrape the accounts that discuss it and filter text or hashtags yourself."}},{"@type": "Question","name": "What forms of handle does it accept?","acceptedAnswer": {"@type": "Answer","text": "elonmusk, @elonmusk, https://x.com/elonmusk and the twitter.com equivalents all resolve to the same account. Reserved paths like home, explore and i are rejected."}},{"@type": "Question","name": "Is maxTweetsPerProfile per run or per profile?","acceptedAnswer": {"@type": "Answer","text": "Per profile. Three profiles at 100 is up to 300 tweets. Set 0 for unlimited, bounded by how far the timeline actually paginates."}},{"@type": "Question","name": "Why did I get fewer tweets than I asked for?","acceptedAnswer": {"@type": "Answer","text": "Because maxScrolls bounds how far the timeline is paged, and it can bind before your tweet target. Raise the two together. Rate limiting and short timelines are the other two causes - the log tells you which."}},{"@type": "Question","name": "What does includeReplies actually change?","acceptedAnswer": {"@type": "Answer","text": "Two different things. On a profile it loads the with-replies timeline, so the account's own replies appear. On a tweet URL it also collects the replies beneath that tweet - without it, a tweet URL returns just that one tweet."}},{"@type": "Question","name": "Can I get protected accounts, DMs, or follower lists?","acceptedAnswer": {"@type": "Answer","text": "No. Public tweets from public accounts only. You get the follower count on each row, never the list of followers."}},{"@type": "Question","name": "How many fields do I get per tweet?","acceptedAnswer": {"@type": "Answer","text": "29, and they are declared with types in .actor/dataset_schema.json. Four Console views group them: overview, engagement, threads and entities."}},{"@type": "Question","name": "Why is bookmarkCount or viewCount null?","acceptedAnswer": {"@type": "Answer","text": "X does not expose them consistently, particularly on older tweets. Null means unavailable, not zero - exclude nulls from averages instead of coercing them to 0."}},{"@type": "Question","name": "Are the engagement counts reliable?","acceptedAnswer": {"@type": "Answer","text": "They are accurate at capture time and then keep moving. Two runs minutes apart will differ. Publish your capture time alongside any figure, and don't present scraped counts as authoritative measurement."}},{"@type": "Question","name": "How do I rebuild a thread?","acceptedAnswer": {"@type": "Answer","text": "Group on conversationId, then order by inReplyToTweetId. isReply and inReplyToUsername tell you where each row sits."}},{"@type": "Question","name": "How do I calculate engagement rate?","acceptedAnswer": {"@type": "Answer","text": "Divide any count by authorFollowers on the same row - no second lookup needed. Exclude rows where the count is null."}},{"@type": "Question","name": "Is a retweet marked as such?","acceptedAnswer": {"@type": "Answer","text": "Yes, isRetweet. Filter isRetweet false before measuring an account's own output, or you will attribute someone else's words and metrics to them."}},{"@type": "Question","name": "Do I get long-form (note) tweets in full?","acceptedAnswer": {"@type": "Answer","text": "Yes. Where X serves a long-form tweet, text carries the full note rather than the truncated version."}},{"@type": "Question","name": "How is this billed?","acceptedAnswer": {"@type": "Answer","text": "Per platform usage - the compute time and resources a run consumes, not per row. It drives a real browser through a proxy, so run time is the cost. Current rates are on the Actor's Apify page."}},{"@type": "Question","name": "What makes a run expensive?","acceptedAnswer": {"@type": "Answer","text": "maxScrolls, mostly - every scroll waits for X to answer, and scrolls that return nothing still cost time. The number of profiles multiplies it, since each is its own browser session."}},{"@type": "Question","name": "How do I guarantee an agent never overspends?","acceptedAnswer": {"@type": "Answer","text": "Pass maxTotalChargeUsd as a query parameter on the run endpoint - a hard per-execution ceiling. Always set one on unattended and scheduled runs."}},{"@type": "Question","name": "What happens if I abort a run?","acceptedAnswer": {"@type": "Answer","text": "Tweets are pushed to the dataset as each profile or tweet finishes, and the Actor handles the abort signal and exits promptly, so you keep everything collected before the abort."}}]}