# Instagram Followers Scraper — Followers & Following by Username (`steadyfetch/instagram-followers-scraper`) Actor

From $0.50 per 1,000 followers. The followers and the following list of any public Instagram account, by handle or profile URL — handle, name, user ID, verified and private flags. No login, no cookies. Your result limit is exact: ask for 500 and you get 500. A row we cannot deliver is never charged.

- **URL**: https://apify.com/steadyfetch/instagram-followers-scraper.md
- **Developed by:** [Steadyfetch Team](https://apify.com/steadyfetch) (community)
- **Categories:** Social media, Lead generation, Automation
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 followers

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Instagram Followers Scraper — Followers & Following by Username

Paste the handles you care about and get the people who follow them, the people they follow, or both: handle, full name, user id, profile URL, profile picture, verified and private flags, and the position each one held in the list. No login, no cookies, no proxy to configure. **Your result limit is exact — ask for 500 and you get 500**, per account and per list, and a row we could not deliver is never charged. Nothing is charged for starting a run, which most actors in this niche do charge for.

**Using an AI agent?** Pin this actor in Apify's MCP server with one link: `https://mcp.apify.com?tools=steadyfetch/instagram-followers-scraper`

- **Actor id:** `steadyfetch/instagram-followers-scraper`
- **Input:** `{ "usernames": ["nasa"], "mode": "followers", "resultsLimit": 500 }` — `usernames` is the one field you have to set.
- **Cap the bill:** set `maxTotalChargeUsd` on the run (a run option, not Actor input), e.g. `0.50` — the run stops cleanly when it reaches it.
- **Price:** from **$0.50 per 1,000 rows** on Gold and above ($0.0005 a row), $2.00 per 1,000 on the Apify free plan — the same price for a followers row and a following row. No start fee, and a row we could not deliver is never charged. Full table below.
- **How often it changes:** follower lists churn daily on an active account, so a weekly or daily schedule is where this earns its keep — the rows you already have are skipped rather than charged again, so a schedule pays only for what changed.

*Unofficial. Not affiliated with, endorsed by, or sponsored by Instagram or Meta Platforms, Inc.*

### What you get

One row per person, in the order the list served them:

| Column | What it holds |
|---|---|
| `username` · `fullName` · `userId` | who they are, and Instagram's own numeric id |
| `profileUrl` · `profilePicUrl` | their profile link, and their picture |
| `isPrivate` · `isVerified` · `accountBadges` | their account flags, as Instagram reports them |
| `followerOf` | **this person follows the named account** (a `followers` run) |
| `followingOf` | **the named account follows this person** (a `following` run) |
| `direction` · `seedUsername` · `seedUserId` · `listPosition` | which list this row came from, for which of your handles, and where it sat in it |
| `source` · `input` · `sourceIndex` · `scrapedAt` | always `instagram`, the exact value you typed, its place in your list, and when it was read |
| `repeat` · `charged` · `status` · `retryable` · `statusReason` | whether your account already had this row, whether it was billed, and why |

`followerOf` and `followingOf` are **not the same fact**, and exactly one of them is filled on any row. A run in `both` mode returns both lists, and a person who appears in each gets two rows — that is two different facts, and the mutual-follow check people usually want is a join on those two columns.

### What a row looks like

One delivered row from a real run — the `following` list of `nasa`, 2026-09-11. Every person on the list is one `follower` charge, and `direction` plus `listPosition` say which list the row came from and where it sat in it. Profile pictures are signed and time-limited by Instagram, so they are shown here as placeholders.

```json
{
  "userId": "3443079229",
  "username": "jaxajp",
  "fullName": "JAXA（宇宙航空研究開発機構）",
  "profileUrl": "https://www.instagram.com/jaxajp/",
  "profilePicUrl": "https://…",
  "isPrivate": false,
  "isVerified": true,
  "accountBadges": [],
  "followerOf": null,
  "followingOf": "nasa",
  "direction": "following",
  "seedUsername": "nasa",
  "seedUserId": "528817151",
  "listPosition": 3,
  "source": "instagram",
  "input": "nasa",
  "sourceIndex": 0,
  "scrapedAt": "2026-09-11T10:25:49.238Z",
  "repeat": false,
  "charged": true,
  "status": "delivered",
  "retryable": false,
  "statusReason": null,
  "#k": "following:nasa:3443079229",
  "#ce": {"follower":1},
  "#ask": {"delivered":1}
}
```

### Price

One charged event, `follower`, and it lands only on a row that was actually delivered. It covers **both** directions at the same price — a row from the followers list and a row from the following list cost the same, because they cost us the same.

| Apify plan | Per row | Per 1,000 rows |
|---|---|---|
| Free plan | $0.002 | $2.00 |
| Bronze | $0.0012 | $1.20 |
| Silver | $0.00075 | $0.75 |
| Gold and above | $0.0005 | **$0.50 per 1,000** |

Platform usage is included in that price — there is no separate compute bill on top, no start fee, and no second charge for "details": every column below is in the row at that one price. Set `maxTotalChargeUsd` on the run and it is a hard ceiling: the run stops cleanly under it and the last row says what is left.

### Honest rows: what "not charged" actually means

Every run closes its own books. A row that was asked for and not delivered leaves an uncharged row of its own saying which of ten things happened, in plain words, and none of them is billed:

- **`account_not_found`** — Instagram has no account at that handle. Proved by Instagram's own account lookup, not guessed from an empty list.
- **`private_account`** — the account is private, so Instagram publishes no follower list for it and nobody can read one without being approved by the account. Answered from the account lookup, before a single list read is spent.
- **`capped_by_instagram`** — the account is large enough that Instagram stops showing its list. See the section below; this is the one every competing actor leaves out.
- **`no_list`** — the list ran out before your limit did. That is the whole list Instagram publishes for the account, not a limit on the run.
- **`vendor_unavailable`** — the read did not go through. Temporary, says nothing about the account, and a re-run is the fix.
- **`source_refused`** — Instagram refused that read outright, and it refuses it the same way every time, so a re-run cannot change it. Check the handle in a browser; if it works there, say so on the Issues tab.
- **`unsupported_shape`** — Instagram answered in a shape this actor does not read yet. That is on us, not on the account, and a re-run gets the same answer — tell us on the Issues tab which handle it was and support for it will be added.
- **`vendor_budget`** — this actor reached its own monthly collection allowance and stopped rather than collecting more.
- **`stopped_at_limit`** — one of your own limits, the run clock, or one of this actor's own reading bounds stopped it first: it stops after three pages in a row that held nothing but rows you already have, and it reads at most 2,200 pages for any one list. The row names which one, so you raise the right setting. When a list is left unfinished this way, the run's last row carries a `resumeCursor`: pass that object back in **Continue a stopped run**, with the same usernames, and the next run picks up at exactly that point — no page is read twice, nothing already delivered is charged again, and nothing in between is skipped.
- **`user_input`** — a handle, a list value or a setting could not be used. The row names the field and the fix, and the rest of your handles still run.

The last row of every run is a receipt: delivered, asked for, what was skipped, what stopped it, and the charged-event totals, so your invoice reconciles from the dataset itself. A time limit ends the collecting, never the delivering — rows already in hand are always written out.

### Instagram caps very large accounts, and this actor says so

Instagram limits how much of a very large account's follower list it will show to anyone. No tool can go past it, however it is priced.

Measured live on 2026-09-11: **`nasa`, with 104,378,996 followers, returned 49 follower rows and then stopped.** An account with 19 followers returns all 19. The two look identical on the wire, so this actor decides the difference against the account's own stated follower count rather than guessing from a short list.

When it happens you get a `capped_by_instagram` row naming how many rows were reached and how many the account reports having — **and the rows past the cap are not charged**. This is Instagram's own limit, not a setting on your run, and raising your result limit will not move it. The "following" list is not capped this way in our measurements: nasa's 91 following walked to the end in four pages.

So: this actor is at its best on the accounts whose lists are actually reachable — small and mid-size accounts, and the full "following" list of accounts of any size.

### Settings

- **Usernames** — one handle or profile URL per line: `nasa`, `@nasa`, or the profile URL you copied out of the address bar. A pasted block of links is split on link boundaries, and a value that cannot be used becomes one uncharged row naming the fix while the rest of your handles still run.
- **Which list** (`mode`) — `followers`, `following` or `both`. Default `followers`.
- **Max results per account** (`resultsLimit`) — per account **and per list**, default 100. Three handles in `both` mode at 100 each asks for 600 rows, and every one of those 600 is either delivered or named by a row of its own. This actor delivers up to 50,000 rows per list; ask for more and the run still starts, continues at 50,000, and leaves one uncharged row saying what you asked for and what bound it.
- **Include rows you already have** (`includeSeen`) — hand back the rows your account already holds instead of skipping them. Still uncharged, marked `repeat: true`.
- **Skip rows in this dataset** (`resumeFromDatasetId`) — carry the same memory across Apify accounts, or resume a chain, by naming a dataset whose rows should not be collected again.
- **Continue a stopped run** (`resumeCursor`) — paste the `resumeCursor` object from a previous run's last row to carry on from exactly where it stopped. It positions the lists you ask for on this run, so keep the same usernames beside it; leave it empty to start from the top. A value that cannot be read is refused on an uncharged row rather than quietly restarting the list from page one.
- **Max run seconds** (`maxRunSeconds`) — default 600. The clock ends the collecting, never the delivering. It runs between 30 seconds and 1 hour; set it outside that and the run still starts, at the nearest one, with one uncharged row saying so. **No setting here is ever refused for being too big or too small** — the run starts, it runs at the nearest limit, and the row tells you which.

Press Start with nothing filled in and you get three built-in sample rows so you can see the column shape: nothing is read from Instagram on that run and there is no result fee for it.

### How often this data changes, and the second run

Follower lists churn daily on an active account — people follow and unfollow constantly, so the answer to "who follows this account" is a moving one and a weekly or daily schedule is where this actor earns its keep.

Re-running the same handle would otherwise re-buy the rows you already have, so it does not. Every run remembers what it delivered to your account, in the key-value store `ig-followers-account` in your own Apify account, and a later run skips those: not delivered again, not charged, and the slot goes to the next new follower instead — so you still get the number you asked for, and a schedule costs only what changed. The memory is a key-value store in your own Apify account, so a run started with a scoped API token in restricted-access mode needs key-value store Read, Write and Create permission (Write is what lets the run remember what it delivered; without it every run pays again) or Actor runs set to Full access; without it the run says so on its status line and on an uncharged row, and cannot skip the rows you already have — so the schedule pays for them again until the token is allowed to open that store.

### Reliability

The rows come from a licensed data feed rather than from scraping Instagram's own web pages, which is why there is nothing to log into and nothing to configure. When that feed cannot answer, the run says so on an uncharged row and finishes successfully — a failed read is never billed and never dressed up as an account with no followers.

### Related actors

- One account's own posts and reels: **Instagram Profile Posts Scraper** — https://apify.com/steadyfetch/instagram-profile-posts
- The spoken words inside a reel: **Instagram Reel Transcript Scraper** — https://apify.com/steadyfetch/instagram-reel-transcript-scraper

### Limits and good manners

Only public accounts return a list; a private one is answered as private and charged nothing. Instagram's own cap on very large accounts is described above and applies to every tool equally. Profile picture links are signed and time-limited by Instagram, and the requesting network address is stripped out of every link before it reaches your dataset. This actor may stop working if Instagram changes how the data is served; if it does, failed rows are never charged.

### Support

Something off, or a column you need that is not here? Open an issue on the Issues tab — we usually reply within a couple of hours.

If it earned its keep, a rating helps other buyers find it, and saving the actor keeps it one click away.

# Actor input Schema

## `usernames` (type: `array`):

Instagram handles, one per line — "nasa", "@nasa" or https://www.instagram.com/nasa/ all work, and "\_demo" is the sample switch, not a handle. A handle that does not exist, a private or empty account and a read that did not go through are delivered as labelled rows and are never charged. A hashtag or a link to a single post is refused with an uncharged row naming the field on this shelf that does take it. Leave it empty and the run returns built-in sample rows instead of collecting anything, so you can see the output shape at no result fee.

## `mode` (type: `string`):

"followers" (default) returns the accounts that follow each username. "following" returns the accounts each username follows. "both" returns each list separately, and a person who appears in both gets one row per list, because they are two different facts. Your result limit applies to each list on its own. An unrecognised value is refused on an uncharged row rather than quietly swapped for another list.

## `resultsLimit` (type: `integer`):

Hard cap on the rows delivered for EACH account and EACH list. Three handles in "both" mode at 100 each collects up to 600 rows. A list that holds fewer than you asked for leaves an uncharged row saying how many it actually holds — it never borrows another account's share. ⚠ Instagram itself limits how much of a very large account's follower list anyone can see; when that limit is what stops a list, the row says so and the rows past it are not charged. This actor delivers at most 50,000 rows per list: ask for more and the run continues at 50,000, with one uncharged row saying so, rather than being refused.

## `maxRunSeconds` (type: `integer`):

The run stops cleanly before this many seconds and reports what is left, instead of being killed by a timeout. A time limit ends the collecting, never the delivering: rows already in hand are always written out. The clock runs between 30 seconds and 1 hour: set it outside that and the run continues at the nearest one, with one uncharged row saying so, rather than being refused.

## `includeSeen` (type: `boolean`):

OFF (default): a row already delivered to your account is skipped — no row, no charge — and the next new one takes its place, so you still get the number you asked for. ON: those rows are delivered again anyway, marked `repeat: true`, and still not charged. Rows older than 90 days in your account's memory count as new again either way.

## `resumeCursor` (type: `object`):

The resumeCursor object from the last row of a previous run of this actor, exactly as it appears there. This run starts each list at the position that run reached: no page is read twice, no row already delivered to you is delivered or charged again, and nothing in between is skipped. It positions the lists you ask for on THIS run — it does not add accounts, so keep the same usernames beside it. Leave it empty to start from the top. A value that cannot be read is refused on an uncharged row rather than quietly restarting the list from page one.

## `resumeFromDatasetId` (type: `string`):

The dataset ID of a previous run of this actor. Rows already in it are treated as rows you already have and are skipped, so a follow-up run collects only what is new even from a different Apify account. Selecting it here is what grants this run permission to read it.

## Actor input object example

```json
{
  "usernames": [],
  "mode": "followers",
  "resultsLimit": 100,
  "maxRunSeconds": 600,
  "includeSeen": false
}
```

# Actor output Schema

## `followers` (type: `string`):

One row per account delivered: handle, name, user ID, profile URL, profile picture, and the verified and private flags — plus which of your accounts it belongs to and which list it came from. `followerOf` means this account follows the one you asked about; `followingOf` means the one you asked about follows this account. Every row carries `charged` and `status`, so the invoice reconciles from the dataset itself — only rows with charged = true were billed, at one price per row for either list, with no second charge for details and nothing charged for starting a run. Rows your account already had are skipped before they take a slot and are never charged again — that check reads a key-value store in your own account, so a run on a scoped API token without key-value store permission cannot make it and says so on its status line and on an uncharged row; with "Include rows you already have" they come back marked `repeat: true`, still uncharged.

## `misses` (type: `string`):

One uncharged row for every list that returned less than it was asked for, saying which of nine things happened: `account_not_found` (Instagram has no account under that handle), `private_account` (the account is private, so Instagram publishes no list for it at all), `capped_by_instagram` (Instagram limits how much of a very large account's follower list anyone can see — this is Instagram's own limit, not a setting on your run), `no_list` (the list is public and simply ran out — the row says how many it holds), `stopped_at_limit` (one of your own limits or the run clock stopped it first), `vendor_unavailable` (the read failed and a re-run is the fix), `source_refused` (Instagram refused that read outright, the same way every time — a re-run cannot change it), `unsupported_shape` (Instagram answered in a shape this actor does not read yet — on us, and a re-run gets the same answer), `vendor_budget` (this actor reached its own monthly collection allowance). None of these is charged.

## `summary` (type: `string`):

Rows delivered, what was asked for, rows your account already had, rows past Instagram's own limit on very large accounts, what stopped the run, the charged-event totals, and — when this run's token could not open the repeat memory — `repeatCheck: "unavailable_scope"`. It also carries `resumeCursor` when a list was left unfinished: a handle you pass straight back as input to continue that list exactly where this run stopped. It is a position marker, not a result and not a charged column — nothing is billed for it, and continuing costs only the new rows the next run actually delivers. A run whose lists all ran out carries `resumeCursor: null`.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "usernames": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("steadyfetch/instagram-followers-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "usernames": [] }

# Run the Actor and wait for it to finish
run = client.actor("steadyfetch/instagram-followers-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "usernames": []
}' |
apify call steadyfetch/instagram-followers-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,steadyfetch/instagram-followers-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/SKbAepS7VxEfqbdsD/builds/Ne0VVf6N1bWl1NSKV/openapi.json
