X (Twitter) Followers Scraper - $0.09 per 1,000
Pricing
from $0.09 / 1,000 accounts
X (Twitter) Followers Scraper - $0.09 per 1,000
Scrape the followers of any public X (Twitter) account, or the accounts it follows. One row per profile: handle, name, bio, follower and following counts, posts, verification, location, avatar, website and join date. The cheapest X followers scraper on the market. No API key, no login, no cookies.
Pricing
from $0.09 / 1,000 accounts
Rating
0.0
(0)
Developer
Dami's Studio
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
21 hours ago
Last modified
Categories
Share
X (Twitter) Followers Scraper
Give it one or more X (Twitter) handles and get back the accounts that follow them — or, with one switch, the accounts they follow. Every row is a full profile: handle, display name, bio, follower and following counts, post count, verification badge and its type, location, avatar, banner, website and the date the account joined X. No API key, no cookie to paste, no browser.
- Followers or following, chosen with a single input field — the same rows either way.
- Every profile field is read from X itself, so the counts are the live ones, not a cached guess.
- Several handles per run, each paged independently up to your own per-handle limit.
- A handle that does not exist, or is protected, costs you nothing: it returns an uncharged row that says so.
- Runs with empty input return one labelled sample row, free, so you can see the shape before spending anything.
Price
$0.09 per 1,000 followers, plus a $0.0005 start fee per run (billed per gigabyte of run memory, so exactly that on the default 1 GB).
This is the cheapest X (Twitter) followers scraper on the market, and it is the same rate on every plan, free or paid. There are no volume tiers, no minimum spend, no subscription and no add-on fees. What you read here is what you pay on day one and on day four hundred.
| Followers | Total cost |
|---|---|
| 100 | $0.0095 |
| 1,000 | $0.0905 |
| 10,000 | $0.9005 |
| 100,000 | $9.0005 |
What is actually charged
- One
follower-scrapedevent per follower row written to the dataset. Nothing else is metered per row. - Free: the sample row an empty run returns, and every diagnostic row — a blocked target, a dead URL, a search that matched nothing. Those rows all carry
"charged": false. - Accounts already returned for the same handle in the same run are dropped before they are charged.
- Handles that do not exist, are suspended or are protected return an uncharged diagnostic row.
- The sample row an empty run returns is never charged.
- A run that finds nothing costs the start fee and nothing more.
- Rows never leave the dataset without a charge, and are never charged without a row. The billed event is a named one, so there is no price quietly attached to
apify-default-dataset-item— the trick that makes some scrapers bill you for their own error messages.
Input
{"handles": ["NASA","github"],"mode": "followers","maxItemsPerHandle": 200}
| Field | What it does |
|---|---|
handles | One or more X handles, with or without the leading @. Full profile URLs such as https://x.com/NASA work too. Up to 10 per run. |
mode | followers (default) returns the accounts that follow each handle. following returns the accounts each handle follows. |
maxItemsPerHandle | How many accounts to return for each handle. Default 50, hard ceiling 2,000 per handle and 5,000 per run. Keep it low while testing — you pay per row. |
enrichProfiles | On by default. Leave it on: it is what fills in follower counts, location, join date and the rest. Turn it off only if all you want is the list of handles. |
proxyUrls | Leave empty. Fill it in only if you want the traffic to leave through proxy servers you already pay for, as http://user:pass@host:port. |
sessionCookies | Optional, and not needed for a normal run. Leave it empty. An auth_token=…; ct0=… pair from your own logged-in x.com is used only as a fallback, on the rare run where no public source answers, and it gives that fallback a rate limit nobody else draws on. Stored as a secret. |
Run it with empty input and you get one clearly labelled sample row, free, so you can see the output shape before you spend anything.
Output
One row per follower. A real row from a real run:
{"ok": true,"charged": true,"recordType": "profile","sourceHandle": "NASA","relation": "follower","userName": "NASAHubble","name": "Hubble","url": "https://x.com/NASAHubble","description": "The official X account for the NASA Hubble Space Telescope, managed and operated by NASA's Goddard Space Flight Center.","followersCount": 8906746,"followingCount": 44,"tweetsCount": 8427,"isVerified": true,"verifiedType": "government","location": "Goddard Space Flight Center","profileImageUrl": "https://pbs.twimg.com/profile_images/3468011581/efb985f24af0a814a722457a768f3cc5_400x400.jpeg","createdAt": "2008-03-06T20:15:02.000Z","isProtected": false,"website": "http://www.nasa.gov/mission_pages/hubble/main/index.html","profileBannerUrl": "https://pbs.twimg.com/profile_banners/14091091/1669391293","listedCount": 15230,"mediaCount": 3116,"favouritesCount": 1241,"userId": "14091091","profileEnriched": true,"scrapedAt": "2026-08-16T15:48:36.774Z"}
Field notes
sourceHandle— the handle you asked about — the account whose list this row came from. Use it to split a multi-handle run.relation—followerwhen the row is somebody who followssourceHandle,followingwhen it is somebodysourceHandlefollows.userName— the handle, exactly as X spells it, without the @. Safe to use as a key.userId— X's numeric account id. It never changes, even when the handle does, so it is the better key for anything long-lived.description— the bio as written, including line breaks and emoji.followersCount— the live follower count at read time, straight from X.followingCount— how many accounts this profile follows.isVerified— true when the account carries a checkmark of any kind.verifiedType— which kind:blue,business,governmentorcreator. Null when the account is not verified.location— the free-text location field on the profile. People write anything they like in it, including jokes.website— the link on the profile, expanded from the shortened form X displays.profileImageUrl— the avatar at 400x400 rather than the 48px thumbnail X returns by default.createdAt— the date the account joined X, normalised to ISO 8601 in UTC.isProtected— true when the account is private. Its own posts and lists are not readable.profileEnriched— true when the profile fields were filled in from X. False means only the handle and display name were available for that row — rare, and visible so you can filter on it.
Every real row carries "charged": true. Sample rows carry "_sample": true and diagnostic rows carry "_diagnostic": true with an errorCode you can filter on, and neither is ever billed.
How it works
- You give it handles. For each one it first asks X about the handle itself, which settles in a single request whether the account exists, whether it is protected, and how many entries the list even has — so a typo does not turn into a long, billed run.
- It then reads the follower (or following) list page by page from public, logged-out sources, following the same cursor the pages themselves expose, until it has as many accounts as you asked for. This is always tried first, because it needs no session and so competes with nothing.
- On the rare run where none of those sources answers, the list is read from X directly with a session instead of the handle being skipped. That path is a fallback and nothing else: it is never used while a public source is still answering, because the session is a shared resource and a page served publicly is a page that costs nobody anything.
- Every account it finds is then looked up on X directly, and that is where the profile fields in each row come from: the counts, the location, the badge, the avatar and the join date are the ones X itself reports at read time.
- Requests leave through a large pool of rotating addresses, so a per-address rate limit is answered by moving rather than by waiting, and the run keeps going.
- No account of yours is used, no cookie of yours is asked for, and nothing is created on your behalf. The only session involved is the Actor's own fallback, and you never have to supply or manage it.
What people use it for
- Audience research — pull the followers of three accounts in your niche and see who overlaps, who is verified, and how big their own audiences are.
- Lead lists — filter the rows on
followersCount,locationor a keyword indescriptionto get a shortlist of accounts worth reaching out to. - Competitive audience mapping — run the same handles weekly and diff on
userIdto see exactly who arrived and who left. - Influencer vetting — read the following list of an account and check what kind of profiles it actually follows before you pay for a placement.
- Community building — find the accounts that follow several of your peers but not you yet.
- Dataset building — the rows are flat and typed, so they load into a spreadsheet, a warehouse or a notebook without cleaning.
Reading the output
Every run writes three kinds of row, and they are easy to tell apart:
- Real rows carry
"charged": trueand"recordType": "profile". These are the rows you paid for, one billed event each. - The sample row carries
"_sample": trueand"charged": false. There is exactly one, it only appears when the input had no handles, and it exists so you can look at the shape of the output before you spend anything. - Diagnostic rows carry
"_diagnostic": true,"charged": falseand anerrorCodeyou can switch on:NOT_FOUNDwhen a handle does not exist, is suspended or is protected;NO_RESULTSwhen the list is genuinely empty;BLOCKEDwhen no public source answered for that handle on that run;NETWORKfor a transport failure;TIME_BUDGETwhen the run ran out of time before reaching a handle. Each carries a plain-Englisherrorand thesourceHandleit belongs to.
If you only want the data, filter on charged == true. The count of those rows always equals the number of events you were billed for, so the dataset is its own invoice.
Ordering, and what "the first 200 followers" means
The list comes back in the order X itself keeps it, which for followers is newest first — the account that followed most recently is row one. So maxItemsPerHandle: 200 gives you the 200 most recent followers, not a random 200 and not the oldest 200. That is usually what you want for audience tracking, and it is what makes a weekly re-run cheap: the new arrivals are all at the top.
The following list is ordered the same way — most recently followed first.
There is no way to jump to an arbitrary offset. If you need 2,000 accounts you pay for 2,000 rows; the run pages through them in order.
Limitations
- A protected (private) account keeps its followers and following lists private. The run returns one uncharged row saying so and moves on — there is no public way to read them.
- The ceiling is 2,000 accounts per handle, 5,000 rows per run and 10 handles per run. For a very large audience, split the work across runs.
- The list is read newest-first and cannot be started from an arbitrary offset, so a deep pull of an account with millions of followers means paging through the rows in between.
- Follower and following counts are a snapshot at read time. On an active account they will differ from what you see if you look a minute later.
locationis free text that people fill in themselves. It is often a joke, a country, or empty, and it is not geocoded.- A small number of accounts occasionally come back with only the handle and display name filled in — usually because the account was suspended or renamed between the list page and the lookup. Those rows carry
profileEnriched: falseso you can filter them out. - Very rarely no source answers for a handle during a run. That returns an uncharged
BLOCKEDrow rather than a partial one, with asessionStatefield saying whether the fallback was still usable; re-running usually clears it. - Email addresses and phone numbers are not returned. X does not publish them and this Actor does not guess them.
- Deleted, suspended and deactivated accounts simply do not appear in the list — X removes them from it, so there is nothing to report.
Questions
Do I need an X API key, a developer account or my own cookies?
No. Nothing is asked of you beyond the handles, and the normal path uses no credentials at all.
So what is sessionCookies for?
It is an optional fallback. Follower lists come from public sources first; if none of them answers on a given run, the Actor reads the list from X with a session of its own rather than giving up on the handle. Putting your own auth_token and ct0 in sessionCookies means that fallback runs on your account and your rate limit instead of a shared one — worth doing if you pull at volume, pointless otherwise. Leaving it empty is the normal case.
Can I get the followers of a private account?
No, and neither can anything else. X keeps a protected account's follower and following lists private. You get an uncharged row telling you the account is protected, and you are not billed for it.
How do I get the accounts a handle follows instead of its followers?
Set mode to following. The output columns are identical; the relation field on every row changes to following so mixed datasets stay unambiguous.
What does a run cost?
One start fee, plus one charge per account row delivered. Sample rows and diagnostic rows are free, so a run that finds nothing costs you only the start fee. There is a cost table in the Price section above.
Can I run several handles at once?
Yes, up to ten per run. Each handle gets its own maxItemsPerHandle budget, and each row carries sourceHandle so you can split the dataset afterwards.
Will the run fail if something goes wrong?
No. A blocked, empty or missing handle produces an uncharged diagnostic row explaining what happened and the run still finishes as succeeded. A failed run would still bill you the start fee, which would mean paying to be told something went wrong.
Can I schedule it?
Yes. Nothing is held between runs, so the same input is safe to repeat. Diff on userId rather than userName — handles change, ids do not.
How fresh are the numbers in each row?
They are read at the moment the row is built, directly from X. The scrapedAt field on every row records exactly when.