# Instagram Fake Follower Checker & Engagement Rate Audit (`frameprobe/instagram-audit`) Actor

Check whether an Instagram account's followers and engagement are real. Get engagement rate, median likes and comments, follower-to-following ratio and whether engagement is rising or falling across recent posts. One handle in, one row out. No login, no API key.

- **URL**: https://apify.com/frameprobe/instagram-audit.md
- **Developed by:** [FrameProbe](https://apify.com/frameprobe) (community)
- **Categories:** Social media, Marketing
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $50.00 / 1,000 account auditeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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 Fake Follower Checker & Engagement Rate Audit

A fake follower checker and engagement rate calculator for any public Instagram account. No login, no
API key. One handle in, one row out: engagement rate, median likes and comments, the
follower-to-following ratio, whether engagement is rising or falling across the account's recent
posts, and the dates those posts cover.

### What you'd do without this

- Pull the account's recent posts.
- Drop the pinned ones, which sit at the top of the grid out of date order, and the collaboration
  posts that belong to other accounts.
- Take the median likes and comments, divide by followers, and compare against what is normal for an
  account that size.
- Fit a trend across the posts, and check it is bigger than the account's normal post-to-post noise
  before calling it a trend.
- Then do it all again for the next account.

### Quickstart

```json
{
  "usernames": ["nasa"]
}
```

A username, an @username or a profile link all work. Up to 100 accounts in one run.

### What the numbers cover: the latest posts, not a fixed period

The profile fetch returns a fixed number of the account's most recent grid posts, not a fixed
number of days. In a sample of 439 accounts read on 2026-09-11 it returned 12 posts for 423 of them,
whatever their total. Twelve posts is a month for an account that posts three times a week, and
under two weeks for one that posts daily.

So every row says exactly what it covered:

- `postsFirstAt` and `postsLastAt`: when the oldest and newest analysed posts went up.
- `postsSpanDays`: the days between them.
- `postsReturned`, `postsPinnedExcluded`, `postsOtherOwnerExcluded`, `postsUnreadable`,
  `postsSkippedNoCounts` and `postsAnalyzed`: how many posts came back, how many were left out and
  why, and how many every number is computed over.
- `sourcePosts`: every post that came back, with its date, likes, comments and owner as received,
  and how it was used. Every number on the row can be recomputed from it.

On NASA, 12 posts came back and 6 were used: 3 were pinned and 3 were collaboration posts published
by other accounts. The 6 covered 8 days.

### Trend: five posts or no trend

`engagementTrendPctPerPost` is the straight-line change from one analysed post to the next, oldest
first, as a percentage of the account's own average. `engagementTrend` gives it a direction only
when it is bigger than a tenth of the account's own spread (`engagementSpreadPct`). Otherwise it is
`flat`.

Below five analysed posts there is no trend at all. `engagementTrend` reads `insufficient`, and the
rate and medians are still computed over the posts there are. Five is the smallest number of posts
at which even a perfectly steady rise or fall could be told apart from chance.

### The numbers lead and the verdict trails

- `verdict` is one of `ACCOUNT_CHANGED`, `INSUFFICIENT_POSTS`, `NO_FOLLOWER_COUNT`,
  `LIKELY_INFLATED`, `SUSPICIOUS` or `AUTHENTIC`.
- `LIKELY_INFLATED` needs two signals: an engagement rate below the usual range for the follower
  count, and following more accounts than follow it. One signal alone is `SUSPICIOUS`. Only accounts
  under 10,000 followers have a measured follow threshold, so above that size the strongest verdict
  is `SUSPICIOUS`.
- `basis` says what the verdict was measured against, and it caps `confidence`. `AUTHENTIC`,
  `SUSPICIOUS` and `LIKELY_INFLATED` all compare the account with other accounts of its size, so all
  three say `basis: "tier"`. **Every row says `confidence: "low"`**: only the account's own history
  would earn more, and this Actor keeps none.
- Fewer than three analysed posts reads `INSUFFICIENT_POSTS`, and is not charged.
- No follower count above zero reads `NO_FOLLOWER_COUNT`. There is no engagement rate, so nothing is
  compared, and it is not charged. None of the 493 accounts in the sample read it.

#### The usual range is measured, on a sample

`tierTypicalLowPct` and `tierTypicalHighPct` are the 10th and 90th percentiles of engagement rate
among accounts of the same size, from 419 accounts read on 2026-09-11, each with at least three
analysed posts. **So about 1 in 10 of those accounts sits below the low end.** An ordinary account can
fall under it, which is why falling under it is a signal and not proof.

| Followers | Accounts measured | Low (10th percentile) | High (90th percentile) |
|---|---|---|---|
| Under 10,000 | 80 | 1.2289% | 20.2602% |
| 10,000 to 100,000 | 97 | 0.3068% | 10.3615% |
| 100,000 to 500,000 | 81 | 0.0834% | 5.2928% |
| 500,000 to 1,000,000 | 29 | 0.1036% | 2.9002% |
| 1,000,000 to 10,000,000 | 86 | 0.0416% | 3.7436% |
| 10,000,000 and over | 46 | 0.0217% | 2.8521% |

- **It is not a random sample of Instagram.** It was gathered by starting from twenty large brand
  accounts and following the accounts they tag and mention, so nobody picked accounts by fame, and it
  leans toward active accounts that brands work with.
- **The 500,000 to 1,000,000 row rests on the fewest accounts**, and its low end sits above the row
  before it. The high end of the 1,000,000 to 10,000,000 row sits above the row before it too. That
  is the sample, not a rule, and it is published as measured.
- **Accounts over 10 million followers have their own row.** In the same sample 7 of 16 very large
  brands still read `SUSPICIOUS` on their rate alone. Read the numbers, and apply your own
  threshold, before the verdict.

#### The follow threshold is measured the same way

`followerToFollowingRatio` is followers divided by following. Below 1, the account follows more
accounts than follow it. The threshold is the 10th percentile of that ratio among accounts of the
same size in the same sample, so about 1 in 10 of them sits below it too.

| Followers | Accounts measured | 10th percentile | Used as a threshold |
|---|---|---|---|
| Under 10,000 | 79 | 0.87 | yes |
| 10,000 to 100,000 | 96 | 7.78 | no |
| 100,000 to 500,000 | 81 | 103.55 | no |
| 500,000 to 1,000,000 | 29 | 269.04 | no |
| 1,000,000 to 10,000,000 | 86 | 1528.91 | no |
| 10,000,000 and over | 45 | 15636.95 | no |

From 10,000 followers up, the lowest tenth of accounts still follow an eighth of their audience or
less. That is not following back, so no threshold is applied there. **Those tiers deliberately give
no follow-ratio signal.** A floor at 1, following more accounts than follow you, was considered and
left off: none of the 337 accounts above 10,000 followers in the sample is under it, and the lowest
ratio among them is 1.52, so it would be a rule with no evidence that it catches anything. The ratio
is on every row either way, so you can apply your own.

### What to expect from a list of accounts

In the 2026-09-11 sample of 493 accounts:

- **419 (85.0%)** got a verdict. These are the ones charged.
- **31 (6.3%)** got an engagement rate from one or two posts, too few for a verdict. The row carries
  the numbers. Not charged.
- **43 (8.7%) returned nothing usable.** Not charged. Of those:
  - 25 had no post it could use (8 returned no posts, 12 had only pinned or other accounts' posts, 5
    had only posts with hidden counts);
  - 10 were private;
  - 4 had no account under that name;
  - 2 were restricted by age;
  - 2 came back with a post list this Actor could not put in order, so it declined to guess which
    posts were pinned. That is this Actor's choice, not the fetch failing.

So about 1 in 7 accounts in a pasted list can come back without a charged answer. The sample leans
toward active accounts, so a list of quieter accounts may do worse.

### Example row (an illustrative account)

`sourcePosts` is cut to two of its twelve entries here. A real row carries one entry per post
returned.

```json
{
  "username": "example_account",
  "accountId": "1234567890",
  "profileUrl": "https://www.instagram.com/example_account",
  "checkedAt": "2026-09-11T06:00:00Z",
  "status": "ok",
  "error": null,
  "isVerified": false,
  "isBusinessAccount": true,
  "isPrivate": false,
  "postsTotal": 311,
  "postsReturned": 12,
  "postsPinnedExcluded": 1,
  "postsOtherOwnerExcluded": 1,
  "postsUnreadable": 0,
  "newestPostAgeHours": 35.3,
  "upstreamDatasetId": "aBcDeFgHiJkLmNoPq",
  "sourcePosts": [
    {"timestamp": "2026-07-02T11:20:00.000Z", "likesCount": 4210, "commentsCount": 96, "ownerId": "1234567890", "ownerUsername": "example_account", "isPinned": true, "usedAs": "pinned"},
    {"timestamp": "2026-09-09T18:40:05.000Z", "likesCount": 1090, "commentsCount": 31, "ownerId": "1234567890", "ownerUsername": "example_account", "isPinned": null, "usedAs": "analysed"}
  ],
  "followers": 48200,
  "following": 610,
  "followerToFollowingRatio": 79.02,
  "postsAnalyzed": 10,
  "postsSkippedNoCounts": 0,
  "postsFirstAt": "2026-08-18T16:02:11Z",
  "postsLastAt": "2026-09-09T18:40:05Z",
  "postsSpanDays": 22.11,
  "medianLikes": 1150.0,
  "medianComments": 38.0,
  "engagementRatePct": 2.4689,
  "engagementSpreadPct": 21.4,
  "engagementTrendPctPerPost": -3.1,
  "engagementTrend": "falling",
  "postsPerWeek": 2.85,
  "tierTypicalLowPct": 0.3068,
  "tierTypicalHighPct": 10.3615,
  "verdict": "AUTHENTIC",
  "basis": "tier",
  "confidence": "low",
  "signals": [
    "engagement rate 2.4689% sits inside the typical band for this follower tier, measured on other accounts, not on this account's own history"
  ],
  "caveats": [
    "engagement is computed over currently-visible public posts; a deleted underperforming post is invisible here and pulls this median UP",
    "this Actor cannot see whether the handle was sold or repurposed: the profile data it reads carries no username history, so ACCOUNT_CHANGED never fires. An account whose engagement fell across a change of owner reads here as SUSPICIOUS or LIKELY_INFLATED instead, so this leans toward accusing a real account",
    "this Actor does not choose the account: the profile fetch resolves the username, and it can return a different account than the one you meant (in a 2026-09-11 measurement the handle chanel came back as a 42,933-follower personal account). Check profileUrl, followers and isVerified before acting; a wrong match can push the verdict either way, because every number on the row then belongs to someone else",
    "1 pinned post(s) left out: an account chooses what to pin, and on the one account measured the pinned posts included its two most-liked, so counting them would pull the median UP and the trend toward falling",
    "1 post(s) on this grid belong to another account (collaboration posts) and are left out, because their likes come partly from that account's followers",
    "likes keep arriving after a post goes up, and the newest analysed post was 35.3 hours old when read; the younger it is, the more this pulls the trend toward falling and the median DOWN",
    "upstreamDatasetId points at the profile fetch's own dataset in your account, and Apify deletes unnamed datasets after a retention period, so that link will stop working; every field this row was computed from is copied onto the row, in sourcePosts and the profile fields, so it stands on its own"
  ]
}
```

Every row carries every key. A value that does not apply is `null`, never a missing key.

### What it can't do, and which way it leans

- **Deleted posts are invisible.** An account that deletes its weak posts has its median pulled up,
  so it reads as more authentic, not less. This leans toward `AUTHENTIC`, the answer an account
  inflating its numbers would want.
- **It cannot see a change of owner.** The profile data it reads carries no username history, so
  `ACCOUNT_CHANGED` never fires. A handle that was sold or repurposed, whose engagement fell across
  the change, reads here as `SUSPICIOUS` or `LIKELY_INFLATED` instead. This leans toward accusing a
  real account, so check the account's history yourself before acting on either verdict.
- **It does not choose the account.** The profile fetch turns the username into an account, and it
  can pick a different one than you meant: in the sample, `chanel` came back as a personal account
  with 42,933 followers. Check `profileUrl`, `followers` and `isVerified`. A wrong match can push the
  verdict either way.
- **The newest posts are still collecting likes.** `newestPostAgeHours` says how old the newest
  analysed post was. The younger it is, the more it pulls the trend toward falling and the median
  down.
- **Pinned posts and collaboration posts are left out.** An account chooses what to pin, and on NASA
  its pinned posts included its two most-liked, so counting them would pull the median up.
- **Some post lists cannot be read.** Pinned posts sit at the top of the list, out of date order.
  When more posts at the top are out of order without being marked pinned than there are posts below
  them, this Actor declines the account rather than guess which ones are pinned. That was 2 of 493 in
  the sample. They are not charged.
- **The usual range and the follow threshold come from a sample**, described above.
- **The fetch's own dataset does not last.** `upstreamDatasetId` points at it, and Apify deletes it
  after a retention period. The row carries what it was computed from in `sourcePosts`.
- **Private and age-restricted accounts cannot be checked.** They are reported with the reason and
  are not charged.
- **Only the most recent posts are fetched.** This is not a history of the account.

### Pricing

- A start charge once per run, after your input is accepted.
- One charge per account whose row carries a computed answer, after the row is in your dataset.
- Not charged: an account that is private or age-restricted, that the profile fetch did not return,
  whose post list could not be read, that has too few posts for a verdict, or that was skipped
  because your run's maximum charge had no room for it.
- The profile fetch runs `apify/instagram-profile-scraper` in your account and is billed to you by
  that Actor, per profile, at its own rate. Its paid "About account" add-on is never switched on.
- If your run's maximum charge cannot cover every account, the ones beyond it are not fetched, so
  nothing is spent on your account for them.

The amounts are on the Pricing tab.

### Input

| Field | What it does |
|---|---|
| `usernames` | One or more Instagram usernames or profile links. Also accepted as `username`, `profiles`, `accounts`, `urls`, `profileUrls` or `startUrls`, as a string, a list, or a list of `{"url": ...}` objects. Commas and new lines split a string. Up to 100 accounts a run |

# Actor input Schema

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

One or more public Instagram accounts. A username, an @username or a full profile link all work. Each account is fetched and charged separately. An account that is private, or that the profile fetch does not return, is reported with the reason and is not charged. Up to 100 accounts per run.

## Actor input object example

```json
{
  "usernames": [
    "nasa"
  ]
}
```

# Actor output Schema

## `accounts` (type: `string`):

Engagement rate, median likes and comments, the trend across the account's recent posts and the dates those posts cover, the follower-to-following ratio, and a verdict that trails the numbers. An account that could not be computed has status failed, the reason in error, and is not charged.

# 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": [
        "nasa"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("frameprobe/instagram-audit").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": ["nasa"] }

# Run the Actor and wait for it to finish
run = client.actor("frameprobe/instagram-audit").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": [
    "nasa"
  ]
}' |
apify call frameprobe/instagram-audit --silent --output-dataset

```

## MCP server setup

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

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/NAhdMK6B161zsGhio/builds/ad6fbRUej832n9cPQ/openapi.json
