X (Twitter) Scraper - Tweets, Threads & Engagement Rate avatar

X (Twitter) Scraper - Tweets, Threads & Engagement Rate

Pricing

from $9.30 / 1,000 results

Go to Apify Store
X (Twitter) Scraper - Tweets, Threads & Engagement Rate

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

Techforce Global

Maintained by Community

Actor stats

0

Bookmarked

4

Total users

3

Monthly active users

a day ago

Last modified

Share

X / Twitter Scraper - Profile Timelines and Single Tweets, with All Six Engagement Counts, Author Follower Count and Thread Structure

Actor Inputs Output MCP

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, text and mentions all 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
SetupX developer account, app registration, a project, and a paid tier for most read accessAn Apify token. No X developer account, no app registration, and no X credentials from you
Sanctioned by XYes — the official, terms-compliant routeNo — automated collection is against X's terms. See Compliance
Read volumeTiered and capped per month; the free tier is effectively write-only for most read endpointsBounded by run time and X's rate limiting, not a monthly quota
Result completenessDeterministic within your quota⚠️ Best-effort. Partial and empty runs are normal
Author follower count on the tweet rowA separate user lookup, then a join✅ Same row — authorFollowers, so engagement rate is one division
Bookmark and view countsTier-dependent✅ Returned whenever X exposes them (often null)
Thread structureAvailable, assembled by youconversationId + inReplyToTweetId on every row
Hashtags, mentions, links, mediaPresent in the payload you parse✅ Pre-extracted as arrays
Delivery into Notion / Slack / AirtableBuild 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 29
fields per tweet including full engagement counts, author identity and follower count, thread
and reply structure, and extracted hashtags, mentions, links and media. Authentication is
server-side via the Actor's own secrets - callers supply no credentials. Optional delivery
into 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 Actor
owner's own secrets, server-side. If any Actor asks you to type your X password into its
input, that is a red flag.
output_contains_personal_data: >
authorUsername, authorId, authorName, text and mentions identify real people. authorId is a
stable identifier that survives a handle change, and mentions identify third parties who did
not 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-limits
hard, and changes its internal response shapes. A run can legitimately return far fewer
tweets 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 specific
tweet 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 300
tweets. maxScrolls bounds how far the timeline is paged, so a low maxScrolls can cap you
below 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 just
that 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, guaranteed
complete 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 unauthorised
connector 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/elonmusk and twitter.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, inReplyToTweetId and inReplyToUsername let you rebuild a conversation instead of getting a flat pile.

  • Extracts entities separately. hashtags, mentions, urls and media come out as arrays, so you're not regexing the tweet text.

  • Records which input produced each row. scrapedFrom is profile:<handle> or tweet:<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

  1. Supply profiles, tweetUrls, or both. With neither, the run logs an error and exits. Neither is marked required, so validation won't catch it.

  2. There is no search input. No keywords, no hashtags. To cover a topic, scrape the accounts that discuss it and filter text or hashtags yourself.

  3. 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.

  4. maxTweetsPerProfile is per profile. Three profiles at 100 is up to 300 tweets.

  5. maxScrolls can cap you below maxTweetsPerProfile. It bounds how far the timeline is paged. If you ask for 500 tweets with maxScrolls: 5, the scroll limit wins. Raise both together.

  6. includeReplies does 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.

  7. bookmarkCount and viewCount are frequently null. X doesn't expose them consistently, especially on older tweets. Null means unavailable, not zero — exclude nulls from averages.

  8. 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.

  9. A retweet is not original content. Filter isRetweet: false before measuring what an account actually says, or you'll attribute someone else's words and metrics to them.

  10. mentions identifies third parties. Those handles belong to people who didn't post the tweet. Treat that array with the same care as the author fields.

  11. media is a nested array, so it's in the dataset but not available as a delivery placeholder. Use {urls} for links.

  12. deliveryMode defaults to none. Delivery is off until you set it explicitly and provide mcpConnector and mcpTool.

  13. Keep the proxy enabled. It defaults to Apify Proxy. Direct platform IPs make the login wall and rate limits worse.


Input Contract

ParameterTypeRequiredDefaultDescription
profilesarray<string>see rule 1Handles, @handles or profile URLs. Reserved paths like home and explore are rejected.
tweetUrlsarray<string>see rule 1Individual tweet/status URLs, x.com or twitter.com.
maxTweetsPerProfileintegerno100Tweets per profile.
includeRepliesbooleannofalseProfile: include the account's replies. Tweet URL: also collect replies under it.
maxScrollsintegerno25How far to page a timeline. Can cap you below maxTweetsPerProfile.
proxyConfigurationobjectno{"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)

ParameterTypeDefaultDescription
mcpConnectorstringMCP connector to deliver into. The Console offers a picker of connectors you've authorised.
mcpToolstring""Tool to call — e.g. create_page, send_message. Required when a connector is set.
deliveryModestring"none"summary, chunked, perTweet, none. Off by default.
mcpArgumentsobject{}Arguments for the tool. {placeholder} tokens substituted per call.
mcpMessageTemplatestring""Text rendered into {message}.

Delivery placeholders

ScopePlaceholders
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

FieldTypeNullableMeaning
idstringyesTweet id. The stable key for joining runs.
urlstringyesCanonical link to the tweet.
textstringyesTweet body. Personal data — user-authored.
createdAtstringyesWhen it was posted, as X reports it.
langstringyesLanguage code X assigned.
sourcestringyesPosting client, where X still exposes it.
scrapedFromstringyesprofile:<handle> or tweet:<id> — which input produced the row.

Author

FieldTypeNullableMeaning
authorUsernamestringyes@handle. Personal data.
authorIdstringyesNumeric account id. Personal data — stable, and it survives a handle change.
authorNamestringyesDisplay name, often a real name. Personal data.
authorVerifiedbooleanyesVerification badge.
authorFollowersintegeryesFollower count at capture time.
authorProfileImageUrlstringyesAvatar URL.

Engagement — all snapshots

FieldTypeNullableMeaning
likeCountintegeryesLikes.
retweetCountintegeryesRetweets.
replyCountintegeryesReplies.
quoteCountintegeryesQuote tweets.
bookmarkCountintegeryesFrequently null — not always exposed.
viewCountintegeryesFrequently null, especially on older tweets.

Structure and entities

FieldTypeNullableMeaning
isRetweetbooleanyesFilter this out to measure original content.
isQuotebooleanyesQuotes another tweet.
isReplybooleanyesIs a reply. Populated on profiles only with includeReplies.
conversationIdstringyesThread id — group on this to rebuild a conversation.
inReplyToUsernamestringyesHandle being replied to. Personal data.
inReplyToTweetIdstringyesParent tweet id.
hashtagsarraynoHashtags. [] when none.
mentionsarraynoHandles mentioned. Personal data — third parties.
urlsarraynoExpanded outbound links.
mediaarraynoAttached 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

ViewShows
TweetsAuthor, text, timestamp and the headline engagement counts
EngagementAll six counts plus authorFollowers — for reach and rate analysis
Threads & RepliesConversation id, reply and quote structure
Hashtags, Mentions & LinksThe 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 ApifyClient
client = 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 Counter
print(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 original
if 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 it
maxScrolls: 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}"
}
}

deliveryMode must be set explicitly — it defaults to none. Use chunked for long timelines; perTweet on 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.

ConditionWhat you seeCauseDeterministic action
NO_SOURCESLog: no valid profiles or tweet URLs; run exitsBoth inputs empty, or every value invalidModify Input: supply a valid handle or tweet URL.
INVALID_HANDLE_SKIPPEDLog: skipping invalid profile inputA reserved path (home, explore, i) or a malformed handleModify Input: use a real handle.
INVALID_TWEET_URL_SKIPPEDLog: skipping invalid tweet URLNot a /status/<id> URLModify Input: use a full tweet URL.
NO_AUTH_COOKIESLog: no usable authentication cookiesThe Actor's session secrets are unset or expiredNot caller-fixable. Report it — the operator must refresh the Actor's credentials.
PROFILE_EMPTY_OR_WALLEDLog names the account and says protected/empty or cookies missing/expiredProtected account, empty timeline, or an expired sessionRead which cause the log names. Protected → stop. Session → report to the operator.
PARTIAL_TIMELINEFewer tweets than maxTweetsPerProfileRate limiting, maxScrolls reached, or the timeline ran outExpected. Raise maxScrolls, or accept the sample.
SCROLL_LIMIT_BOUND_FIRSTConsistently short results across profilesmaxScrolls too low for the tweet targetModify Input: raise maxScrolls alongside maxTweetsPerProfile.
ZERO_RESULTSSUCCEEDED, empty datasetAny of the aboveRead the log first, then retry once. Don't loop.
NULL_VIEW_OR_BOOKMARK_COUNTviewCount / bookmarkCount nullX doesn't expose them consistentlyExpected. Null ≠ zero. Exclude from averages.
RETWEETS_SKEWING_ANALYSISMetrics attributed to the wrong authorRetweets counted as original contentFix Consumer: filter isRetweet: false.
NO_REPLY_FIELDSisReply / inReplyToTweetId unpopulatedincludeReplies is falseModify Input: enable it.
DELETED_TWEETLog: tweet may be deleted or protectedThe tweet no longer existsExpected. Not recoverable.
MEDIA_PLACEHOLDER_EMPTY{media} renders empty in a connector callIt's a nested array, not a placeholderModify Input: use {urls}.
CONNECTOR_NOT_DELIVERINGConnector set, nothing delivered, no errordeliveryMode still none (the default)Modify Input: set it explicitly.
CONNECTOR_RATE_LIMITEDConnector errors partway throughperTweet on a large runModify Input: switch to chunked or summary.
CONNECTOR_TOOL_NOT_FOUNDLog: tool not available, followed by the available toolsmcpTool name is wrongModify Input: copy an exact name from that log line. Dataset unaffected.
CONNECTOR_UNREACHABLELog: could not reach the connectorConnector not authorised, or running outside the Apify platformAuthorise 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…IntentInput
"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?"RankingProfile run, filter isRetweet: false, sort likeCount
"What's their engagement rate?"RateCounts ÷ authorFollowers, nulls excluded
"What hashtags do they use?"EntitiesProfile run, then aggregate hashtags
"Who do they talk to?"MentionsProfile run, then aggregate mentions
"Track this tweet's growth"Scheduled monitortweetUrls 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 searchPick accounts that discuss X, then filter text locally
"Post the digest to Slack"Scrape + deliverAbove, 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.

ChoiceEffect on cost
Number of profilesEach is a separate browser session and timeline walk.
maxScrollsThe main driver of run time — each scroll waits for X to answer.
maxTweetsPerProfileStops a walk early once hit, so a realistic target saves time.
includeRepliesMore tweets per page load, and a deeper walk on tweet URLs.
Rate limitingScrolls that return nothing still cost time. The most expensive failure mode.
Being walledA run with an expired session burns time and returns nothing.

Keeping runs predictable:

  1. 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.
  2. Match maxScrolls to your tweet target. A high target with a low scroll cap wastes the target; a high scroll cap with a low target wastes scrolls.
  3. 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.
  4. Always set a spend cap on unattended and scheduled runs.
  5. Read the log before re-running. Retrying into a rate limit or an expired session just spends time.

Troubleshooting

#Symptom you seeMost likely causeFix
1Run finishes in seconds with an empty dataset and an error line about no valid profiles or tweet URLsBoth profiles and tweetUrls were empty, or every value was rejectedSupply at least one valid handle or /status/ URL. Neither field is marked required, so the platform will not catch this for you.
2A handle you passed is silently missing from the resultsIt 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 underscoresCheck the log for Skipping invalid profile input. Pass the bare handle, @handle, or the profile URL.
3A tweet URL was skippedThe URL had no /status/<id> segment — a profile URL, a search URL or a shortened linkUse the full tweet URL from X's share menu. Both x.com and twitter.com are accepted.
4Every profile returns zero tweets and the log warns about authentication cookiesThe Actor's own X session is unset or expiredNot 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.
5One account returns nothing while others workThat account is protected, suspended, or has an empty timelineConfirm the profile loads in a normal browser while logged out. Protected accounts are out of scope.
6Far fewer tweets than maxTweetsPerProfilemaxScrolls was reached first, X rate-limited the session, or the timeline genuinely ran outRaise 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.
7Results shrink on a re-run of the same inputX throttles within a session, and harder the longer a run goesSplit the work across several smaller scheduled runs rather than one deep run.
8viewCount or bookmarkCount is null on most rowsX does not expose these consistently, especially on older tweetsExpected. null means unavailable, not zero — exclude nulls from averages rather than coercing them.
9An account's numbers look inflatedRetweets are being counted as the account's own contentFilter isRetweet: false before measuring what an account actually says.
10Quoted tweets by other people are missing from a profile runA profile run keeps only tweets authored by the target handleExpected. To get the quoted tweet itself, pass its URL in tweetUrls.
11A tweet URL returned exactly one row when you wanted the threadincludeReplies is false, which also caps the scroll depth at 2Set includeReplies: true, and raise maxScrolls for a long thread.
12Two runs of the same tweet report different engagement numbersCounts are snapshots true only at capture timeExpected. Record your capture time next to any figure you publish, and join runs on id for a growth curve.
13Run takes far longer than expectedmaxScrolls is the main driver of run time, and scrolls that return nothing still cost timeLower maxScrolls, and set a spend cap on unattended runs.
14Connector is configured but nothing is delivereddeliveryMode is still none, which is the defaultSet deliveryMode explicitly, and make sure mcpTool is filled in.
15{media} renders empty in a connector callmedia is a nested array, so it is in the dataset but not exposed as a placeholderUse {urls} for links.
16Connector starts erroring partway through a long runperTweet mode makes one call per tweet and trips the service's rate limitSwitch to chunked or summary.

Diagnostic checklist before opening an issue

  1. 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.
  2. Confirm the run's status. SUCCEEDED with zero items is a valid outcome here, not a crash.
  3. Probe with one account: {"profiles":["<handle>"],"maxTweetsPerProfile":10,"maxScrolls":5}. If that returns tweets, the pipeline works and the issue is scale or throttling.
  4. Check the proxy is still enabled. It defaults to Apify Proxy; a direct platform IP makes the wall and the rate limits worse.
  5. 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

FileWhat it defines
.actor/input_schema.jsonEvery input, its type and default
.actor/dataset_schema.jsonAll 29 output fields with types, and the four Console views
.actor/output_schema.jsonWhere the run's output is exposed
.actor/actor.jsonActor 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.


ActorBest for
This ActorX / Twitter profile timelines and single tweets with full engagement metrics
Reddit ScraperReddit posts, threaded comments, communities and users
YouTube ScraperYouTube videos, channels and comment threads
Facebook Public Comment TrackerComment threads under a public Facebook page's posts
Pinterest ScraperPins 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, inReplyToUsername and mentions are all personal data under GDPR / UK GDPR. authorId is 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.

  • mentions implicates 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 text and the counts — not authorId, authorName or mentions. 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 createdAt and your capture time alongside any figure you quote, and don't present scraped metrics as authoritative measurement.

  • Attribution over mirroring. url is 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?



🏷️ 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."
}
}
]
}