A follower count says how big an audience is. The list behind it says who is in it: which brands and creators follow a café, which accounts a creator follows, which names two accounts in the same niche have in common. Instagram's own API doesn't give you that list, and on some accounts Instagram itself shows only part of it.
This guide exports an account's followers or following to CSV with Python, tells you when Instagram has cut the list short, checks whether one account follows another, and compares two lists. Every response and output below is real, collected through ScrapingBot on October 27, 2026.
Before you start
- A ScrapingBot API key. Create a free account for 100 credits: 20 pages, up to about 1,000 accounts. No card needed.
- Python 3.9 or newer and
pip install requests. - A public Instagram account to start from. Private accounts' lists aren't available.
Meta's API gives counts, not lists
Meta's Instagram API exposes followers_count and follows_count on business and creator accounts, and its business_discovery edge reads those counts for other professional accounts. None of its edges lists the followers or followed accounts themselves (checked October 2026). The lists are visible only in Instagram itself, and that is what /followers/by_user_id and /following/by_user_id return as JSON.
One page of followers
Every Instagram call is a POST to https://scrapingbot.io/api/v1/instagram. Send the account in user_id: the numeric ID, or simply the username, which is looked up for you within the same call and charge.
curl -X POST https://scrapingbot.io/api/v1/instagram \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"endpoint": "/followers/by_user_id", "params": {"user_id": "specialtycoffeeassociation"}}'
The first page of the Specialty Coffee Association's followers, trimmed to one of its 50 accounts:
{
"success": true,
"data": {
"users": [
{
"id": "76612778675",
"username": "crownandscale",
"full_name": "Crown + Scale Collective",
"is_private": false,
"is_verified": false,
"latest_reel_media": 0,
"has_anonymous_profile_picture": false,
"profile_pic_url": "https://instagram.fbhx2-2.fna.fbcdn.net/v/t51.82787-19/…",
…
},
…
],
"user_count": 352974,
"page_size": 50,
"has_more": true,
"next_max_id": "275|QVFEcnNVNGIwaElVb2xzLWRKZGNGY3NBd3d5…",
"should_limit_list_of_followers": false,
"status": "ok",
…
},
"duration": "1.83",
"statusCode": 200,
"creditsUsed": 5
}
| Field | What it is |
|---|---|
users[].id | The numeric account ID. It survives a change of username, so key your records on it. |
username, full_name | The handle and display name. full_name can be empty. |
is_private, is_verified | Whether the follower's own account is private, and whether it has the verified badge. 123 of the 420 followers in our first export were private. |
latest_reel_media | The time of the account's latest story while one is up (a Unix timestamp), otherwise 0. |
profile_pic_url | A signed image link that expires; download the picture soon if you need it. |
user_count | The account's full follower count (following count on /following/by_user_id). Not the number of accounts on this page. |
has_more, next_max_id | Whether another page exists, and the cursor to send back as max_id to get it. |
Each page holds up to 50 accounts and costs 5 credits; the follower objects carry no follower counts of their own. Look an account up with /user/by_username when you need its numbers.
What Instagram lets you see
We exported or sampled nine lists from eight public accounts for this guide. Most paged to the end; three follower lists stopped after one page, whatever their size suggested:
| List | What came back |
|---|---|
| @crownandscale followers | 73 of 81, in 2 pages |
| @guakita_caffe followers | 420 of 422, in 9 pages |
| @cafecoadoclube followers | Pages; first page of 827 checked |
| @specialtycoffeeassociation followers | Pages; first two pages of 352,974 checked |
| @lancehedrick followers | 50 of 134,706, then has_more: false |
| @morgandrinkscoffee followers | 49 of 1,386,873, then has_more: false |
| @nasa followers | 50 of 104 million, then has_more: false |
| @lancehedrick following | 1,281 of 1,285, in 26 pages |
| @jimseven following | 1,122 of 1,118, in 23 pages |
- Some follower lists are cut short. Lance Hedrick has fewer followers than the Specialty Coffee Association, yet his list ended after 50 while the association's kept going. You can't predict it from the size; you can detect it:
has_moreisfalsewhile you hold far fewer accounts thanuser_count. Those short lists also changed from one call to the next. - Counts and lists rarely match exactly. Expect a few accounts fewer than
user_count(420 of 422), occasionally a few more (1,122 of 1,118, as follows changed during the run), and now and then the same account on two pages, so de-duplicate onid. - Following lists paged to the end in every case we tried.
- Private accounts return an empty list with no
user_count, as a successful 5-credit call.
The script
Save this as ig_follows.py. It takes a username, pages through the followers (or the following with --following) up to --pages pages, de-duplicates on the account ID, writes a new timestamped CSV each run (never overwriting an earlier one) and says whether the list was complete, cut short by Instagram, or stopped early.
import csv
import os
import sys
import time
from datetime import datetime
import requests
API = "https://scrapingbot.io/api/v1/instagram"
HEADERS = {"x-api-key": "YOUR_API_KEY"}
FIELDS = ["user_id", "username", "full_name", "is_private", "is_verified", "has_story"]
class Stop(Exception):
"""Why paging stopped early. Whatever was collected is still saved."""
def call(endpoint, params, tries=4):
"""One Instagram call: 5 credits on success. Failed calls are refunded."""
error = None
for attempt in range(tries):
try:
res = requests.post(API, headers=HEADERS, timeout=60,
json={"endpoint": endpoint, "params": params})
body = res.json()
except (requests.RequestException, ValueError) as err: # no answer, or not JSON
error = err
else:
if body.get("success"):
return body["data"]
if res.status_code in (400, 401, 402, 404): # retrying will not help
raise Stop(f"{res.status_code} {body.get('error')}")
error = body.get("error")
time.sleep(2 ** attempt) # 408, 429, 5xx or a network error: wait and try again
raise Stop(f"kept failing: {error}")
def export(account, kind="followers", max_pages=20):
"""Page through an account's followers (or following), 50 accounts a page."""
params = {"user_id": account} # a username works; it is looked up for you
rows, total, pages, end = {}, None, 0, "reached --pages; raise it for more"
while pages < max_pages:
try:
data = call(f"/{kind}/by_user_id", params)
except Stop as reason:
end = str(reason)
break
pages += 1
total = data.get("user_count", total)
for u in data.get("users", []):
rows.setdefault(u["id"], { # pages can repeat an account: keep the first
"user_id": u["id"], "username": u["username"],
"full_name": u.get("full_name") or "",
"is_private": u.get("is_private"), "is_verified": u.get("is_verified"),
"has_story": bool(u.get("latest_reel_media")),
})
if not data.get("has_more") or not data.get("next_max_id"):
end = "end of list"
break
params = {"user_id": account, "max_id": data["next_max_id"]}
return list(rows.values()), total, pages, end
if __name__ == "__main__":
account = sys.argv[1].lstrip("@")
kind = "following" if "--following" in sys.argv else "followers"
max_pages = int(sys.argv[sys.argv.index("--pages") + 1]) if "--pages" in sys.argv else 20
rows, total, pages, end = export(account, kind, max_pages)
if pages == 0:
sys.exit(f"@{account}: {end}")
if total is None and not rows:
sys.exit(f"@{account} is private: Instagram lists {kind} of public accounts only.")
total = len(rows) if total is None else total
# One file per run, never overwritten: two exports are what you compare later.
stamp = f"{account}_{kind}_{datetime.now():%Y%m%d-%H%M%S}"
path, n = f"{stamp}.csv", 1
while os.path.exists(path):
path, n = f"{stamp}-{n}.csv", n + 1
with open(path, "x", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=FIELDS)
writer.writeheader()
writer.writerows(rows)
print(f"@{account}: {len(rows):,} of {total:,} {kind} in {pages} page{'s' if pages != 1 else ''} "
f"({pages * 5} credits), saved to {path}")
print(f" {sum(r['is_private'] for r in rows)} private, "
f"{sum(r['is_verified'] for r in rows)} verified, "
f"{sum(r['has_story'] for r in rows)} with a story up")
if end == "end of list" and len(rows) < 0.9 * total:
print(f" Instagram shows only {len(rows)} of this account's {kind}: "
f"the list ends there, so no further pages exist.")
elif end != "end of list":
print(f" Stopped early: {end}")
- The cursor is opaque; pass it back as is. On following lists and small follower lists
next_max_idlooked like an offset ("50","100"); on the Specialty Coffee Association's followers it was"275|QVFE…". Don't build it yourself. --pagescaps the spend. The default of 20 pages is 100 credits, the free allowance. A page is always 5 credits, so the cost of a run ispages × 5.- An early stop keeps what you have. A bad key, an empty balance (
402) or an unknown account (404) ends the run but still writes the CSV. Timeouts (408), concurrency limits (429), server errors and dropped connections are retried with a growing pause; if they keep failing, the pages collected so far are saved too. Failed calls are refunded. - Pages run one after another. Each needs the previous page's cursor, so one list can't be fetched in parallel. To export many accounts faster, run one list per worker, up to your plan's concurrency limit.
Run it
A 422-follower coffee account, paged to the end:
python ig_follows.py guakita_caffe
@guakita_caffe: 420 of 422 followers in 9 pages (45 credits), saved to guakita_caffe_followers_20261027-153700.csv
123 private, 7 verified, 61 with a story up
A creator whose follower list Instagram cuts short:
python ig_follows.py @lancehedrick
@lancehedrick: 50 of 134,706 followers in 1 page (5 credits), saved to lancehedrick_followers_20261027-153434.csv
10 private, 1 verified, 4 with a story up
Instagram shows only 50 of this account's followers: the list ends there, so no further pages exist.
The same creator's following list is complete, but at 1,285 accounts it needs more than the default 20 pages:
python ig_follows.py lancehedrick --following --pages 30
@lancehedrick: 1,281 of 1,285 following in 26 pages (130 credits), saved to lancehedrick_following_20261027-154255.csv
76 private, 245 verified, 485 with a story up
The 26 pages took 88 seconds in all, between 2.4 and 5.3 seconds each. With the default --pages 20 the run stops at 996 accounts and says so. The CSV has one row per account:
user_id,username,full_name,is_private,is_verified,has_story
8188331,darylrbueno,Daryl Bueno,False,True,True
…
Does one account follow another?
You don't need the whole list to answer that. Add query and Instagram searches the list by username and name in one call. The catch: on lists Instagram limits, the search is silently ignored and you get unrelated accounts back. Those pages say so with should_limit_list_of_followers (or should_limit_list_of_followings) set to true, so check it before you read an empty result as "no":
from ig_follows import call
def in_list(account, name, kind="followers"):
"""True or False, or None when Instagram doesn't let you search this list."""
data = call(f"/{kind}/by_user_id", {"user_id": account, "query": name})
if data.get("should_limit_list_of_" + ("followers" if kind == "followers" else "followings")) is not False:
return None # limited (the search is ignored) or private (no flag, no list)
return any(u["username"].lower() == name.lower() for u in data.get("users", []))
print(in_list("specialtycoffeeassociation", "crownandscale")) # True
print(in_list("lancehedrick", "jimseven", kind="following")) # True
print(in_list("lancehedrick", "jimseven")) # None
print(in_list("cafecoadoclube", "zzqqxxnotarealname")) # False
print(in_list("61111972795", "jimseven")) # None (a private account)
Those are the real answers. A private account's page carries no flag at all (and no list), so the check trusts only an explicit false. The flag is a better guide than size: Lance Hedrick's follower list carried it, and so did the 422-follower list from the first run, which pages to the end but ignores the search too (it returned 25 unrelated accounts for each of the 15 names we tried). Each search is one call, 5 credits.
Accounts two creators both follow
Following lists are where niche research gets interesting: the brands, shops and people that several creators in a space follow are usually the ones that matter in it. Export two lists, then intersect them on the account ID. Save this as both.py:
import csv
import sys
def load(path):
with open(path, newline="", encoding="utf-8") as f:
return {r["user_id"]: r for r in csv.DictReader(f)}
a, b = load(sys.argv[1]), load(sys.argv[2])
both = sorted((r for i, r in a.items() if i in b),
key=lambda r: (r["is_verified"] != "True", r["username"]))
print(f"{len(both)} accounts are in both lists ({len(a):,} and {len(b):,})")
for r in both[:int(sys.argv[3]) if len(sys.argv) > 3 else 20]:
badge = "verified" if r["is_verified"] == "True" else ""
print(f" @{r['username']:<28} {r['full_name'][:30]:<30} {badge}")
python ig_follows.py jimseven --following --pages 30
python both.py lancehedrick_following_20261027-154255.csv jimseven_following_20261027-154348.csv 8
128 accounts are in both lists (1,281 and 1,122)
@acaiacoffee Acaia verified
@agarojewska Agnieszka Rojewska verified
@baristahustle Barista Hustle verified
@baristamagazine Barista Magazine verified
@coffeeandlucas Federico Lucas Pezzetta 📸 verified
@coffeecollectif Coffee Collective verified
@cometeer Cometeer Coffee verified
@coolroastcurves Cool Roast Curves verified
James Hoffmann's following list took 23 pages (115 credits), so the comparison cost 245 credits for both lists, about 4.5 cents on Starter. 36 of the 128 shared accounts are verified; the rest include grinder makers, roasters and a coffee festival.
New and lost followers between two exports
Exporting the same account on a schedule and comparing the files looks like an easy way to see who followed and who left. Save this as compare.py:
import csv
import sys
def load(path):
with open(path, newline="", encoding="utf-8") as f:
return {r["user_id"]: r for r in csv.DictReader(f)}
old, new = load(sys.argv[1]), load(sys.argv[2])
gained = [r["username"] for i, r in new.items() if i not in old]
missing = [r["username"] for i, r in old.items() if i not in new]
print(f"{len(old)} -> {len(new)} accounts: {len(gained)} new, {len(missing)} missing")
We exported the 422-follower account twice, ten minutes apart. Each run writes its own timestamped file, so both are there to compare:
python compare.py guakita_caffe_followers_20261027-153700.csv guakita_caffe_followers_20261027-154637.csv
420 -> 406 accounts: 1 new, 15 missing
user_count said 422 both times. The second export's last page simply held 6 accounts instead of 21, so at least 14 of the 15 "missing" followers hadn't gone anywhere. Treat a follower as lost only when they are missing from two exports in a row, compare exports whose row count is close to user_count, and on lists without the limit flag confirm with in_list() before you act on it.
Cost and speed
- 5 credits per page of up to 50 accounts. 1,000 accounts is 20 pages, 100 credits, about 1.8 cents on Starter ($49.99 for 275,000 credits). 10,000 followers is about 1,000 credits, roughly 18 cents.
- About 1,000 accounts a minute per list. Pages took 1.3 to 6.2 seconds in our runs. The Specialty Coffee Association's 352,974 followers would be about 7,060 pages, 35,300 credits and several hours; we checked only its first two pages.
- Failed calls cost nothing, and a missing account is a free
404. A private account's empty list is a successful call, 5 credits.
Using follower data responsibly
Follower lists are lists of people. Keep only the columns you need, don't use them to message people in bulk, and if you work with data about people in the EU or UK, the GDPR applies to what you store. The terms of service cover what you may do with data from ScrapingBot.
Where to go from here
- Need the numbers behind each account? Look up follower counts in bulk, then rank the accounts by engagement rate.
- Starting from a topic rather than an account: search Instagram accounts, hashtags and posts by keyword.
- One account, no code: the free Instagram profile stats tool. For TikTok, try the engagement rate calculator or the sound tracker.
- Comparing providers? The best Instagram scraper APIs in 2026 covers cost and coverage. The followers reference lists every parameter, and the Instagram API page every endpoint.
Common questions
Is there an official Instagram API for follower lists?
No. Meta's Instagram API gives followers_count and follows_count for business and creator accounts, but none of its endpoints lists the accounts behind those numbers (checked October 2026). To get the list of a public account's followers or following, you need a data API such as ScrapingBot's /followers/by_user_id and /following/by_user_id.
How do I get all followers of an Instagram account?
Call /followers/by_user_id with the account's username or numeric ID, then send each page's next_max_id back as max_id while has_more is true. Each page holds up to 50 accounts and costs 5 credits. In our test, a 422-follower account came back as 420 accounts in 9 pages.
Why do I only get about 50 followers for some accounts?
Instagram cuts some accounts' follower lists short. For Lance Hedrick (134,706 followers), Morgan Eckroth (1.39 million) and NASA (104 million), the first page was the only page: has_more was false. The Specialty Coffee Association, with 352,974 followers, paged normally, so it is not a size threshold. Compare the accounts you got with user_count to spot it.
Can I search an account's followers for one name?
Often. Add query with a username or name and Instagram searches the list. When a page carries should_limit_list_of_followers: true, the search is ignored and you get unrelated accounts back, so check that flag before trusting a "not found". In our checks the search worked on the Specialty Coffee Association's followers and on Lance Hedrick's following, and was ignored on his followers.
Can I get the followers of a private account?
No. For a private account the call succeeds with an empty users list and no user_count, and it costs 5 credits like any page. Only public accounts' lists are available.
How much does it cost to export 10,000 followers?
About 200 pages at 5 credits each: 1,000 credits, roughly $0.18 on the Starter plan ($49.99 for 275,000 credits). The 100 free credits cover 20 pages, up to about 1,000 accounts.