# Instagram Engagement Rate Calculator & Audit (`sz022/instagram-threads-influencer-audit`) Actor

Check creators before you pay them. Paste Instagram and Threads profiles and get one row per account: exact followers, median likes, comments and views, engagement rate, posting frequency and standout posts. No login needed. Schedule runs, use the API, export to CSV or Excel.

- **URL**: https://apify.com/sz022/instagram-threads-influencer-audit.md
- **Developed by:** [CHEN-SHU-CHEN](https://apify.com/sz022) (community)
- **Stats:** 3 total users, 2 monthly users, 75.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $15.00 / 1,000 instagram 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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

### What is Instagram Engagement Rate Calculator & Audit?

You have a shortlist of creators and a budget to split between them. Follower counts are on every profile, but they say nothing about whether anyone still engages, and checking by hand means opening two dozen posts per account and typing numbers into a sheet.

Paste **Instagram and Threads profiles**, mixed together, and get **one row per account**: exact **followers**, **median likes, comments and views** over the recent posts, **engagement rate**, **posting frequency**, when they last posted, and which posts stood out. No login is needed.

Built for vetting accounts you have already found. If you need to *find* creators by hashtag, niche or location, use a discovery tool; this one takes accounts and nothing else.

Because it runs on Apify, you can **schedule** a monthly re-check of your roster, call it through the **API**, connect it to Make, Zapier or Google Sheets, and export the table as **CSV, Excel or JSON**.

### How to audit Instagram and Threads influencers

1. Open the **Input** tab and paste profile URLs or handles into **Accounts**, one per line. `https://www.instagram.com/nasa/`, `https://www.threads.com/@nasa`, `ig:nasa` and `threads:nasa` all work.
2. Set **Recent posts per account**. 24 is the default; up to 50 is allowed.
3. Leave the optional sections alone unless you need them. Logged out is the default and needs no setup.
4. Click **Start**. Threads accounts finish in a few seconds; an Instagram account with 24 posts takes about 30 seconds.
5. Open the **Output** tab, or export the dataset. Accounts that could not be read are still in the table, with a `status` and a reason.

### What data does the influencer audit return?

| Field | Notes |
|---|---|
| `status` | `success`, `partial`, `no_posts`, `not_found`, `private`, `login_required`, `cookie_expired`, `parse_failed`, `unsupported_input`, `unsupported_platform`, `not_processed` |
| `platform`, `username`, `profileUrl` | |
| `inputIndex` | The account's position among the accounts audited, counting from 1. Blank lines and repeated accounts are dropped first, so it can differ from the line number in your list |
| `followerCount` | Exact, from the profile itself |
| `postsAnalyzed`, `postsRequested` | Pinned posts are skipped and do not count toward either |
| `windowStart`, `windowEnd` | Dates of the oldest and newest post analysed |
| `likesMedian`, `likesMean` | |
| `commentsMedian`, `commentsMean` | Replies on Threads |
| `viewsMedian`, `viewsMean`, `postsWithViews` | Instagram videos and reels only. Image posts have no views, and Threads does not publish views in its profile feed |
| `engagementRate` | See the definition below. In percent |
| `engagementRateMean` | The same with the mean, for comparing with tools that publish that |
| `postingFrequency` | Posts per week inside the window |
| `lastPostAt`, `daysSinceLastPost` | |
| `topPosts` | The three posts with the most likes + comments |
| `outlierPosts` | Posts with at least 3× the median engagement, with `timesMedian` |
| `repostsMedian`, `quotesMedian` | Threads only |
| `pinnedPostsSkipped`, `postsByOthersSkipped`, `postsFailed`, `postsWithoutLikes` | What was left out, so you can tell a thin sample from a quiet account |
| `authMode` | `logged_out`, `graph_api` or `cookie`: how this row was read |
| `notes`, `error` | Plain-language reasons whenever a number is missing or the sample is short |

### How is engagement rate calculated?

**Engagement** of a post is likes + comments. A post only counts when both are known.

**`engagementRate`** is the median, over the posts analysed, of engagement ÷ followers, × 100. So an account with 100,000 followers whose typical post gets 2,000 likes and comments has an engagement rate of `2.0`. It uses the median on purpose: one viral post can double the mean and tell you nothing about the next post you pay for.

**`postingFrequency`** is the number of gaps between posts divided by the weeks between the oldest and newest post. It describes how often the account posts while it is active. An account that posted twice a week until four months ago still shows `2.0` here, which is why `daysSinceLastPost` sits next to it.

**Pinned posts** sit at the top of a profile however old they are. Neither platform marks them for logged-out visitors, so a leading post older than one shown after it is treated as pinned and skipped. Left in, a pinned post from last year would stretch the window and dilute the frequency.

**Collab posts** on Instagram appear on both partners' grids, but their likes come from both audiences. By default a collab owned by the partner is skipped (`postsByOthersSkipped`), so the numbers describe the account's own posts. Turn on `includeCollabPosts` to count them. Reposts on Threads are always skipped: their counts belong to the original author.

**Unknown is `null`, never `0`.** A post whose author hid its like count has no like count. It is left out of the like median rather than counted as zero, and `postsWithoutLikes` says how many were.

### How much does an influencer audit cost?

You pay per account, not per post or per minute. There are three events:

| Event | Charged for these statuses | Price | Per 1,000 accounts |
|---|---|---|---|
| `account-audited-instagram` | `success`, `partial`, and `no_posts` on an Instagram account: read to a definite answer, even when that answer is that it has no posts in the window | $0.02 | $20 |
| `account-audited-threads` | The same statuses on a Threads account | $0.005 | $5 |
| `account-unavailable` | `not_found`, `private`, `login_required`, `cookie_expired`, `parse_failed`: the account was looked up but could not be read | $0.0005 | $0.50 |

Instagram costs more because its profile grid carries no like or comment counts, so every post is read with a request of its own, about 50 requests for 24 posts. Threads shows the counts in its feed, so a whole account takes a handful of requests.

Free, never charged: `unsupported_input` (not a profile), `unsupported_platform` (Facebook pages) and `not_processed` (never reached because the run stopped early).

`no_posts` is only reported when the feed was read to its end, or past the `maxPostAgeDays` window, and every post on the way was read and dated. If the feed stops early, the Actor reaches its 10-page reading limit, or a post cannot be read or dated, the window cannot be confirmed: the row is `partial` if some posts were analysed and `parse_failed` if none were, never an empty-handed `no_posts`. A short sample is only described as "all the account has" when nothing like that happened. If 6 posts in a row can't be read or dated before any usable one turns up, reading stops there and the row is `parse_failed`. Reading one account also stops after 120 seconds on Instagram or 45 seconds on Threads, which normal accounts never reach; a slow proxy then gives a `partial` row, or `parse_failed` if nothing was read.

#### Is auditing Instagram influencers free?

Apify's free plan includes $5 of usage a month, which covers about 250 Instagram accounts or about 1,000 Threads accounts. You can try the Actor on your own shortlist before paying anything.

#### How do I keep a run from costing more than I expect?

Set a maximum charge for the run when you start it. Before reading each account, the Actor checks that the remaining budget still covers one more account on that account's platform, whether it turns out audited or unavailable, and stops if it does not. Every account it did not reach is listed as `not_processed`, so you can see exactly what was left out. If Apify cannot record a charge after three tries, the run stops the same way rather than keep working unbilled. A run also stops the same way if it is plainly costing more than it earns, for example through a proxy of your own that stalls every request. A run takes up to 1,000 accounts.

#### What happens if Apify restarts a run?

Apify sometimes moves a running Actor to another server, which restarts it. The Actor saves its progress after every account, so the restart carries on after the last finished one. An account that was charged but whose row may not have been saved yet is finished from the saved copy: it is not read again, and the restart looks for that account's own row in the dataset (by its `inputIndex`) to decide whether the row still needs writing. If the dataset cannot be read after three tries, the run stops and fails rather than guess, and nothing is read or charged. When a run stops early, that decision is saved before its `not_processed` rows go out, and progress is saved after each of them, so a restart finishes the list instead of reading accounts again.

Two narrow windows remain. If the run stops in the moment after a charge is accepted but before progress is saved, the restart reads that one account again and charges it under the same idempotency key, which Apify only deduplicates for about three minutes, so a slower restart can charge that account twice. And because a write can take a few seconds to show up when the dataset is read back, the row in flight at the restart can occasionally appear twice. Both affect at most one account per restart.

### Input example

```json
{
  "accounts": [
    "https://www.instagram.com/nasa/",
    "threads:nasa"
  ],
  "postsPerAccount": 12
}
```

### Output example

One row from a real run on 2026-10-18, trimmed to the main fields:

```json
{
  "platform": "Threads",
  "username": "nasa",
  "status": "success",
  "authMode": "logged_out",
  "followerCount": 12781695,
  "postsAnalyzed": 12,
  "windowStart": "2026-09-25T20:17:28.000Z",
  "windowEnd": "2026-10-16T17:10:38.000Z",
  "likesMedian": 2076.5,
  "commentsMedian": 56.5,
  "viewsMedian": null,
  "engagementRate": 0.0167,
  "postingFrequency": 3.69,
  "daysSinceLastPost": 1.1,
  "outlierPosts": [
    {
      "url": "https://www.threads.com/@nasa/post/Decc755lYhC",
      "likes": 41038,
      "comments": 586,
      "timesMedian": 19.5
    }
  ],
  "notes": ["Threads does not publish view counts in the profile feed; viewsMedian is null"]
}
```

### Three ways to read an account

#### Logged out (the default)

Nothing to set up. In testing it read 48 Instagram posts from each of four accounts by paging the profile grid, and 52–91 Threads posts from each of four accounts. Every page returned HTTP 200, so 50 posts per account is within reach on both platforms.

Instagram's grid lists posts without their counts, so each post is then read on its own, the same way the post-metrics Actor reads it. That is why an Instagram account takes about 30 seconds for 24 posts. Threads includes the counts in the feed itself, so it takes a few seconds.

#### Official Instagram API (optional, business and creator accounts)

If you run an Instagram professional account, Meta's **Business Discovery** endpoint returns another professional account's follower count and the like, comment and view counts of its media. It is the route Meta sanctions, and it does not depend on page markup.

Setup:

1. Your Instagram account must be a business or creator account, linked to a Facebook Page.
2. Create an app at developers.facebook.com and add the Instagram product with Facebook Login.
3. In the Graph API Explorer, generate a user access token with `instagram_basic`, `instagram_manage_insights` and `pages_read_engagement`. Exchange it for a long-lived token, which lasts about 60 days.
4. Find your Instagram user ID: `GET /me/accounts?fields=instagram_business_account` on that token.
5. Paste the token into `igGraphAccessToken` and the ID into `igGraphUserId`.

It returns `followers_count`, `media_count`, and per post `like_count`, `comments_count`, `view_count`, `timestamp`, `media_type` and `permalink`. It **cannot read personal accounts**. Those accounts, and any API failure, fall back to logged out, with the reason in `notes`. An expired or revoked token is reported once and the rest of the run carries on logged out.

This mode was built from Meta's reference documentation and has not yet been run against a live token.

#### Your session cookie (optional, at your own risk)

> **Read this before pasting a cookie.**
>
> - **The account can be restricted or banned.** Meta detects automated use of logged-in sessions, especially from data-centre IPs, and acts against the account the cookie belongs to. **Use a secondary account you can afford to lose, never your own or a client's.**
> - **Logging in means accepting Meta's terms,** which prohibit automated collection. Logged-out reading involves no account and no terms acceptance. With a cookie, that is no longer true.
> - **Cookies expire,** and a password change or a security check revokes them. An expired cookie is reported as `cookie_expired` in `notes`, and the run carries on logged out.

You probably do not need it: logged out already reaches 50 posts per account on both platforms. A cookie only helps with accounts that show nothing to logged-out visitors, such as Threads accounts that redirect to the login page, or a private account the cookie's account follows.

The cookie is used only for the profile and feed requests. Individual Instagram posts are still read logged out, so the account behind the cookie makes as few requests as possible. It is stored as a secret input. Before anything is written to the log, the dataset or the key-value store, the exact cookie values, the Graph API token and the proxy password are cut out of it, and credentials are stripped from any pasted link (a `user:password@` part, or token-like parameters in the query or fragment).

This mode has **not been tested with a real account**. If the logged-in page cannot be read, the row says so in `notes` and is read logged out instead, so a cookie never makes a result worse than having none.

### Other Actors for social media reporting

| Actor | Use it when |
|---|---|
| [Instagram, Facebook, Threads & YouTube Post Metrics](https://apify.com/sz022/social-post-metrics) | You have the post URLs from a campaign and need likes, comments, shares and views for each one in one table |
| [Instagram, Facebook & Threads Post Metrics Tracker](https://apify.com/sz022/social-post-metrics-tracker) | You want the same posts re-checked on a schedule, with the change since the last run |

### FAQ

#### Is it legal to audit Instagram and Threads accounts?

The default mode reads only what any logged-out visitor can see on a public profile, and uses no account. The session-cookie mode is different: logging in means accepting Meta's terms, which prohibit automated collection, and the account can be banned. Read the warning above before using it. This is not legal advice; check the rules that apply to you.

#### What can it not return?

- **Facebook pages.** Logged out, a page shows one post, and the request that returns more was rate-limited after a single use and stayed blocked for at least ten minutes. That is not enough to audit an account, so Facebook rows come back `unsupported_platform` and are not charged.
- **Reach, impressions, saves, audience demographics.** These are visible only to the account itself. Any public tool showing them is estimating.
- **Fake-follower scores.** Spotting bought followers needs the follower list, which no logged-out request can see. A low engagement rate on a large account is the honest signal this Actor can give you.
- **Threads view counts.** Threads publishes them only on each post's own page, not in the profile feed.
- **Private accounts** logged out, and Threads accounts that only show their profile after login.

#### Why did an account come back login_required?

On both Instagram and Threads, a handle that does not exist and one that is login-gated can both redirect a logged-out visitor to the login page, so both come back `login_required` with `error: "login_wall"`, not `not_found`. Check the spelling first. If many correctly spelled accounts come back this way, the platform is limiting logged-out access from the proxy; see the next question.

#### Why are some accounts slow or failing in bulk?

Instagram and Threads both read through Apify's datacenter proxy; every platform test so far has succeeded on it. Residential and Google SERP proxy groups are not used for these reads, because they are billed per GB or per request and the price per account is set for datacenter traffic. A run that asks for them falls back to the datacenter proxy and notes it on each row. If many correctly spelled accounts come back `login_required`, the platform is limiting logged-out access for now: run again later, or use your own proxy URLs, which are used as given.

#### Why is memory limited to 1 GB?

The Actor reads pages over plain HTTP and peaks at about 110 MB, so 1 GB is plenty. More memory would not make it faster, only more expensive, so runs are held between 512 MB and 1 GB.

#### Other limits worth knowing

- Pinned-post detection relies on dates. A pinned post that is also the newest cannot be told apart from an ordinary one, but then it belongs in the window anyway.
- Instagram view counts come from the post embed and are only present on videos and reels.
- Extraction depends on undocumented internal JSON and query IDs, which Meta can change without notice. The query IDs are read from each profile page rather than hard-coded, so routine rotation does not break paging, but a structural change would. Failures are reported per account rather than crashing the run.

#### Something broke or a number looks wrong

Open an issue on the **Issues** tab with the account and the run link. Reports are answered there.

# Actor input Schema

## `accounts` (type: `array`):

Instagram and Threads profile URLs or handles, mixed freely: https://www.instagram.com/nasa/, https://www.threads.com/@nasa, or a handle with a prefix (ig:nasa, threads:nasa). A handle without a prefix uses the platform set below. Facebook pages are accepted but come back as unsupported_platform and are not charged. Up to 1,000 per run.

## `defaultPlatform` (type: `string`):

Which platform a handle without a URL or prefix belongs to.

## `postsPerAccount` (type: `integer`):

How many of the most recent posts to analyse. Pinned posts are skipped and do not count toward this. Instagram reads each post on its own, so 24 posts take about 30 seconds per account; Threads takes a few seconds.

## `includeCollabPosts` (type: `boolean`):

A collab post appears on both accounts' grids but belongs to one of them, and its likes come from both audiences. Off by default, so the numbers describe the account's own posts.

## `maxPostAgeDays` (type: `integer`):

Optional. Leave empty to take the most recent posts however old they are.

## `igGraphAccessToken` (type: `string`):

Official mode for Instagram business and creator accounts. A Facebook user access token with instagram_basic, instagram_manage_insights and pages_read_engagement. See the README for setup. Personal accounts, and any failure, fall back to logged out.

## `igGraphUserId` (type: `string`):

The numeric Instagram user ID of the business or creator account the token belongs to (not the account you are auditing).

## `instagramSessionCookie` (type: `string`):

The sessionid cookie of an Instagram account you control. Meta may restrict or ban an account used this way; use a secondary account. Read the README first. Logged out reads up to 50 posts without it.

## `threadsSessionCookie` (type: `string`):

The sessionid cookie from threads.com for an account you control. Same risks as the Instagram cookie.

## `abortAfterConsecutiveFailures` (type: `integer`):

Ends the run early when a platform stops responding. Accounts that do not exist or are private do not count toward this. Set to 0 to never stop early.

## `proxyConfiguration` (type: `object`):

Apify's datacenter proxy, which has read Instagram and Threads profiles in every test on the platform. Residential and Google SERP proxy groups are not used: the price per account is set for datacenter traffic, so a run that asks for them falls back to the datacenter proxy and notes it on each row. Proxy URLs of your own are used as given.

## Actor input object example

```json
{
  "accounts": [
    "https://www.instagram.com/nasa/",
    "threads:nasa"
  ],
  "defaultPlatform": "instagram",
  "postsPerAccount": 6,
  "includeCollabPosts": false,
  "abortAfterConsecutiveFailures": 5,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `audit` (type: `string`):

Followers, median and mean likes, comments and views over the recent posts, engagement rate, posting frequency, top and outlier posts. Unknown values are null, never 0.

## `failedAccounts` (type: `string`):

The subset of input accounts that returned no numbers, with the reason for each. Also present in the dataset itself.

# 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 = {
    "accounts": [
        "https://www.instagram.com/nasa/",
        "threads:nasa"
    ],
    "postsPerAccount": 6
};

// Run the Actor and wait for it to finish
const run = await client.actor("sz022/instagram-threads-influencer-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 = {
    "accounts": [
        "https://www.instagram.com/nasa/",
        "threads:nasa",
    ],
    "postsPerAccount": 6,
}

# Run the Actor and wait for it to finish
run = client.actor("sz022/instagram-threads-influencer-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 '{
  "accounts": [
    "https://www.instagram.com/nasa/",
    "threads:nasa"
  ],
  "postsPerAccount": 6
}' |
apify call sz022/instagram-threads-influencer-audit --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,sz022/instagram-threads-influencer-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/TDoJVsiyMOpz9ZRqR/builds/QRIkqEbG1fQuwrbNT/openapi.json
