Twitter (X) Bulk Unfollow + Follower Remover — API Guide

By Alex Chen•7 min read

People searching for a Twitter follower remover usually mean one of two things. Some want to unfollow a long list of accounts they no longer read. Others want to remove people from their own follower list. These are separate actions on X, and only the first one can be done with the twitterapi.io API.

This guide covers both honestly: the exact endpoint, body and price for bulk unfollowing, runnable Python that works at a human pace and keeps a log, the read endpoints that help you decide whom to unfollow, and where follower removal actually lives.

01 — Section

Two intents — clarify which you mean

Unfollow (your following list): you stop following an account. Your following count drops by one and their follower count drops by one. twitterapi.io supports this with POST /twitter/unfollow_user_v2.

Remove a follower (your follower list): an account follows you and you want that to stop. twitterapi.io does not offer this. There is no remove-follower endpoint, and there are no block or unblock endpoints either, so the block-then-unblock trick cannot be scripted through our API.

Where follower removal works: in the X app or on x.com, open the follower's profile or your followers list, open the menu next to the account and choose "Remove this follower".

The rest of this guide is about bulk unfollowing, with one section on follower removal near the end.

02 — Section

Runnable — log in once, then unfollow one account

The unfollow call acts as your X account, so it needs a login cookie. POST /twitter/user_login_v2 takes user_name, email, password and proxy (all required) plus an optional totp_secret, and returns login_cookies. The API reference strongly recommends enabling 2FA and passing the TOTP secret, because a cookie from an account without 2FA may not work for write actions. A cookie from this login only works with endpoints that carry the v2 suffix.

proxy is your own residential proxy in the form http://username:password@ip:port. Use the same value for login and for unfollow calls. Store the cookie and reuse it instead of logging in on every run.

user_id is the numeric ID, not the handle. GET /twitter/user/info?userName=... returns the profile under data, and data.id is the value to send.

A 200 response carries status (success or error) and message. Check status rather than assuming the unfollow happened.

python
import os, requests

BASE = "https://api.twitterapi.io"
HEADERS = {"X-API-Key": os.environ["TWITTERAPI_IO_KEY"]}
PROXY = os.environ["RESIDENTIAL_PROXY"]  # your own proxy: http://username:password@ip:port

def login() -> str:
    """Log in once and return login_cookies. Store it and reuse it."""
    r = requests.post(
        f"{BASE}/twitter/user_login_v2",
        headers=HEADERS,
        json={
            "user_name": os.environ["X_USER_NAME"],
            "email": os.environ["X_EMAIL"],
            "password": os.environ["X_PASSWORD"],
            "totp_secret": os.environ["X_TOTP_SECRET"],  # optional, recommended
            "proxy": PROXY,
        },
        timeout=120,
    )
    r.raise_for_status()
    body = r.json()
    if body.get("status") != "success":
        raise RuntimeError(f"login failed: {body.get('message')}")
    return body["login_cookies"]

def user_id_for(handle: str) -> str:
    """Resolve a handle to the numeric user ID the unfollow call needs."""
    r = requests.get(
        f"{BASE}/twitter/user/info",
        headers=HEADERS,
        params={"userName": handle},
        timeout=30,
    )
    r.raise_for_status()
    return r.json()["data"]["id"]

def unfollow(login_cookies: str, user_id: str) -> dict:
    r = requests.post(
        f"{BASE}/twitter/unfollow_user_v2",
        headers=HEADERS,
        json={"login_cookies": login_cookies, "user_id": user_id, "proxy": PROXY},
        timeout=60,
    )
    r.raise_for_status()
    return r.json()  # {"status": "success" | "error", "message": "..."}

cookies = os.environ.get("X_LOGIN_COOKIES") or login()
target_id = user_id_for("some_account")
print(unfollow(cookies, target_id))
03 — Section

Decide whom to unfollow with read endpoints

A good cleanup starts with data, not with a blind loop. These read endpoints only need the X-API-Key header, no login cookie and no proxy.

EndpointWhat it gives youPrice
GET /twitter/user/followingsAccounts a user follows, newest first, up to 200 per page (pageSize 20-200, cursor), with id and userName for each1 credit per account at 200 per page ($0.01 per 1,000)
GET /twitter/user/followersAccounts that follow a user, newest first, same page size1 credit per account at 200 per page ($0.01 per 1,000)
GET /twitter/user/check_follow_relationshipdata.following and data.followed_by for a source and target handle100 credits per call ($0.001)
GET /twitter/user/infoOne profile under data, including id, followers, statusesCount18 credits per call ($0.00018)
GET /twitter/user/last_tweetsUp to 20 recent posts per page with createdAt, by userId or userName15 credits per post returned ($0.00015)

Prices are list prices, where $1 buys 100,000 credits. Smaller follower and following pages cost more per account: 2 credits each for 100-199 per page and 3 credits each below 100, so ask for 200.

Inactive accounts: read the newest createdAt from GET /twitter/user/last_tweets. If the latest post is older than your cutoff, the account is a candidate.

Mutual follows: call GET /twitter/user/check_follow_relationship with your handle as source_user_name. If data.followed_by is true, the account follows you back, and you may want to keep it.

04 — Section

Pacing — keep it human

An unfollow sent through the API is still an action taken by your X account, and X's platform rules on automation apply to it. Run the cleanup the way a person would, only without the clicking.

Small daily batches: pick a modest number per day and spread a long list over days or weeks. The script below uses 20 per run. That is a conservative setting we chose for the example, not a limit published by X.

A few seconds between calls, with jitter: the script waits a random 4 to 9 seconds after each unfollow.

Do not mix follow and unfollow runs: following and unfollowing the same accounts in cycles is the pattern X's rules describe as follow churn. A cleanup is a one-way job.

Log every action: write the account ID, handle, time and API response to a local file. The log lets you resume the next day, audit what happened and re-follow an account you removed by mistake.

X does not publish exact numbers for how many unfollows an account can make in a day, so this guide does not quote one. For limits on the official API, see X's rate limits reference in the sources.

05 — Section

What a cleanup costs

The unfollow call itself costs $0.0006 (60 credits) per call. The reads around it are billed per item returned.

StepUnit priceExample
List who you follow1 credit per account at 200 per page2,000 accounts = 2,000 credits = $0.02
Check last activity15 credits per post returned, up to 20 posts per pageone account = up to 300 credits = $0.003
Check for a mutual follow100 credits per call100 checks = $0.10
Unfollow$0.0006 per call (60 credits)100 unfollows = $0.06

The login call is billed separately ($0.10 per login) and you only need it when you do not have a working cookie.

06 — Section

Removing a follower — not through this API

twitterapi.io has no endpoint that removes a follower from your account, and no block or unblock endpoint. Any guide that shows such a call against our API is wrong, and the call will fail.

In the X app or on x.com: open your followers list or the follower's profile, open the menu next to the account, and choose "Remove this follower". The account stops following you and can follow you again later unless you block it.

What our API can still do here: GET /twitter/user/followers lists your followers with profile data, so you can build the list of accounts you want to remove (for example accounts with no posts) and then work through it by hand in the app.

Follower removal in bulk is offered by some third-party tools through their own interface. Check how a tool gets access to your account before you connect it.

07 — Section

Alternative — the official X API

X's own API has an unfollow endpoint: DELETE /2/users/{source_user_id}/following/{target_user_id}, documented at docs.x.com/x-api/users/follows/introduction.

Auth is OAuth 2.0 user context with the follows.write scope, so the account owner has to authorize your app.

Access and rate limits depend on your X API plan. Read the current values at docs.x.com/x-api/fundamentals/rate-limits before you plan a bulk job.

It fits when you already run an app on X's developer platform and want X-issued tokens. twitterapi.io fits when you want an API key, a login cookie and pay-per-call pricing without an X developer app.

08 — Section

Comparison — 3 unfollow / removal paths

Dimensiontwitterapi.ioX official API3rd-party tools (Circleboom / Crowdfire)
UnfollowPOST /twitter/unfollow_user_v2DELETE /2/users/{source_user_id}/following/{target_user_id}through the tool's own interface
Follower removalnot availablesee X's API docsvaries by tool
AuthX-API-Key header + login_cookies + your own proxyOAuth 2.0 user context, follows.writesign in with your X account
Target identifiernumeric user_idnumeric user IDschosen in the interface
Scriptableyesyesno, interface-driven
Cost$0.0006 per unfollow call (60 credits)per X's current API pricingper the tool's own pricing
Best forscripted cleanup with your own selection rulesapps already on X's developer platformone-off cleanup without code
python
# Bulk unfollow, human-paced: list who you follow, keep mutuals, pick accounts
# that have not posted in a long time, unfollow a small batch, log every action.
import os, json, time, random, requests
from datetime import datetime, timezone, timedelta

BASE = "https://api.twitterapi.io"
HEADERS = {"X-API-Key": os.environ["TWITTERAPI_IO_KEY"]}
PROXY = os.environ["RESIDENTIAL_PROXY"]        # http://username:password@ip:port
LOGIN_COOKIES = os.environ["X_LOGIN_COOKIES"]  # login_cookies from /twitter/user_login_v2
MY_HANDLE = os.environ["MY_HANDLE"]

DAILY_BATCH = 20        # your own choice; keep it small
INACTIVE_DAYS = 180
LOG_FILE = "unfollow_log.jsonl"

def get(path_url: str, params: dict) -> dict:
    r = requests.get(path_url, headers=HEADERS, params=params, timeout=30)
    r.raise_for_status()
    return r.json()

def list_following(handle: str) -> list:
    users, cursor = [], ""
    while True:
        body = get(f"{BASE}/twitter/user/followings",
                   {"userName": handle, "pageSize": 200, "cursor": cursor})
        users.extend(body.get("followings", []))
        cursor = body.get("next_cursor")
        if not body.get("has_next_page") or not cursor:
            return users

def last_post_time(user_id: str):
    body = get(f"{BASE}/twitter/user/last_tweets", {"userId": user_id})
    payload = body.get("data") or {}
    times = [
        datetime.strptime(t["createdAt"], "%a %b %d %H:%M:%S %z %Y")
        for t in payload.get("tweets", [])
        if t.get("createdAt")
    ]
    return max(times) if times else None

def follows_me(handle: str) -> bool:
    body = get(f"{BASE}/twitter/user/check_follow_relationship",
               {"source_user_name": MY_HANDLE, "target_user_name": handle})
    return bool(body.get("data", {}).get("followed_by"))

def unfollow(user_id: str) -> dict:
    r = requests.post(
        f"{BASE}/twitter/unfollow_user_v2",
        headers=HEADERS,
        json={"login_cookies": LOGIN_COOKIES, "user_id": user_id, "proxy": PROXY},
        timeout=60,
    )
    r.raise_for_status()
    return r.json()

def already_done() -> set:
    if not os.path.exists(LOG_FILE):
        return set()
    with open(LOG_FILE, encoding="utf-8") as f:
        rows = [json.loads(line) for line in f if line.strip()]
    return {row["user_id"] for row in rows if row["status"] == "success"}

def log(row: dict) -> None:
    row["at"] = datetime.now(timezone.utc).isoformat()
    with open(LOG_FILE, "a", encoding="utf-8") as f:
        f.write(json.dumps(row, ensure_ascii=False) + "\n")

cutoff = datetime.now(timezone.utc) - timedelta(days=INACTIVE_DAYS)
done = already_done()
following = list_following(MY_HANDLE)
print(f"following: {len(following)}")

unfollowed = 0
for user in reversed(following):          # oldest follows first
    if unfollowed >= DAILY_BATCH:
        break
    user_id, handle = user.get("id"), user.get("userName")
    if not user_id or not handle or user_id in done:
        continue
    last = last_post_time(user_id)
    if last is None or last > cutoff:      # no readable posts, or still active: keep
        continue
    if follows_me(handle):                 # mutual follow: keep
        continue
    try:
        result = unfollow(user_id)
        status, msg = result.get("status"), result.get("message")
    except requests.RequestException as exc:
        status, msg = "error", str(exc)
    log({"user_id": user_id, "handle": handle, "last_post": last.isoformat(),
         "status": status, "msg": msg})
    print(f"@{handle}: {status}")
    if status == "success":
        unfollowed += 1
    time.sleep(random.uniform(4, 9))       # a few seconds, with jitter

print(f"unfollowed {unfollowed} this run; run again tomorrow for the next batch")
09 — Questions

Questions readers ask

Which endpoint unfollows an account?

POST /twitter/unfollow_user_v2 on twitterapi.io. Send login_cookies, user_id and proxy in the JSON body and your key in the X-API-Key header. All three body fields are required.

Can I pass a handle instead of a user ID?

No. user_id is the numeric ID. Resolve a handle with GET /twitter/user/info?userName=... and read data.id, or take the id field from each entry returned by GET /twitter/user/followings.

Can I remove followers with the twitterapi.io API?

No. There is no remove-follower endpoint and no block or unblock endpoint. Remove a follower in the X app or on x.com: open the follower's profile or your followers list, open the menu and choose "Remove this follower". You can use GET /twitter/user/followers to build the list of accounts first.

Where do I get login_cookies?

From POST /twitter/user_login_v2. It takes user_name, email, password and proxy, plus an optional totp_secret, and returns login_cookies. The API reference recommends enabling 2FA and passing the TOTP secret. Store the cookie and reuse it; it only works with endpoints that have the v2 suffix.

What does a bulk unfollow cost?

Each unfollow call costs $0.0006 (60 credits), so 100 unfollows cost $0.06. Listing who you follow costs 1 credit per account at 200 per page, which is $0.01 per 1,000 accounts at $1 per 100,000 credits.

How many accounts can I unfollow per day?

X does not publish an exact daily number, so this guide does not quote one. Keep batches small, wait a few seconds between calls, spread a long list over several days and follow X's automation rules. The example script uses 20 per run as a conservative setting of our own.

Can I follow an account again after unfollowing it?

Yes. POST /twitter/follow_user_v2 takes the same body (login_cookies, user_id, proxy) and has the same price of $0.0006 per call. Keep a log of what you unfollow so you have the IDs.

Will the person I unfollow get notified?

X does not send a notification when you unfollow someone. They can still notice if they look at their follower list or use a tool that compares follower snapshots.

10 — Further reading

Continue

Sources & further reading
More from this series
Build it

Stop reading. Start building.

Starter credits cover real testing on real data. Google sign-in, no card, no application queue.

Get an API key

Already building with TwitterAPI.io? Publish an honest tutorial or build log about it and earn up to $200 a month in API credits.