X has no button that exports a follower list. Your own data archive only lists follower IDs, and it takes a day or more to arrive. Browser extensions that scroll the follower page for you work for a few hundred accounts and then hit X's limits. This guide does it with a short script instead: it pages through the follower list of any public account and writes one CSV row per follower, with handle, name, bio, location, follower and following counts, verification and join date.
It uses TwittrAPI, a third-party API for public X data. You need an API key from us, but no X developer account and no X login.
Step 1: Get an API key
Create a key on /start; new accounts get $0.10 of test credit, enough for several thousand followers. Store it in an environment variable:
export TWITTRAPI_API_KEY="your-key"Each page of followers is one billed call. See the cost table further down before you export an account with millions of followers.
Step 2: Request the first page of followers
The endpoint is GET /user/{userId}/followers. Pass -1 as the ID and the handle as username, or look up the numeric ID first with GET /user/by/username/{username} if you will call it many times. Add format=simple for flat user objects.
curl -G 'https://api.twittrapi.com/user/-1/followers' \
-H "X-API-KEY: $TWITTRAPI_API_KEY" \
--data-urlencode 'username=nasa' \
--data-urlencode 'count=100' \
--data-urlencode 'format=simple'The response is {"data": [users], "nextCursor": "...", "hasNextPage": true}. Each user has id, username, name, description, location, url, createdAt, verified, verifiedType, protected, avatarUrl and counts with followers, following, tweets, likes, media and listed (user fields).
Followers come newest first: the accounts that followed most recently are on page one.
Step 3: Page through the whole list and write the CSV
This Python script follows nextCursor until hasNextPage is false and writes each page to the file as it arrives, so a stopped run still leaves you with a usable file.
Paging through followers with a cursor
Call 1
GET /user/{userId}/followers
→ ~50–70 followers
nextCursor: "A1"
Call 2
?cursor=A1
→ ~50–70 followers
nextCursor: "B2"
Call n
?cursor=…
→ last followers
hasNextPage: false
hasNextPage is false instead of counting.import csv
import os
import sys
import requests
API = "https://api.twittrapi.com"
HEADERS = {"X-API-KEY": os.environ["TWITTRAPI_API_KEY"]}
FIELDS = ["id", "username", "name", "description", "location", "url", "followers", "following",
"tweets", "verified", "verifiedType", "protected", "createdAt"]
def export_followers(handle: str, path: str, max_pages: int = 1000) -> int:
cursor, rows, pages = None, 0, 0
with open(path, "w", newline="", encoding="utf-8") as f:
w = csv.DictWriter(f, fieldnames=FIELDS)
w.writeheader()
while pages < max_pages:
params = {"username": handle, "count": 100, "format": "simple"}
if cursor:
params["cursor"] = cursor
r = requests.get(f"{API}/user/-1/followers", headers=HEADERS, params=params, timeout=60)
r.raise_for_status()
page = r.json()
pages += 1
for u in page["data"]:
w.writerow({
**{k: u[k] for k in ["id", "username", "name", "description", "location", "url",
"verified", "verifiedType", "protected", "createdAt"]},
"followers": u["counts"]["followers"],
"following": u["counts"]["following"],
"tweets": u["counts"]["tweets"],
})
rows += 1
f.flush()
print(f"page {pages}: {rows} followers", file=sys.stderr)
if not page["hasNextPage"]:
break
cursor = page["nextCursor"]
return rows
if __name__ == "__main__":
handle = sys.argv[1]
export_followers(handle, f"{handle}_followers.csv")Run it with python export_followers.py nasa. For the accounts someone follows rather than their followers, change the path to GET /user/{userId}/followings. For verified followers only, use GET /user/{userId}/verifiedFollowers.
With the Python SDK
The twittrapi package retries 429 and 5xx responses for you, which matters on long exports:
from twittrapi import TwittrAPI
client = TwittrAPI(response_format="simple", max_retries=5)
for page in client.paginate(client.users.followers, username="nasa", count=100, max_pages=None):
for u in page["data"]:
print(u["username"], u["counts"]["followers"])max_pages=None removes the default limit of 10 pages. Write the rows with the same csv.DictWriter code as above.
Step 4: Resume a long export
A list of a million followers is thousands of calls and takes a while. Save the cursor after every page and pass it back if the script stops:
import json
from pathlib import Path
state = Path("nasa_cursor.json")
cursor = json.loads(state.read_text())["cursor"] if state.exists() else None
for page in client.paginate(client.users.followers, username="nasa", count=100,
max_pages=None, cursor=cursor):
# ... write the rows (open the CSV in append mode "a" when resuming) ...
if page["nextCursor"]:
state.write_text(json.dumps({"cursor": page["nextCursor"]}))Cursors are X's own and expire after a while, so resume within hours, not weeks. Pagination is sequential: page 2 needs the cursor from page 1, so one export runs at about one page per second however high your rate limit is. To export several accounts, run them in parallel.
Node.js version
npm install twittrapiimport { createWriteStream } from "node:fs";
import { TwittrAPI } from "twittrapi";
const client = new TwittrAPI({ format: "simple" }); // key from TWITTRAPI_API_KEY
const cols = ["id", "username", "name", "description", "location", "followers", "following", "verified", "createdAt"];
const cell = (v) => `"${String(v ?? "").replace(/"/g, '""')}"`;
const out = createWriteStream("nasa_followers.csv");
out.write(cols.join(",") + "\n");
for await (const page of client.paginate(
client.users.followers,
{ username: "nasa", count: 100 },
{ maxPages: Infinity },
)) {
for (const u of page.data.data) {
const row = { ...u, followers: u.counts.followers, following: u.counts.following };
out.write(cols.map((c) => cell(row[c])).join(",") + "\n");
}
}
out.end();Every value is quoted and inner quotes are doubled, which is what CSV readers expect for bios that contain commas, quotes or line breaks.
What it costs
X decides how many users a page holds. For follower lists it is usually around 50 to 70 per page, sometimes more with count=100. At 50 to 70 per page:
| Followers | Calls (approx.) | Cost on Standard ($0.09 per 1,000 calls) |
|---|---|---|
| 10,000 | 140 to 200 | under $0.02 |
| 100,000 | 1,400 to 2,000 | about $0.13 to $0.18 |
| 1,000,000 | 14,000 to 20,000 | about $1.30 to $1.80 |
Calls that fail on our side are refunded (billing). For a quick follower count without exporting, one call to GET /user/by/username/{username} returns counts.followers.
Limits to know about
- Protected accounts: the follower list of a private account is not public, so you get nothing back.
- The total will not match exactly. Suspended and deactivated accounts drop out of the list, and the count on the profile is X's own number. Expect a small difference.
- Very large accounts: X may end the list before the profile count is reached. If you need complete coverage of a huge account, check the row count against
counts.followersand contact support before you start. - Personal data: a follower list is personal data under the GDPR and similar laws. Have a reason to collect it, keep only the columns you need and do not use it for spam. Our acceptable use policy applies.
Frequently asked questions
Can you export Twitter followers to CSV for free?
X itself has no CSV export. Your X data archive is free but only contains follower IDs, as JSON. Browser extensions have free tiers of a few hundred accounts. With TwittrAPI the test credit for new accounts covers several thousand followers; after that a list of 10,000 costs a few cents.
Can I export the followers of an account that is not mine?
Yes, if the account is public. The follower list is visible to anyone on X, and the API reads the same list. Private (protected) accounts return no followers.
How long does an export take?
About one page of 50 to 70 followers per second, because each page needs the cursor of the previous one. 10,000 followers take three to four minutes; a million take several hours. Several accounts can run in parallel.
Which columns can the CSV have?
Every field of the user object: ID, handle, display name, bio, location, website, join date, avatar and banner URLs, verification, protected flag, and counts of followers, following, tweets, likes, media and lists.
How do I export the accounts someone follows?
Use the followings endpoint instead of followers. The script stays the same apart from the path.
Sources
- Followers and Following endpoint reference (page sizes, parameters)
- Simple format: user fields
- Billing and refunds
TwittrAPI is not affiliated with X Corp. It reads only public data.



