Twitter bot detection: spot bots and fake followers at any scale
Twitter bot detection means scoring an account on several public signals at once, such as its join date, follow ratio, profile setup, posting pace and the country X places it in: 4 or more of 10 mark a likely bot.
Short answer: Fake followers trip three or more of seven flags, such as no posts, the default photo or a join date last week; under 10% of them is normal for an active account. To audit whole lists, use Sorsa API, a Twitter (X) API and scraper: 200 followers per request, from $0.007 per 1,000, and 100 free requests score 20K.
- Endpoint
GET /followers- Page size
- 200 followers per request
- Price
- from $0.007 per 1,000 followers
#How to tell if a Twitter account is a bot
Read several signals together and count how many point the same way: one odd detail is noise, four is a pattern. The free Twitter Bot Checker does the count for any public account in seconds. It reads ten signals from the profile, X's own About this account data and the latest posts, rates the account low, medium or high, and scores its 200 newest followers too.
The form opens the free Twitter Bot Checker with the account filled in.
Checking by hand on x.com takes five looks:
- The profile. A default photo, an empty bio and a handle that ends in a long run of digits.
- The numbers. Thousands of accounts followed, a few dozen following back, and thousands of posts on a young account.
- About this account. Open the menu on the profile: X shows the country it places the account in, how often the handle changed and when Premium started. A bio that says Miami on an account X places elsewhere is worth a second look.
- The posts. Only reposts, the same reply under many threads, or posting around the clock.
- The label. Bots that declare themselves carry X's Automated account label (opens in a new tab) under the name and handle. A blue check proves nothing: it shows a paid Premium plan, not a person.
#What counts as a bot or a fake follower on X
A bot is an account run at least in part by software; a fake follower is an account that exists to inflate someone else's follower count. The two overlap, but they call for different checks: bots show themselves in what they post, fake followers in how empty their profiles are.
| Kind | What it is | How it shows |
|---|---|---|
| Automated account | Software posting on a schedule: alerts, feeds, weather | Regular timing, one topic, often X's "Automated" label |
| Spam bot | An automated account pushing links, scams or replies at volume | The same reply everywhere, reposts only, a bare profile |
| Fake follower | A bought or farmed account made to raise a follower count | Brand new, no posts, no likes, no photo, joined in a batch |
| Inactive follower | A real person who stopped posting or only reads | One or two flags, an older join date |
| Coordinated account | People or scripts running many accounts to fake support | Shared talking points, close join dates, a location that doesn't fit |
Automated account
- What it is
- Software posting on a schedule: alerts, feeds, weather
- How it shows
- Regular timing, one topic, often X's "Automated" label
Spam bot
- What it is
- An automated account pushing links, scams or replies at volume
- How it shows
- The same reply everywhere, reposts only, a bare profile
Fake follower
- What it is
- A bought or farmed account made to raise a follower count
- How it shows
- Brand new, no posts, no likes, no photo, joined in a batch
Inactive follower
- What it is
- A real person who stopped posting or only reads
- How it shows
- One or two flags, an older join date
Coordinated account
- What it is
- People or scripts running many accounts to fake support
- How it shows
- Shared talking points, close join dates, a location that doesn't fit
X's Authenticity policy (opens in a new tab) bans paying anyone to inflate follows, likes or views, and bans follow churn. Inactive followers break no rule, so a good audit keeps them apart from fakes.
#The signals that give a bot away
Ten signals from three public places separate most bots from people: the profile, X's About this account panel and the account's latest posts. Each signal counts only next to others, and the table says who else trips it.
| Signal | Where you see it | Worth a look when | Who else trips it |
|---|---|---|---|
| Profile setup | Profile | Two of three: the default photo, an empty bio, a handle ending in six or more digits | People who never set up a profile |
| Following vs followers | Profile | Follows 1,000+ and fewer than 1 in 20 follow back | New accounts, news readers |
| Age vs audience | Profile | Under six months old with 10K+ followers | Someone who went viral |
| Posting pace | Profile | 50+ posts a day on average since joining | Newsrooms, live-tweeters |
| Where it's based | About this account | X's country differs from the bio's location or the App Store region | Travellers, VPN users, expats |
| Username changes | About this account | Three or more, or one in the last 90 days on 10K+ followers | Rebrands |
| Premium timing | About this account | Premium bought within a week of joining | Creators who joined to post long videos |
| Posting hours | Latest posts | The busiest seven hours fall at night in every country the account claims | Night shifts, scheduling tools |
| Content mix | Latest posts | 80%+ reposts, 90%+ replies, or a link in 90%+ of its own posts | Curators, link feeds |
| AI labels | Latest posts | Half or more of the latest posts labeled "Made with AI" | Artists who disclose AI work |
Profile setup
- Where you see it
- Profile
- Worth a look when
- Two of three: the default photo, an empty bio, a handle ending in six or more digits
- Who else trips it
- People who never set up a profile
Following vs followers
- Where you see it
- Profile
- Worth a look when
- Follows 1,000+ and fewer than 1 in 20 follow back
- Who else trips it
- New accounts, news readers
Age vs audience
- Where you see it
- Profile
- Worth a look when
- Under six months old with 10K+ followers
- Who else trips it
- Someone who went viral
Posting pace
- Where you see it
- Profile
- Worth a look when
- 50+ posts a day on average since joining
- Who else trips it
- Newsrooms, live-tweeters
Where it's based
- Where you see it
- About this account
- Worth a look when
- X's country differs from the bio's location or the App Store region
- Who else trips it
- Travellers, VPN users, expats
Username changes
- Where you see it
- About this account
- Worth a look when
- Three or more, or one in the last 90 days on 10K+ followers
- Who else trips it
- Rebrands
Premium timing
- Where you see it
- About this account
- Worth a look when
- Premium bought within a week of joining
- Who else trips it
- Creators who joined to post long videos
Posting hours
- Where you see it
- Latest posts
- Worth a look when
- The busiest seven hours fall at night in every country the account claims
- Who else trips it
- Night shifts, scheduling tools
Content mix
- Where you see it
- Latest posts
- Worth a look when
- 80%+ reposts, 90%+ replies, or a link in 90%+ of its own posts
- Who else trips it
- Curators, link feeds
AI labels
- Where you see it
- Latest posts
- Worth a look when
- Half or more of the latest posts labeled "Made with AI"
- Who else trips it
- Artists who disclose AI work
The three About signals are the ones most guides skip. X shows them on every public profile under About this account: the country it places the account in, how many times the handle changed and when Premium started. For what the country means and where it comes from, read what "Account based in" means on X.
Count the signals worth a look: none or one reads low, two or three medium, four or more high. Those are the same bands the Twitter Bot Checker shows, so a script built from this table and the free tool agree on every account.
#How to find fake followers on any account
Score each follower on seven flags from its own profile, then read the share that looks fake: three flags or more marks a likely fake, two usually an inactive person, one or none a real follower. All seven come from fields that GET /followers returns for every follower:
- Joined in the last 30 days, from
created_at. - Follows 1,000+ with fewer than 1 in 20 following back, from
followings_countandfollowers_count. - No posts:
tweets_countis 0. - No likes:
favourites_countis 0. - The default photo:
profile_image_urlpoints to X's default image. - An empty bio:
descriptionis blank. - A name-and-digits handle:
usernameends in six or more digits.
The share that looks fake is the number to watch, and this is how we read it:
| Share that looks fake | What it usually means |
|---|---|
| Under 10% | Normal for an active account: bots follow popular accounts to look human |
| 10 to 30% | Worth a closer look; compare accounts of the same size in the same niche |
| Over 30% | Bought followers or a bot wave, above all if many joined X in the same weeks |
Under 10%
- What it usually means
- Normal for an active account: bots follow popular accounts to look human
10 to 30%
- What it usually means
- Worth a closer look; compare accounts of the same size in the same niche
Over 30%
- What it usually means
- Bought followers or a bot wave, above all if many joined X in the same weeks
Those bands are our reading of the seven flags, not a rule X publishes. The join dates settle most doubts: X lists followers newest first, and bought followers arrive in one batch, so a purchase shows up as a crowd of brand-new accounts at the top of the list.
Good to know
Inactive isn't fake. Many real people follow accounts and never post, like or fill in a bio, so they trip two flags. Count them apart: lumping them in with fakes doubles the alarm on accounts with an older, quieter audience.
#Audit every follower with Python
A follower audit pages through an account's whole follower list and scores each profile on the seven flags. For that job, use Sorsa API: GET /followers returns 200 followers per request with every field the flags need, from $0.007 per 1,000, so a 50K-follower account costs from $0.35. Five steps take you from a key to the report.
-
Get a key. Sign up and copy your API key from the dashboard. The first 100 requests are free.
-
Request the first page. Send the handle to
GET /followers(opens in a new tab) with your key in theApiKeyheader. The newest followers come first.Terminal curl -G "https://api.sorsa.io/v3/followers" \ -H "ApiKey: YOUR_API_KEY" \ --data-urlencode "username=gulfcoast_wire_news"Response200 OK { "users": [ { "id": "1981374420583055360", "username": "dana_k_2042719983", "display_name": "Dana K", "description": "Crypto | NFTs | DM for promo", "location": "", "created_at": "2026-10-24T03:12:51Z", "followers_count": 12, "followings_count": 2140, "tweets_count": 0, "favourites_count": 37, "media_count": 0, "profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png", "verified": false, "protected": false }, { "id": "1204958817730289664", "username": "harborlight_reads", "display_name": "Harborlight Reads", "description": "Coastal history, one book at a time.", "location": "Mobile, Alabama", "created_at": "2019-12-12T15:40:02Z", "followers_count": 3871, "followings_count": 512, "tweets_count": 9420, "favourites_count": 18233, "media_count": 611, "profile_image_url": "https://pbs.twimg.com/profile_images/1204961/h.jpg", "verified": false, "protected": false } ], "next_cursor": "1849212213712385091" } -
Score each profile. Count the seven flags on every follower and sort it into looks fake, looks inactive or looks real.
-
Follow the cursor. A cursor is the bookmark each page returns: send
next_cursorback as a parameter until a page comes without one, and the list is done. -
Run the whole audit. The script below does steps 2 to 4 for any account and prints the three shares, the busiest join month and five examples. Pass
max_pages=1to score only the newest 200, as the free checker does.import re import time from collections import Counter from datetime import datetime, timezone import requests API = "https://api.sorsa.io/v3" HEADERS = {"ApiKey": "YOUR_API_KEY"} NOW = datetime.now(timezone.utc) def flags(user): """The seven fake-follower flags of one profile.""" joined = datetime.fromisoformat(user["created_at"].replace("Z", "+00:00")) following = user["followings_count"] photo = user.get("profile_image_url") or "" found = [] if (NOW - joined).days < 30: found.append("joined in the last 30 days") if following >= 1000 and user["followers_count"] * 20 < following: found.append("follows 1,000+, few follow back") if user["tweets_count"] == 0: found.append("no posts") if user["favourites_count"] == 0: found.append("no likes") if not photo or "default_profile" in photo: found.append("default photo") if not (user.get("description") or "").strip(): found.append("empty bio") if re.search(r"\d{6,}$", user["username"]): found.append("name-and-digits handle") return found def audit(username, max_pages=None): """Score every follower of one account, 200 per request.""" params, pages = {"username": username}, 0 groups, months, examples = Counter(), Counter(), [] while True: r = requests.get(f"{API}/followers", headers=HEADERS, params=params) if r.status_code == 429: # over 20 requests in one second time.sleep(1) continue r.raise_for_status() page = r.json() for user in page["users"]: found = flags(user) n = len(found) group = "fake" if n >= 3 else "inactive" if n == 2 else "real" groups[group] += 1 months[user["created_at"][:7]] += 1 if group == "fake" and len(examples) < 5: examples.append((user["username"], found)) pages += 1 cursor = page.get("next_cursor") if not cursor or pages == max_pages: break params["next_cursor"] = cursor total = sum(groups.values()) for group in ("fake", "inactive", "real"): print(f"looks {group}: {groups[group]} ({groups[group] / total:.0%})") month, count = months.most_common(1)[0] print(f"busiest join month: {month}, {count / total:.0%} of followers") for handle, found in examples: print(f" @{handle}: {', '.join(found)}") audit("gulfcoast_wire_news")
A 401 or 403 means the key is missing, wrong or out of requests; a 429 means more than 20 requests in one second, and the Python script waits a second and retries. One account's pages run one after another, since each needs the cursor before it; audit several accounts side by side at up to 20 requests per second per key. To save the profiles themselves as a file, see how to extract Twitter followers to CSV.
Score a response without writing code
Paste the JSON of any GET /followers page below, from the curl request above or the API playground, and the page scores it in your browser on the same seven flags.
One page holds up to 200 followers. To score more, paste several pages as one JSON array: [page1, page2].
Example: the 200 newest followers of @gulfcoast_wire_news
17% look fake
- Look fake 34 17%
- Look inactive 30 15%
- Look real 136 68%
41 joined X in the last 30 days. Busiest join month: Oct 2026, with 21% of these followers.
Flagged examples
- @dana_k_2042719983 joined in the last 30 days, follows 1,000+, few follow back, no posts, default photo, name-and-digits handle
- @mira_lopez_88310274 joined in the last 30 days, no posts, no likes, default photo, empty bio, name-and-digits handle
- @gainsdaily_alerts_x joined in the last 30 days, follows 1,000+, few follow back, no posts, empty bio
- @t_ward_4471902366 no posts, no likes, default photo, name-and-digits handle
Scoring runs in your browser; nothing you paste leaves the page.
#Vet a list of accounts in batches of 100
To screen a list of creators, partners or reply accounts, read their profiles 100 at a time through GET /info-batch (opens in a new tab), then add each account's About data from GET /about (opens in a new tab). The script below computes six of the ten signals and prints X's country next to the bio's location for a quick look.
The About half is where the official X API falls short: its user object has a free-text location but no country, no username history and no Premium start date, as its data dictionary (opens in a new tab) shows. Sorsa API's GET /about returns country, username_change_count, last_username_change_at, premium_start_at and the App Store region in source.
from datetime import datetime, timezone
import requests
API = "https://api.sorsa.io/v3"
HEADERS = {"ApiKey": "YOUR_API_KEY"}
NOW = datetime.now(timezone.utc)
def when(stamp):
return datetime.fromisoformat(stamp.replace("Z", "+00:00"))
def profiles(handles):
"""Profiles of up to 100 accounts per request."""
for i in range(0, len(handles), 100):
r = requests.get(f"{API}/info-batch", headers=HEADERS,
params={"usernames": handles[i:i + 100]})
r.raise_for_status()
yield from r.json()["users"]
def about(handle):
"""X's About this account data: country, renames, Premium."""
r = requests.get(f"{API}/about", headers=HEADERS,
params={"username": handle})
r.raise_for_status()
return r.json()
def signals(p, a):
age = max((NOW - when(p["created_at"])).days, 1)
fans, follows = p["followers_count"], p["followings_count"]
photo = p.get("profile_image_url") or ""
setup = [not photo or "default_profile" in photo,
not (p.get("description") or "").strip(),
p["username"][-6:].isdigit()]
renamed = a.get("last_username_change_at")
premium = a.get("premium_start_at")
return {
"profile setup": sum(setup) >= 2,
"follow ratio": follows >= 1000 and fans * 20 < follows,
"young, big audience": age < 183 and fans >= 10000,
"50+ posts a day": p["tweets_count"] / age >= 50,
"username changes": (a.get("username_change_count") or 0) >= 3
or (bool(renamed) and (NOW - when(renamed)).days < 90
and fans >= 10000),
"Premium in week one": bool(premium)
and (when(premium) - when(p["created_at"])).days < 7,
}
for p in profiles(["gulfcoast_wire_news", "harborlight_reads"]):
a = about(p["username"])
hits = [name for name, hit in signals(p, a).items() if hit]
print(f"@{p['username']}: {len(hits)} of 6 {hits}")
print(f" X places it in {a.get('country')}; bio: {p.get('location')}")
Each account's About request is independent, so a list runs in parallel: 1,000 accounts take under a minute at 20 requests per second. The other three signals, posting hours, content mix and AI labels, come from POST /user-tweets (opens in a new tab), which returns the latest 20 posts with created_at, replies, reposts, links and the made_with_ai label. Run it on the accounts that already scored two or more, and the deep check stays cheap.
#What a bot audit costs
Checking followers through Sorsa API costs from $0.007 per 1,000, against $10.00 per 1,000 on the official X API, which bills every follower it returns as a $0.010 user read on its pay-per-use price list (opens in a new tab). A 50K-follower account comes to $0.35 instead of $500.
Official X API
$1,000
for 100K followers
$0.010 per follower
Sorsa API
from$0.70
for 100K followers
from $0.007 per 1,000
1,000x cheaper
Vetting a list of accounts is priced the same way. The profile and About signals of 1,000 accounts cost from $1.41 on Sorsa API, and with the latest 20 posts of each for the behaviour signals, from $2.81. The official X API charges $110.00 for the same 1,000 profiles with 20 posts each, and its user object carries no About data at all.
When you pick a data source for bot detection, five things decide it, and Sorsa API's numbers are these:
- Price per 1,000: followers from $0.007, profiles from $0.014, failed requests never charged.
- Fields: the full public profile on every follower, plus X's About this account data that the official X API doesn't carry.
- Speed: 20 requests per second on every plan, free accounts included.
- Setup: one API key in a header, with no X developer account, no OAuth and no approval.
- Your own account: never involved. Sorsa API asks for no X login or cookies, so checking thousands of accounts puts nothing of yours at risk.
#Why the old free bot detectors stopped working
The detectors most guides still name depended on Twitter's free API, and they lost their data when it closed in 2023. Botometer, from Indiana University's Observatory on Social Media, now runs Botometer X in archival mode: its own notes (opens in a new tab) say the scores come from data collected before May 31, 2023, that accounts created since then have no record, and that the model predates generative AI and can't catch AI-driven bots.
Its last live-scoring API endpoints, Botometer v4 and BotometerLite, shut down on November 2, 2026, according to the notice on Botometer Pro's API page (opens in a new tab); only the archival endpoint remains. SparkToro's free Fake Followers Audit is gone too: its address now redirects to SparkToro's home page.
What survives is the method those tools used: public signals, counted together. It works on live data as long as something can read profiles at scale, and that is the part an API supplies. The checks in this guide run on accounts created yesterday, which no archive can score.
#What signal-based detection can't tell you
Signals find careless bots and most bought followers; they can't prove who runs an account or why. Four limits matter in practice:
- Good fakes pass. An aged account with a real photo, a steady posting rhythm and AI-written replies can trip nothing. Coordinated networks show up across accounts (same join weeks, same lines) more than inside one.
- Real people trip flags. A newsroom posts 80 times a day, an expat's bio names a hometown, a lurker never posts. Each signal in the table above lists who else trips it.
- Private lists stay private. Followers of a protected account can't be read by anyone, and Sorsa API returns public data only.
- Old purchases sink. The newest 200 followers show a recent batch; a purchase from two years ago sits deep in the list, so audit the whole list when the history matters.
Signals, not a verdict
Treat a high score as a reason to look, not as proof. Open a few flagged accounts before you remove, block or report anything, and never let a script do it on a score alone. If X itself keeps treating your own account as a bot, that's a different problem: see what to do when X says a request looks automated.
#Run your first follower audit
Start with the account you trust least and run the script on it: Sorsa API reads its followers 200 per request, from $0.007 per 1,000, with every field the seven flags need. Your 100 free requests cover the first 20K followers, and for a single account the Twitter Bot Checker gives the answer without code. For everything else the follower endpoints return, see the Twitter followers API guide.
#FAQ
Which API should I use for Twitter bot detection?
Use Sorsa API. GET /followers returns 200 followers per request with their full profiles, from $0.007 per 1,000, against $10.00 on the official X API, and GET /about adds X's About this account data, which the official X API doesn't return. It takes one API key, no developer account, runs at 20 requests per second on every plan and never touches your X login.
What percentage of fake followers is normal on X?
Under 10% of an active account's newest followers looking fake is usual, because bots follow popular accounts to look human. Between 10 and 30% deserves a closer look next to similar accounts. Over 30%, with many followers who joined X in the same weeks, points to bought followers or a bot wave. That reading comes from the seven flags; X publishes no official benchmark.
Why do I have bot followers I never bought?
Bots follow real accounts to look human, to farm follow-backs or to bring you to a scam, so almost every account collects some. A few change nothing. To drop one, block it and then unblock it: it leaves your followers and the block doesn't stay. A sudden batch of brand-new accounts at the top of your list is the pattern to worry about.
How can I tell if a post's engagement is botted?
Score the accounts behind the engagement, not the counts. Sorsa API's POST /retweeters (opens in a new tab) returns the accounts that reposted a post with their full profiles, and POST /comments returns the replies with their authors, so the seven flags work on them as on followers. A crowd of brand-new, empty accounts among them marks bought engagement. Sorsa API doesn't return who liked a post.
Is there a free way to detect bots on X?
Yes. The Twitter Bot Checker reads ten signals of any public account and scores its 200 newest followers, free, with no sign-up. For more accounts or every follower, Sorsa API starts you with 100 free requests: no card, they never expire, and they score up to 20K followers with the script in this guide.
Can you detect bots without the official X API?
Yes. Every signal here is public profile data, and Sorsa API returns it with one key in a header: no X developer account, no OAuth, no approval. The official X API needs all three, charges $0.010 for each user it returns and caps pay-per-use at 3M post reads a month.
#How we checked
On November 2, 2026 we matched every endpoint, field and page size in this guide against the live Sorsa API reference, and the official X API's prices and user fields against docs.x.com. We read Botometer's notes and its API page, X's Help Center pages on automated labels and authenticity, and followed SparkToro's old audit address to where it now leads. The seven flags, the ten signals and their thresholds are the ones the free Twitter Bot Checker uses; the accounts in the examples are made up.
Sources
- Sorsa API reference: Followers (opens in a new tab)
- Sorsa API reference: Account About Info (opens in a new tab)
- X API pricing (docs.x.com) (opens in a new tab)
- X API data dictionary: user fields (docs.x.com) (opens in a new tab)
- About automated account labels (X Help Center) (opens in a new tab)
- Authenticity policy (X Help Center) (opens in a new tab)
- About Botometer (OSoMe, Indiana University) (opens in a new tab)
- Botometer Pro: deprecation and sunset notice (opens in a new tab)