# Export Twitter/X followers to CSV

> Download the follower list of any public X account to a CSV file with handle, name, bio, follower counts and join date. Python and Node.js scripts, costs and the limits X sets.

HTML page: https://www.twittrapi.com/blog/export-twitter-followers-to-csv
Published 2026-10-11.

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](https://www.twittrapi.com/start); new accounts get $0.10 of test credit, enough for several thousand followers. Store it in an environment variable:

```bash
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`](https://www.twittrapi.com/docs/reference/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}`](https://www.twittrapi.com/docs/reference/user-by-username-username) if you will call it many times. Add `format=simple` for flat user objects.

```bash
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](https://www.twittrapi.com/docs/simple-format#user)).

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.

Figure: paging through followers with a cursor. Call /user/{userId}/followers, pass the returned cursor back as `cursor` for the next page, and stop at the last page. Every page is one call ($0.065 to $0.09 per 1,000 calls).

```python
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`](https://www.twittrapi.com/docs/reference/user-userid-followings). For verified followers only, use [`GET /user/{userId}/verifiedFollowers`](https://www.twittrapi.com/docs/reference/user-userid-verifiedfollowers).

### With the Python SDK

The [`twittrapi` package](https://www.twittrapi.com/docs/integrations#sdks) retries 429 and 5xx responses for you, which matters on long exports:

```python
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:

```python
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

```bash
npm install twittrapi
```

```js
import { 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](https://www.twittrapi.com/docs/billing)). For a quick follower count without exporting, one call to [`GET /user/by/username/{username}`](https://www.twittrapi.com/docs/reference/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.followers` and contact [support](mailto:support@twittrapi.com) 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](https://www.twittrapi.com/aup) 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](https://www.twittrapi.com/docs/reference/user-userid-followers) and [Following](https://www.twittrapi.com/docs/reference/user-userid-followings) endpoint reference (page sizes, parameters)
- [Simple format: user fields](https://www.twittrapi.com/docs/simple-format#user)
- [Billing and refunds](https://www.twittrapi.com/docs/billing)

TwittrAPI is not affiliated with X Corp. It reads only public data.
