X (Twitter) Profile Scraper — Accounts, Activity & Growth avatar

X (Twitter) Profile Scraper — Accounts, Activity & Growth

Pricing

$1.00 / 1,000 account rows

Go to Apify Store
X (Twitter) Profile Scraper — Accounts, Activity & Growth

X (Twitter) Profile Scraper — Accounts, Activity & Growth

One row per X account: followers, following, posts, bio, website, location, verification, account age — plus what X does not state: posting cadence, average and median engagement, top post, and the follower change since your last run. No login, no API key.

Pricing

$1.00 / 1,000 account rows

Rating

0.0

(0)

Developer

Berkan Kaplan

Berkan Kaplan

Maintained by Community

Actor stats

0

Bookmarked

3

Total users

2

Monthly active users

5 days ago

Last modified

Categories

Share

X (Twitter) Profile Scraper — Accounts, Activity & Growth 𝕏

Paste X accounts; get one row each with everything X states about the account — and three things it does not: how often it posts, how much engagement it actually gets, and how its following changed since your last run. No login, no API key, no browser: one read per account, charged per row.

  • 👤 The whole account record — followers, following, posts, likes given, listed count, media count, bio, website, location, verification and its type, protected and default-image flags, profile and banner images, join date, account age, lifetime posts per day, followers-to-following ratio
  • 📈 Activity measured, not guessed — posts per day on the embedded page, days since the last post, average and median engagement, highest engagement, engagement per 1,000 followers, the top post with its link, languages, the share of posts with media, links, hashtags and replies, and the accounts it mentions most
  • 🔁 Growth between runs — the follower change, the change per day, the percentage change and the posts added since this account was last read. The first run of an account says so instead of writing zeros
  • 🧾 Honest about its limits — the activity figures come from the one page X embeds (20–105 posts, measured 2026-10-19), not from the whole history, and every row carries pageSpanDays so you know what they were measured over
  • 🤖 Agent-ready — MCP, and pay-per-event so an AI agent can run it and settle the bill itself

What does this Actor do?

It reads X's own embed (syndication) backend — the service that renders embedded timelines on other people's websites. One read per account brings two things at once: the account object X states (38 fields) and the account's newest page of posts. The first becomes the row's identity and counts; the second becomes the activity figures. Nothing is logged in, no cookie jar is kept and no browser is started, which is why a run is fast and cheap and does not break when X changes its web app.

Use it when you need: an influencer or competitor shortlist with real engagement figures; a weekly follower-growth table; a check of which accounts in a list are still alive; firmographics for accounts you already know.

Use something else when: you need follower lists, keyword search, an account's full posting history, or e-mail addresses — X's embed backend serves none of those, so this Actor does not claim them. For a watchlist of posts rather than accounts, use X (Twitter) Account Monitor; for tweets you already have the addresses of, X (Twitter) Tweet Details Scraper.

Quick start (API)

Three accounts, with activity and growth:

curl -X POST "https://api.apify.com/v2/acts/foxlabs~x-profile-scraper/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "accounts": ["@NASA", "x.com/nike", "@Microsoft"], "includeActivity": true, "trackGrowth": true }'

Prefer no code? Open the Input tab, paste your accounts one per line, and click Start.

Getting started (3 steps)

  1. Paste the accounts. A handle (@nasa), a profile address (x.com/nasa) or the account's numeric id (11348282) — all three work, mixed in one list.
  2. Leave activity and growth on. They cost nothing extra: the activity figures come from the same single read, and growth only needs the counts from your previous run.
  3. Run it on a schedule (daily or weekly) if you want the growth columns to fill. The first run is the baseline; every run after it states what changed.

Input

FieldTypeDefaultWhat it does
accountsarray—The accounts to read: @handle, x.com/handle or numeric id. Required.
includeActivitybooleantrueMeasures the account's newest embedded page: cadence, engagement, top post, languages, content mix, most-mentioned accounts.
trackGrowthbooleantrueStates the change since this account was last read by a run of yours.
historyStorestringx-profile-historyThe key-value store that holds the counts last seen per account.
minFollowersinteger0Accounts under this are not delivered and not charged.
onlyVerifiedbooleanfalseOnly accounts X marks as verified.
activeWithinDaysinteger0Only accounts whose newest embedded post is within N days (0 = no limit).
maxAccountsinteger0Cost cap for the whole run (0 = every account).
concurrencyinteger3Accounts read side by side. X refuses bursts (measured: eight at a time lost half a ten-account run, three at a time delivered all ten), and anything it still refuses is read again one at a time at the end of the run.
rememberAccountIdsbooleantrueKeeps each handle's numeric id after the first read, so later runs take the cheap road.
accountIdStorestringx-profile-account-idsThe store that holds the handle → id pairs. It belongs to this Actor: a store another Actor created cannot be opened from here.
proxyConfigurationobjectApify Proxy (datacenter)Datacenter addresses answer every read made by numeric id. Handle lookups go through residential addresses, because a handle lookup from a datacenter address is rate-limited.

Output

One row per account. Download as JSON, CSV, Excel or read it from the API.

{
"type": "profile",
"id": "11348282",
"handle": "NASA",
"url": "https://x.com/NASA",
"name": "NASA",
"bio": "Making the seemingly impossible, possible. ✨",
"website": "http://www.nasa.gov/",
"followers": 92320972,
"following": 115,
"tweets": 74438,
"likesGiven": 17032,
"listedIn": 96866,
"mediaPosted": 28201,
"verified": true,
"verifiedType": "Government",
"createdAt": "2007-12-19T20:20:32.000Z",
"accountAgeDays": 6879,
"tweetsPerDay": 10.82,
"followersPerFollowing": 802791.06,
"postsPerDay": 3.9,
"lastPostAt": "2026-10-16T19:42:20.000Z",
"daysSinceLastPost": 2.7,
"avgEngagement": 2914.8,
"avgEngagementPer1kFollowers": 0.032,
"topPostUrl": "https://x.com/NASA/status/2109320622882873464",
"activity": {
"postsOnPage": 20,
"pageSpanDays": 5.1,
"postsLast7Days": 17,
"medianEngagement": 1480,
"maxEngagement": 25678,
"languages": ["en"],
"withMediaPercent": 45,
"withLinkPercent": 55,
"mentionsMost": ["Space_Station", "NASAJohnson", "NASAArtemis"]
},
"growth": {
"previousReadAt": "2026-10-12T09:00:00.000Z",
"daysSincePreviousRead": 7.1,
"followersChange": 18422,
"followersChangePerDay": 2594.6,
"followersChangePercent": 0.02,
"postsChange": 27
},
"scrapedAt": "2026-10-19T12:24:43.230Z"
}

Account fields

FieldTypeDescription
id / handle / urlstringNumeric account id, the handle, and the profile address
name / bio / location / websitestringAs X states them; null when X states none (location is empty on most brand accounts)
followers / following / tweetsintegerThe counts X states
likesGiven / listedIn / mediaPostedintegerLikes the account gave, lists it appears on, media items it posted
verified / verifiedType / protected—Verification and its kind (Business, Government, Blue…)
profileImage / bannerImagestringFull-size image addresses on pbs.twimg.com
createdAt / accountAgeDays—Join date and the account's age in days
tweetsPerDaynumbertweets / accountAgeDays — the lifetime average
followersPerFollowingnumberThe ratio that separates a broadcaster from a follow-back account
withheldInCountriesarrayCountries X says the account is withheld in

Activity fields (includeActivity, also flattened onto the row)

postsOnPage, newestPostAt, oldestPostAt, pageSpanDays, daysSinceLastPost, postsPerDayOnPage, postsLast7Days, postsLast30Days, totalEngagementOnPage, avgEngagement, medianEngagement, maxEngagement, avgEngagementPer1kFollowers, topPostId, topPostUrl, topPostText, topPostEngagement, topPostAt, languages, withMediaPercent, replyPercent, withLinkPercent, withHashtagPercent, mentionsMost, hashtagsMost.

The row also carries postsPerDay, lastPostAt, daysSinceLastPost, avgEngagement, avgEngagementPer1kFollowers and topPostUrl as plain columns, so a CSV export can be sorted without opening the nested object.

Growth fields (trackGrowth)

previousReadAt, daysSincePreviousRead, followersChange, followersChangePerDay, followersChangePercent, followingChange, postsChange, postsPerDaySincePreviousRead, listedInChange, likesGivenChange — plus followersChange and followersChangePerDay as plain columns. growth is null the first time an account is read: there is nothing to compare against, and a zero would be a lie.

A line that cannot be delivered is written as a free type: "status" row with the reason — not an X account, no public record on X, an account the run could not read, or an account an earlier line already named. Status rows are never charged.

Use cases

  • Influencer shortlist with real numbers. Followers are easy to buy; avgEngagementPer1kFollowers and postsPerDayOnPage are not. One run turns a list of handles into a comparable table.
  • Weekly growth report. Schedule it; every run states the follower change and the posts added since the last one.
  • Dead-account sweep. activeWithinDays plus daysSinceLastPost separates live accounts from abandoned ones.
  • Competitor and portfolio monitoring. Verification, listed count, bio and website changes are all in the row.
  • Feeding a dashboard or an LLM. Flat rows, stable field names, ISO timestamps, null where X said nothing.

Integration

MCP / AI agents: ask for this Actor by name — foxlabs/x-profile-scraper — on the Apify MCP server. It is pay-per-event, so an agent can run it and settle the bill per row.

JavaScript

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });
const run = await client.actor('foxlabs/x-profile-scraper').call({
accounts: ['@NASA', '@nike'],
includeActivity: true,
trackGrowth: true,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map((row) => `${row.handle}: ${row.followers} (${row.followersChange ?? 'first read'})`));

Python

from apify_client import ApifyClient
client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("foxlabs/x-profile-scraper").call(run_input={
"accounts": ["@NASA", "@nike"],
"includeActivity": True,
})
for row in client.dataset(run.default_dataset_id).iterate_items():
if row["type"] == "profile":
print(row["handle"], row["followers"], row.get("avgEngagementPer1kFollowers"))

Written for apify-client 3 for Python, where call() returns a run object; with version 1 or 2 read run["defaultDatasetId"].

Automation (n8n / Zapier / Make): schedule → HTTP request to the Actor API with your account list → handle the JSON dataset → push to a sheet, CRM or dashboard.

Pricing

Pay-per-event: $1 per 1,000 account rows (~$0.001 each). Status rows and accounts left out by a filter are free — you pay for rows you receive. The activity figures and the growth columns cost nothing extra: they come from the same read and from your own stored history.

What you ask forRowsApprox. cost
500 influencer accounts, once500~$0.50
1,000 accounts, weekly growth table4,000/month~$4/month
50 competitor accounts, daily1,500/month~$1.50/month

You also pay Apify for the compute the run uses, which is small: one HTTP read per account, no browser. Apify's free tier includes $5/month of credits.

Limits — what this Actor cannot do

These are not omissions; they are what X's embed backend serves (measured 2026-10-19):

  • The activity figures come from one page. X embeds an account's newest 20–105 posts and offers no paging, so cadence and engagement are measured over pageSpanDays — 5 days for a busy account, months for a quiet one. The field travels with the row for exactly that reason.
  • No follower lists. The counts yes, the lists no.
  • No e-mail or phone. X states neither on an account; website is whatever the account links to.
  • location is often empty. It was empty on all three brand accounts measured; it stays null rather than guessed.
  • Growth needs a previous run. The first read of an account has nothing to compare against and says so.
  • Protected accounts are not readable, and an account X has no public record of comes back as a free status row.
  • The rate budget is shared. X's embed backend limits how fast one account may read, across all of its runs — two runs of this family started at the same time will refuse each other's reads (measured 2026-10-19). Run them one after another, and leave the speed setting at its default; a read X refuses is tried again at the end of the run, one at a time.

FAQ

Do I need an X account, login or API key? No. This reads X's public embed service.

How fresh is the data? Every run reads live; scrapedAt says when.

Why is postsPerDay different from postsPerDayOnPage? The first is the lifetime average (tweets / accountAgeDays); the second is the cadence on the page just read. A dormant account with a long history shows a high lifetime figure and a low current one — that difference is the signal.

Where is the growth history kept? In a key-value store of your Apify account (x-profile-history by default). It is not shared with us or anyone else, and you can use separate stores for separate watchlists. A store that another Actor created cannot be opened from here (Apify scopes a run's permissions to its own Actor); if the name you give cannot be opened, the run still delivers every row and says so in its message.

What does a handle cost extra? One lookup on a residential address the first time (a handle lookup from a datacenter address is rate-limited by X). The id is then remembered in your own key-value store and later runs take the cheap road. Give numeric ids and no lookup is ever needed.

Can I read 1,000 accounts in one run? Yes — one read per account, eight at a time by default.

Troubleshooting

What you seeWhat it means
X has no public account record for @…No account with that name, or it is suspended, protected or has nothing embedded. Free row.
… is the same account as an earlier line of this inputA handle and its numeric id (or the same line twice) were both given. Delivered once, charged once. Free row.
X's embed backend is rate-limiting handle lookups from this addressThe residential lookup for that handle did not get through. Run again, or give the account's numeric id.
… accounts read for the first time: the next run will say what changedGrowth tracking working: this run is the baseline.
Run ends FAILED with did not answer the opening checkX did not answer us at all. Nothing was read or charged; try again.

This Actor reads a public embed service with no login and collects what X itself publishes on public profiles. It stores no private data and no credentials. Always check your own use against X's Terms of Service and the data-protection rules that apply to you, especially if you keep personal data or use it for monitoring individuals.

Support & contact

Found a defect or need a field that is missing? Open an issue on the Actor's Issues tab with the run id — every run keeps a SOURCE_REPORT record with what it saw, read, delivered and left out, which makes a report quick to answer.

Changelog

0.1 — 2026-10-19

  • First release. Accounts by handle, profile address or numeric id; the 38 fields X states plus account age, lifetime posts per day and the followers-to-following ratio; activity figures measured on the newest embedded page; growth between runs from your own key-value store; filters for followers, verification and activity; handle → id remembered between runs; free status rows for every line that cannot be delivered, including an account named twice in one input.

Part of the foXLabs data platform — public-data company, contact, social, procurement and AI-search intelligence scrapers.