# Instagram API Scraper: Engagement Rate Per Follower (`api-empire/instagram-api-scraper`) Actor

- **URL**: https://apify.com/api-empire/instagram-api-scraper.md
- **Developed by:** [API Empire](https://apify.com/api-empire) (community)
- **Categories:** Automation, Lead generation, Social media
- **Stats:** 7 total users, 5 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

## 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 Scraper — Posts, Reels, Profiles and Engagement Rate JSON

This Instagram scraper extracts posts, reels, comments, profile details, hashtag mentions and search results from public Instagram accounts, then divides each post's interactions by that account's real follower count to produce a follower-denominated `engagementRate`. Every row is typed, normalized JSON — no HTML, no CSS selectors, no parsing step. It also emits one `AccountSummary` rollup per account with the sample size printed beside every average. By the end of this page you will know exactly which keys land in your dataset and what each one costs.

### What is Instagram API Scraper: Engagement Rate Per Follower?

**Instagram API Scraper: Engagement Rate Per Follower** is an Apify Actor that scrapes public Instagram data over plain HTTP — no login, no cookies, no `sessionid`, no headless browser — and enriches every post and reel with the metric raw scrapers leave out: engagement rate per follower. A base Instagram scraper hands you `likesCount` and `commentsCount`, which are meaningless without a denominator. A 4,200-like post is extraordinary on a 20,000-follower micro-creator and near-dead on a 20-million-follower celebrity account. This Actor recovers the follower count and does the division.

There is no credential field anywhere in the input schema: `.actor/actor.json` declares fifteen properties and not one accepts an Instagram username, password, session cookie or token. The Actor authenticates nothing — it reads public surfaces the way a logged-out browser does.

Key capabilities, one per entity type the Actor genuinely returns:

- **Scrape Instagram posts** from any public profile or hashtag, decorated with `engagementRate`, `engagementTier`, `likesPerFollower` and `commentsPerFollower`
- **Scrape Instagram reels** from a public profile feed, decorated with the same follower-denominated metrics
- **Scrape Instagram comments** from a post or reel, with commenter identity and like counts
- **Scrape Instagram profile details** — `followersCount`, `followsCount`, `postsCount`, `biography`, `externalUrl`, verified and private flags
- **Scrape hashtag mentions and Instagram search results** for users, hashtags and places
- **Get a per-account rollup row** with `avgEngagementRate`, `avgLikes`, `avgComments` and — always — `postsAnalysed` and `ratedPostsAnalysed`
- Export as JSON, CSV, Excel, XML or HTML from the Apify dataset; no proxy management and no parsing on your side

### 📊 What data does this Instagram scraper collect?

The Actor writes eight structurally distinct row types into one dataset: posts, reels, comments, profile details, mentions, search results, account summaries and uncharged accounting rows. Every row carries a `section` key so you can split or filter the export, and `.actor/actor.json` ships eight pre-built dataset views (📈 Engagement rate, 🪪 Account profiles, 📷 Posts overview, 💬 Comments view, 🪪 Profile details, 🎬 Reels view, 🔖 Mentions view, 🔎 Search results) that select the relevant columns for each.

| Data Type | Key Fields | JSON Field Names |
| --- | --- | --- |
| **Posts** (`section: "posts"`) | Caption, hashtags, likes, comments, timestamp, media URLs, carousel children, plus every follower-denominated metric | `shortCode`, `caption`, `hashtags`, `likesCount`, `commentsCount`, `timestamp`, `displayUrl`, `childPosts`, `engagementRate`, `engagementTier`, `engagementTotal`, `likesPerFollower`, `commentsPerFollower`, `followersCount` |
| **Reels** (`section: "reels"`) | Same key set as posts, with `type` forced to `"Video"` | `type`, `shortCode`, `likesCount`, `commentsCount`, `displayUrl`, `engagementRate`, `engagementTier`, `ownerUsername` |
| **Account summary** (`section: "summary"`) | Per-account rollup with its own sample size | `type` (`"AccountSummary"`), `isSummary`, `ownerUsername`, `avgEngagementRate`, `avgLikes`, `avgComments`, `maxEngagementRate`, `minEngagementRate`, `postsAnalysed`, `ratedPostsAnalysed`, `engagementTier` |
| **Comments** (`section: "comments"`) | Comment text, author, likes, reply count, parent post | `id`, `text`, `ownerUsername`, `ownerProfilePicUrl`, `timestamp`, `likesCount`, `repliesCount`, `owner`, `postUrl`, `postShortCode` |
| **Profile details** (`section: "profiles"`) | Full public profile record | `username`, `fullName`, `biography`, `externalUrl`, `followersCount`, `followsCount`, `postsCount`, `isVerified`, `isPrivate`, `isBusinessAccount`, `profilePicUrl`, `profilePicUrlHD`, `category` |
| **Mentions** (`section: "mentions"`) | One row per `@handle` found in a caption | `type` (`"mention"`), `mentionedUsername`, `ownerUsername`, `postShortCode`, `postUrl`, `timestamp`, `inputUrl` |
| **Search results** (`section: "search"`) | Users, hashtags or places matching a query | `type`, `id`, `username`, `full_name`, `follower_count`, `name`, `media_count`, `lat`, `lng`, `url` |
| **Accounting rows** (`section: "accounting"`) | Diagnostics for accounts whose follower count could not be recovered — **never billed** | `type` (`"Accounting"`), `ownerUsername`, `url`, `errorReason`, `message` |

That is seven billable entity types plus one free diagnostic type from a single run — the difference between a breadth scraper and a single-purpose follower counter.

#### Need more Instagram data?

API Empire publishes several focused Instagram Actors that pair well with this one. **Instagram Audience Quality Checker** looks at the audience rather than the posts, and **Instagram Profile Bio Contact Extractor** pulls contact details out of bios at scale. For content research, **Instagram Hashtag Content Planner** and **Instagram Reel Hashtag Mention Extractor** cover discovery, while **Instagram Comment Moderation Scraper** and **Instagram Mention Sentiment Analyzer** handle the conversation around a post. Rank accounts here first, then hand the winners to whichever fits.

### 📈 How is the engagement rate calculated?

The formula lives in one function, `engagement_rate()` in `src/engagement.py`, and it is the industry-standard follower-denominated definition:

```
engagementRate = (likesCount + commentsCount) / followersCount * 100
```

Read the details off the implementation, because the details are where engagement-rate tools usually disagree:

- **Which interactions are summed:** exactly two — `likesCount` and `commentsCount`. Nothing else. Saves, shares, story replies and video views are not available on the public surfaces this Actor reads, so they are not in the numerator and are not silently estimated.
- **What it is divided by:** `followersCount` for the post's owner, recovered once per account by `ProfileEnricher` and cached for the rest of the run. It is not divided by reach, impressions or the account's own average.
- **How many posts:** none — this is a **per-post** figure. Each post row gets its own rate computed from its own likes and comments. Averaging happens separately, in the `AccountSummary` row.
- **Percentage or fraction:** a **percentage**, rounded to **4 decimal places**. 4,200 likes plus 130 comments on a 120,000-follower account yields `engagementRate: 3.6083`, not `0.036083`.

Two derived percentages ship alongside it, on the same denominator and rounding: `likesPerFollower` = `likesCount / followersCount * 100`, `commentsPerFollower` = `commentsCount / followersCount * 100`. `engagementTotal` is the plain undivided sum `likesCount + commentsCount`.

#### The divide-by-zero path

`engagement_rate()` guards the denominator before dividing: if `followersCount` is `None`, `0` or negative it returns `None`. It never returns `0`, never estimates, and never substitutes a platform average. What lands in the row:

| Situation | `followersCount` | `engagementRate` | `engagementTier` | `likesPerFollower` / `commentsPerFollower` | `engagementTotal` |
| --- | --- | --- | --- | --- | --- |
| Follower count recovered | integer | number (4 dp) | tier label | numbers (4 dp) | number |
| Profile lookup failed or timed out | `null` | `null` | `null` | `null` | number |
| Account reports zero followers | `0` | `null` | `null` | `null` | number |

A `null` rate is visible in a spreadsheet and in a `WHERE engagementRate IS NULL` query. A fabricated `0` is not — it looks like a real, terrible rate and quietly poisons every average downstream. Every account that fails enrichment also produces an **uncharged** `Accounting` row with `errorReason: "profile_enrichment_unavailable"`, so a null column always has a paper trail.

#### The zero-posts path in the rollup

`build_account_summary()` slices `posts[:postsToAverage]` and averages only entries that have values:

- `avgEngagementRate` — mean of the non-null `engagementRate` values in the slice, rounded to 4 dp. If **no** post in the slice has a usable rate, this is `null`, not `0`.
- `avgLikes` / `avgComments` — mean of the non-null `likesCount` / `commentsCount` values, rounded to 4 dp; `null` on an empty sample.
- `maxEngagementRate` / `minEngagementRate` — the best and worst rate in the slice, or `null` when there are none.
- `postsAnalysed` — the size of the slice. Always published.
- `ratedPostsAnalysed` — how many of those posts had a usable follower count. When this is `0`, every average above is `null` by construction.

An account only gets a rollup row if at least one post or reel was scraped for it, so `postsAnalysed` is never `0`, but `ratedPostsAnalysed` frequently is on blocked runs. Read them as a pair: `avgEngagementRate: 4.11` over `ratedPostsAnalysed: 12` is a claim; over `ratedPostsAnalysed: 1` it is an anecdote.

#### Engagement tier bands

`engagementTier` is a **labelling convention applied to the computed number, not a measurement of your corpus.** The bands are hard-coded in `src/engagement.py`:

| `engagementRate` | `engagementTier` |
| --- | --- |
| `null` | `null` |
| below 0.5 | `🔴 Low` |
| 0.5 up to (not including) 1.0 | `🟠 Below average` |
| 1.0 up to (not including) 3.0 | `🟢 Average` |
| 3.0 up to (not including) 6.0 | `🔵 High` |
| 6.0 and above | `🟣 Very high` |

The same bands are applied to `avgEngagementRate` to set `engagementTier` on the `AccountSummary` row.

### How does this Instagram scraper differ from the official Instagram Graph API?

Meta's Instagram Platform API is an owner-side API: it returns data about accounts you or your clients control, after you have built a Facebook app, obtained an access token, and passed app review for the permissions you need. This Actor is an observer-side scraper: it returns the public data any logged-out visitor can see, for any public account, with no app, no token and no review.

| Feature | Instagram Graph API (Meta's Instagram Platform) | Instagram API Scraper: Engagement Rate Per Follower |
| --- | --- | --- |
| Accounts you can query | Instagram Professional accounts you own or that have granted your app access, via a linked Facebook Page | Any public Instagram profile, post, reel or hashtag |
| Credentials required | Facebook app, OAuth access token, permission scopes | None — `.actor/actor.json` has no credential field |
| Approval process | App review before production permissions | None; run the Actor from the Apify Console immediately |
| Follower-denominated engagement rate | Not returned; you compute it from insights endpoints yourself | Returned per post as `engagementRate`, plus a per-account `avgEngagementRate` |
| Entity coverage in one call | Separate endpoints per resource | Posts, reels, comments, profiles, mentions and search in one Actor via `resultsType` |
| Output shape | Meta's JSON, versioned with the Graph API | Stable Apify dataset rows with fixed key names, exportable as JSON/CSV/Excel/XML |
| Competitor data | Limited to the Business Discovery surface for professional accounts | Any public account, including ones you have no relationship with |

Meta's [Instagram Platform documentation](https://developers.facebook.com/docs/instagram-platform) is the authority on what the Graph API grants and how to apply. No rate-limit or quota figures are quoted here — they vary by app tier and are only accurate in Meta's docs.

Use the Graph API when you own the accounts, need private insights such as reach and saves, and can carry the app-review overhead. Use this Actor when you need public, cross-account, competitor-inclusive data and a comparable engagement rate today.

### Why do developers and teams scrape Instagram?

Instagram engagement data drives four very different workflows, and the fields that matter differ sharply between them.

#### For AI engineers and agent builders

Every row is typed JSON with stable keys, so it drops into a retrieval pipeline without a parsing layer. A typical agent tool calls the Actor with `resultsType: "posts"` and `resultsLimit: 24`, embeds `caption` plus `hashtags` into a vector store, and stores `engagementRate`, `engagementTier` and `timestamp` as metadata filters. The agent then answers "show me this brand's above-average posts from last quarter" as a filtered similarity search rather than a scrape-and-hope loop. Because `engagementRate` is `null` rather than `0` when unknown, filters never silently include unmeasured rows.

#### For marketers and influencer teams

Follower count is the vanity metric; engagement rate per follower is the one that predicts campaign performance. Feed a creator shortlist into `accountsToAnalyse`, set `postsToAverage: 12`, and read the `AccountSummary` rows: `avgEngagementRate` beside `ratedPostsAnalysed` gives you both the number and how much to trust it. Set `minEngagementRate: 3` and the run keeps only posts clearing 3%. `biography` and `externalUrl` come back on the same rows, so outreach lists build themselves.

#### For researchers and analysts

The Actor touches only publicly visible Instagram surfaces — no login, no cookies, no private endpoints — which keeps the collection scope defensible in a methods section. A typical study samples N public accounts, fixes `postsToAverage` so every account contributes the same window, and reports `postsAnalysed` and `ratedPostsAnalysed` as the realized per-account sample size. Because missing denominators produce `null` instead of `0`, non-response stays measurable rather than being absorbed into the mean, and the `Accounting` rows document which accounts dropped out and why.

#### For developers building data products

Schedule the Actor from the Apify Console, point a webhook at your ingest endpoint, and you have a normalized engagement feed without maintaining TLS fingerprints, proxy ladders or GraphQL document IDs. The dataset shape is stable across runs, so `INSERT INTO posts (short_code, owner_username, engagement_rate, ...)` keeps working. Filter `type != "Accounting"` on ingest to drop diagnostics and use `section` to route rows into per-entity tables. The Apify API serves the dataset as JSON or CSV, so any language that speaks HTTP can consume it.

### How to scrape Instagram engagement rates (step by step)

This Actor runs on the Apify platform only — from the Apify Console UI or through the Apify API. There is no separate signup and no service-specific API key.

1. **Open the Actor on its Apify Store listing** and click **Try for free**, or open it in the Apify Console if you already have it saved.
2. **Fill in `accountsToAnalyse`** — bare usernames like `humansofny`, full profile URLs, or `/p/<shortcode>/` and `/reel/<shortcode>/` links. Nothing is schema-required, but the run stops immediately unless you supply at least one target here (or in `directUrls`) or a non-empty `search`.
3. **Choose `resultsType`** — `posts` for the engagement-rate workflow, or `comments`, `details`, `mentions`, `reels`, `stories` — and set `resultsLimit` per URL. Add `onlyPostsNewerThan` to bound the window and `minEngagementRate` to screen by threshold.
4. **Tune the rate settings**: leave `computeEngagementRate` and `fetchFollowerCounts` on, set `postsToAverage` to your rollup window, and raise `profileFetchTimeout` if you run through a slow proxy.
5. **Start the run**, watch the live log (it prints each post with its likes and comments), then **download the results** as JSON, CSV, Excel, XML or HTML — or read them straight from the Apify API.

#### What to do when Instagram changes its structure

Instagram rotates endpoints, document IDs and anti-bot checks continuously. This Actor is maintained against those changes, and the dataset schema is the contract: key names, types and the `null`-not-zero rule stay put, so database columns, dashboards and agent tools keep working across updates. A run that suddenly returns empty sections or a flood of `Accounting` rows is the signal to report.

### What changed in Instagram scraping recently?

The most consequential shift is that Instagram now gates its public JSON endpoints on TLS client fingerprint, not just on headers. Everything below is traceable to this Actor's own source, not to hearsay.

- **TLS fingerprint gating.** `requirements.txt` records that plain `requests` gets HTTP 429 on `/api/v1/users/web_profile_info/` while a Chrome fingerprint returns 200, and `src/engagement.py` ships three fingerprints (`chrome124`, `chrome120`, `chrome110`) tried in order. Header spoofing alone no longer clears the check.
- **Search moved behind auth.** `src/scraper.py` logs, on a non-200 from `/api/v1/web/search/topsearch/`: "Instagram has been requiring auth for /web/search/topsearch since 2025". Expect the `search` input to return few or zero rows on a logged-out run.
- **Rotating GraphQL document IDs.** The shortcode query's `doc_id` is a constant Meta rotates; the Actor keeps a three-step fallback (GraphQL → `/api/v1/media/<shortcode>/info/` → embedded page JSON) because any one path can be turned off.
- **Proxy-group churn.** `src/main.py` documents that an Apify proxy group literally named `DATACENTER` returned HTTP 407 on every request; the Actor warms `BUYPROXIES94952` instead.

For DIY scrapers, breakage is routine rather than exceptional. For users of this Actor no action is required: fingerprints, fallbacks and the proxy ladder are maintained inside it, and the output keys do not move when the transport does.

### ⬇️ Input

Every parameter below is read directly from `.actor/actor.json`. None is marked required — but the run exits immediately if you supply neither a target URL (in `accountsToAnalyse` or `directUrls`) nor a non-empty `search`.

| Parameter | Required | Type | Description | Example Value |
| --- | --- | --- | --- | --- |
| `accountsToAnalyse` | No | `array` of strings (`stringList` editor) | Profiles, posts or reels to measure; bare usernames work. Prefilled `["https://www.instagram.com/humansofny/"]`. Merged with `directUrls`, de-duplicated, order preserved. No schema default. | `["humansofny", "https://www.instagram.com/nasa/"]` |
| `computeEngagementRate` | No | `boolean` | Documented as adding `engagementRate` using `(likes + comments) ÷ followers × 100`. **Default: `true`.** See the caveat below the table. | `true` |
| `postsToAverage` | No | `integer` — min `1`, max `500` | How many scraped posts per account feed the `AccountSummary` rollup (`avgEngagementRate`, `avgLikes`, `avgComments`), with `postsAnalysed` always published beside them. Missing or `0` falls back to 12. **Default: `12`.** | `12` |
| `minEngagementRate` | No | `integer` — min `0`, max `100` | Keep only posts whose `engagementRate` is greater than or equal to this percentage; posts with a `null` rate are dropped too whenever this is above `0`. Applies to `posts`/`reels` rows only. **Default: `0`** (keep everything). | `3` |
| `fetchFollowerCounts` | No | `boolean` | Look each account up once per run for `followersCount`, `followsCount`, `postsCount`, `biography`, `externalUrl` (unwrapped from `l.instagram.com`), `isVerifiedAccount`, `isPrivateAccount`. One request per account, cached for the run. **Default: `true`.** | `true` |
| `profileFetchTimeout` | No | `integer` seconds — min `5`, max `120` | Per-profile lookup timeout. On timeout the posts still return with `engagementRate: null` plus an uncharged accounting row. Values below 5, or missing, are clamped up. **Default: `25`.** | `25` |
| `directUrls` | No | `array` of strings (`stringList` editor) | Legacy key from the original Instagram API Scraper. **Merged with** `accountsToAnalyse` rather than overriding it, so saved inputs keep working. Also accepts `{"url": "..."}` objects. No schema default. | `["https://www.instagram.com/natgeo/"]` |
| `resultsType` | No | `string`, `select` — enum `posts`, `comments`, `details`, `mentions`, `reels`, `stories` | What to pull per URL. An unrecognized value logs a warning and falls back to `posts`. **Default: `"posts"`.** | `"posts"` |
| `resultsLimit` | No | `integer` — min `1`, max `100000` | Max items per URL. **Schema default: `10`.** If the key is absent from the run input or below 1, the engine substitutes `30` — see the pitfall note. | `24` |
| `onlyPostsNewerThan` | No | `string`, `datepicker`, `absoluteOrRelative` | Absolute `YYYY-MM-DD` or a relative window. The schema pattern accepts `day`/`week`/`month`/`year`; the parser also accepts `hour`. Months count as 30 days, years as 365. UTC. No default. | `"90 days"` |
| `addParentData` | No | `boolean` | Adds a `parentData` object (`name`, `type`, `url`) naming the source profile or hashtag to each post/reel/mention row. **Default: `false`.** | `false` |
| `search` | No | `string`, `textfield` | Run an Instagram search alongside or instead of the URLs. Empty disables search. **Default: `""`.** | `"streetphotography"` |
| `searchType` | No | `string`, `select` — enum `user`, `hashtag`, `place` | What the query targets. An unrecognized value falls back to `hashtag`. **Default: `"hashtag"`.** | `"hashtag"` |
| `searchLimit` | No | `integer` — min `1`, max `250` | Number of search results. **Schema default: `1`.** If absent or below 1 the engine substitutes `20`; above 250 is clamped to 250. | `20` |
| `proxyConfiguration` | No | `object`, `proxy` editor | **Default and prefill: `{"useApifyProxy": false}`** — direct to Instagram. Enabling Apify Proxy forces a starting tier: a `RESIDENTIAL` group starts and stays residential, any other group starts on datacenter. Either way the Actor warms both pools at startup and escalates automatically when blocked. | `{"useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"]}` |

#### Behaviour worth knowing before you run

- **`computeEngagementRate: false` does not switch the maths off on its own.** The follower lookup is gated on `computeEngagementRate` **OR** `fetchFollowerCounts`, and the decoration step then runs unconditionally on `posts`/`reels` rows. With `computeEngagementRate: false` and `fetchFollowerCounts` at its default `true`, the profile is still fetched and `engagementRate` still written. Set **both** to `false` to genuinely skip it — then every follower-denominated field comes back `null`.
- **`resultsLimit` and `searchLimit` differ between Console and API.** The Console prefills the schema defaults (`10` and `1`). A raw API call that omits the keys gets the engine fallbacks — `30` and `20` — three times and twenty times the billable rows. Always send the keys explicitly from code.
- **`resultsType: "stories"` runs the reels scraper.** `reels` and `stories` share one code path; output is counted and emitted as reels. There is no separate story fetch.
- **Single post and reel URLs do not get an engagement rate.** A `/p/<shortcode>/` or `/reel/<shortcode>/` input under `resultsType: "posts"` goes down the post-detail path and lands in the `details` section — and only `posts` and `reels` sections are decorated. Those rows carry no `engagementRate`, no `followersCount`, and produce no `AccountSummary`. **To measure engagement rate, pass profile URLs or bare usernames.** This is the most common input mistake with this Actor.

#### Example JSON input

```json
{
  "accountsToAnalyse": [
    "humansofny",
    "https://www.instagram.com/nasa/",
    "natgeo"
  ],
  "computeEngagementRate": true,
  "postsToAverage": 12,
  "minEngagementRate": 0,
  "fetchFollowerCounts": true,
  "profileFetchTimeout": 25,
  "directUrls": [],
  "resultsType": "posts",
  "resultsLimit": 24,
  "onlyPostsNewerThan": "90 days",
  "addParentData": false,
  "search": "",
  "searchType": "hashtag",
  "searchLimit": 1,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

### ⬆️ Output

Results are pushed live to the run's default dataset as each item is produced, so you can start reading rows before the run finishes. Every row is typed, normalized JSON with stable key names and a `section` tag, and Apify exports the dataset as JSON, JSONL, CSV, Excel, XML, RSS or HTML. Eight dataset views are pre-declared, so the Console shows the right columns per row type with no configuration.

#### Scraped post (with engagement rate)

This is the full shape of a `section: "posts"` row from a profile input, with all follower-denominated fields present.

```json
{
  "id": "3412998765432109876",
  "type": "Sidecar",
  "shortCode": "C8xTqLpMv2Z",
  "caption": "Twelve years on the same corner. #humansofny #streetphotography @nikonusa",
  "hashtags": ["humansofny", "streetphotography"],
  "mentions": ["nikonusa"],
  "url": "https://www.instagram.com/p/C8xTqLpMv2Z/",
  "commentsCount": 130,
  "firstComment": "This one hit hard.",
  "latestComments": [ "…see the comment shape below — same keys, nested here…" ],
  "dimensionsHeight": 1350,
  "dimensionsWidth": 1080,
  "displayUrl": "https://scontent.cdninstagram.com/v/t51.../main.jpg",
  "images": [
    "https://scontent.cdninstagram.com/v/t51.../main.jpg",
    "https://scontent.cdninstagram.com/v/t51.../child1.jpg"
  ],
  "alt": "A man in a grey coat standing beside a newsstand",
  "likesCount": 4200,
  "timestamp": "2026-06-18T13:05:44.000Z",
  "childPosts": [ "…one object per carousel slide — key set listed below…" ],
  "ownerFullName": "Humans of New York",
  "ownerUsername": "humansofny",
  "ownerId": "1234567890",
  "isCommentsDisabled": false,
  "inputUrl": "humansofny",
  "section": "posts",
  "followersCount": 120000,
  "engagementRate": 3.6083,
  "engagementTier": "🔵 High",
  "likesPerFollower": 3.5,
  "commentsPerFollower": 0.1083,
  "engagementTotal": 4330,
  "followsCount": 312,
  "postsCount": 7841,
  "biography": "New York City, one story at a time.",
  "externalUrl": "https://www.humansofnewyork.com",
  "isVerifiedAccount": true,
  "isPrivateAccount": false,
  "profileSource": "web_profile_info"
}
```

Each `childPosts` entry carries `id`, `type` (`"Image"`/`"Video"`), `shortCode`, `url`, `displayUrl`, `dimensionsHeight`, `dimensionsWidth`, `alt`, `ownerId`, `caption`, `hashtags`, `mentions`, `images`, `latestComments`, `childPosts`, `commentsCount`, `firstComment`, `likesCount`, `timestamp` — the last four are always empty, `0` or `null` on children. `latestComments` entries use the comment shape shown below. `parentData` appears only when `addParentData` is `true`: `{"name": "humansofny", "type": "profile", "url": "https://www.instagram.com/humansofny/"}`.

#### Scraped reel

A reel row carries the **exact same key set** as the post row above — including all thirteen enrichment keys — but lands in `section: "reels"` with `type` forced to `"Video"` and `childPosts` always empty. The distinguishing values:

```json
{
  "id": "3401122334455667788",
  "type": "Video",
  "shortCode": "C8kLmNoPqRs",
  "caption": "Behind the shot. #bts",
  "hashtags": ["bts"],
  "url": "https://www.instagram.com/p/C8kLmNoPqRs/",
  "likesCount": 18740,
  "commentsCount": 402,
  "timestamp": "2026-06-11T17:40:12.000Z",
  "displayUrl": "https://scontent.cdninstagram.com/v/t51.../reelcover.jpg",
  "dimensionsHeight": 1920,
  "dimensionsWidth": 1080,
  "childPosts": [],
  "ownerUsername": "humansofny",
  "section": "reels",
  "followersCount": 120000,
  "engagementRate": 15.9517,
  "engagementTier": "🟣 Very high",
  "likesPerFollower": 15.6167,
  "commentsPerFollower": 0.335,
  "engagementTotal": 19142,
  "profileSource": "web_profile_info"
}
```

#### Account summary rollup

One row per account, emitted after every post for that account has been seen.

```json
{
  "type": "AccountSummary",
  "isSummary": true,
  "ownerUsername": "humansofny",
  "url": "https://www.instagram.com/humansofny/",
  "followersCount": 120000,
  "followsCount": 312,
  "postsCount": 7841,
  "biography": "New York City, one story at a time.",
  "externalUrl": "https://www.humansofnewyork.com",
  "isVerifiedAccount": true,
  "isPrivateAccount": false,
  "profileSource": "web_profile_info",
  "postsAnalysed": 12,
  "ratedPostsAnalysed": 12,
  "avgEngagementRate": 4.1172,
  "avgLikes": 4712.5,
  "avgComments": 228.1667,
  "maxEngagementRate": 15.9517,
  "minEngagementRate": 1.2044,
  "engagementTier": "🔵 High",
  "section": "summary"
}
```

Note the key collision worth planning for: `minEngagementRate` is both an *input* parameter (the filter threshold) and an *output* key on this row (the lowest rate in the sample). They are unrelated values.

#### Scraped comment

```json
{
  "id": "17998234512340987",
  "text": "This one hit hard.",
  "ownerUsername": "marta.reads",
  "ownerProfilePicUrl": "https://scontent.cdninstagram.com/v/t51.../marta.jpg",
  "timestamp": "2026-06-18T14:22:07.000Z",
  "repliesCount": 2,
  "replies": null,
  "likesCount": 41,
  "owner": {
    "username": "marta.reads",
    "id": "1873456210",
    "full_name": "Marta R.",
    "profile_pic_url": "https://scontent.cdninstagram.com/v/t51.../marta.jpg",
    "profile_pic_id": "3390012345678901234",
    "is_verified": false,
    "is_private": false,
    "is_mentionable": true,
    "latest_reel_media": 0
  },
  "postUrl": "https://www.instagram.com/p/C8xTqLpMv2Z/",
  "postShortCode": "C8xTqLpMv2Z",
  "section": "comments"
}
```

`replies` is always `null` — the Actor returns the reply *count* in `repliesCount`, not the reply bodies.

#### Scraped profile details

Produced by `resultsType: "details"` on a profile URL. This row is **not** decorated with engagement fields.

```json
{
  "id": "1234567890",
  "username": "humansofny",
  "fullName": "Humans of New York",
  "biography": "New York City, one story at a time.",
  "externalUrl": "https://l.instagram.com/?u=https%3A%2F%2Fwww.humansofnewyork.com",
  "followersCount": 120000,
  "followsCount": 312,
  "postsCount": 7841,
  "isVerified": true,
  "isPrivate": false,
  "isBusinessAccount": false,
  "profilePicUrl": "https://scontent.cdninstagram.com/v/t51.../pp.jpg",
  "profilePicUrlHD": "https://scontent.cdninstagram.com/v/t51.../pp_hd.jpg",
  "category": "Photographer",
  "inputUrl": "humansofny",
  "section": "profiles"
}
```

`externalUrl` on this row is returned exactly as Instagram serves it, which may be a wrapped `l.instagram.com` redirect. The `externalUrl` written by the enrichment path onto post, reel and summary rows is unwrapped to the destination.

#### Scraped mention

```json
{
  "type": "mention",
  "mentionedUsername": "nikonusa",
  "postShortCode": "C8xTqLpMv2Z",
  "postUrl": "https://www.instagram.com/p/C8xTqLpMv2Z/",
  "ownerUsername": "humansofny",
  "timestamp": "2026-06-18T13:05:44.000Z",
  "inputUrl": "humansofny",
  "section": "mentions"
}
```

#### Search result

Shape varies by `searchType`. A hashtag result:

```json
{
  "type": "hashtag",
  "id": "17841562893027714",
  "name": "streetphotography",
  "media_count": 128400000,
  "search_result_subtitle": "128M posts",
  "url": "https://www.instagram.com/explore/tags/streetphotography/",
  "section": "search"
}
```

A `user` result returns `type`, `id`, `username`, `full_name`, `is_verified`, `is_private`, `profile_pic_url`, `follower_count`; a `place` result returns `type`, `id`, `name`, `subtitle`, `lat`, `lng`, `url`.

#### Accounting row (never charged)

```json
{
  "type": "Accounting",
  "ownerUsername": "someblockedaccount",
  "url": "https://www.instagram.com/someblockedaccount/",
  "errorReason": "profile_enrichment_unavailable",
  "message": "Follower count could not be recovered for this account, so engagementRate is null (not zero) on its posts.",
  "section": "accounting"
}
```

### 💸 What gets charged, and what does not

This Actor is pay-per-event with a single event: **`row_result`**. One `row_result` is charged for each row pushed to the dataset — post, reel, comment, profile detail, mention, search result and `AccountSummary` rows all count.

**`Accounting` rows are pushed uncharged.** Every account whose follower count could not be recovered produces one diagnostic row explaining the `null`, written without a charged event. That is the only free row type — if a row is in your dataset and it is not an `Accounting` row, it was billed. Rows removed by `minEngagementRate` are never pushed at all, so they are never charged; note they *are* still counted in the `AccountSummary` averages, because the rollup accumulates posts before the filter runs.

To exclude the free diagnostic rows downstream, filter on any of these:

```
type != "Accounting"          # by row type
section != "accounting"       # by section tag
errorReason == null           # by presence of the diagnostic field
```

Cost-control notes from the engine's actual behaviour: `resultsType: "mentions"` on a profile emits **one charged row per `@handle` in a caption**, so a post with five mentions bills five rows; a hashtag input under `resultsType: "posts"` triggers one profile lookup and one charged `AccountSummary` row for **each distinct post owner**; and an API call that omits `resultsLimit` gets 30 items per URL instead of the schema's 10.

### How does this Actor compare to other Instagram scrapers?

| Feature | Instagram API Scraper: Engagement Rate Per Follower | Generic Instagram follower/profile scraper |
| --- | --- | --- |
| Entity coverage in one Actor | Posts, reels, comments, profile details, mentions, search results and per-account rollups, selected by `resultsType` | Typically one entity — follower lists, or follower counts, or posts |
| Engagement rate | Computed per post as `engagementRate` and per account as `avgEngagementRate`, formula published above | Not returned; you join follower counts to post counts yourself |
| Missing-data handling | `null` never `0`, plus an uncharged `Accounting` row naming the account and reason | Usually a silent `0` or a dropped row |
| Sample-size disclosure | `postsAnalysed` and `ratedPostsAnalysed` shipped beside every average | Averages published without the denominator |
| Credentials | None — no login, no cookies, no `sessionid` field in the schema | Varies; several require a session cookie |
| Anti-block handling | Automatic ladder: direct → datacenter → residential, sticky on residential, plus three rotating Chrome TLS fingerprints on the profile endpoint | Varies; often requires you to configure a proxy yourself |

For context, quoted only as the competing listings state it and reviewed on the Apify Store 2026-07-25 — none of it measured here: `apify/instagram-followers-count-scraper` returns follower and following counts with no engagement-rate output and directs users to a separate Actor per Instagram entity type. `scraping_solutions/instagram-scraper-followers-following-no-cookies` advertises follower/following list export with continuation tokens and states free-tier API runs are capped at 1,000 results per run. `kaitoeasyapi/premium-x-follower-scraper-following-data` targets X/Twitter rather than Instagram and advertises "$0.1 per 1,000 profiles", "50-70 profiles per second" and a "99.9% uptime guarantee" — no equivalent figures are claimed for this Actor.

If you're building an AI agent or RAG pipeline, the output-format row is the decision-maker — parsing HTML inside an agent loop is a reliability failure mode, not a feature. The second row matters almost as much: an agent that has to compute engagement rate itself needs a second data source for follower counts and a place to put the join.

### How many results can you scrape?

There is no hard cap inside the Actor beyond `resultsLimit`, which accepts up to `100000` per URL, and `searchLimit`, which is clamped to a hard ceiling of `250` in the engine regardless of what you send. The real ceiling is Instagram's own pagination.

Pagination as implemented: profile posts walk the user feed endpoint with `max_id` cursors, twelve items per page, stopping when `resultsLimit` is reached, when `onlyPostsNewerThan` is crossed, when the feed reports no more pages, or at a hard cap of 200 pages. Reels paginate the same way through the clips endpoint with `paging_info.max_id`. Three consecutive failed pages abort that profile. Between pages the Actor sleeps a randomized 2.0–3.0 seconds, and 1.5–3.0 seconds between input URLs.

Two platform-side limits, both documented in this Actor's own input schema and source: Instagram's profile pagination stops near 2,400 items per profile, so a `resultsLimit` far above that returns no more; and `comments` mode returns only the preview comments attached to the post payload — there is no comment-pagination loop, so `resultsLimit` caps what Instagram already sent, not what exists.

No benchmark timings are published here because none have been measured for this Actor.

### Integrate this Instagram scraper and automate your workflow

This Actor works with any language or tool that can send an HTTP request to the Apify API. It runs on the Apify platform only — from the Apify Console or via the Apify API — using your Apify API token. There is no separate service key.

#### REST API integration

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_API_TOKEN>")

run = client.actor("<YOUR_USERNAME>/instagram-api-scraper-engagement-rate-per-follower").call(
    run_input={
        "accountsToAnalyse": ["humansofny", "natgeo"],
        "resultsType": "posts",
        "resultsLimit": 24,
        "postsToAverage": 12,
        "minEngagementRate": 0,
        "computeEngagementRate": True,
        "fetchFollowerCounts": True,
    }
)

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    if item.get("type") == "Accounting":
        continue  # free diagnostic row
    print(item.get("ownerUsername"), item.get("engagementRate"), item.get("engagementTier"))
```

Works in Python, Node.js, Go, Ruby and cURL — the Apify API is plain REST, and the dataset can also be fetched directly as JSON or CSV from `/v2/datasets/<datasetId>/items`.

#### Automation platforms (n8n, Make, LangChain)

**n8n** — use the **Apify node** with the *Run Actor* operation, point it at this Actor's ID, paste the input JSON, and chain *Get Dataset Items*. An IF node on `type != "Accounting"` separates billable data from diagnostics before your database node.

**Make** — the **Apify** app provides *Run an Actor* and *Watch Actor Runs* modules. Run on a schedule, then iterate the dataset and route rows by `section` — `posts` to a sheet, `summary` to a scorecard, `accounting` to an alert channel.

**LangChain** — wrap the Actor call in a tool function returning the dataset items directly. Rows are already typed JSON with stable keys, so tool output enters the model context with no parsing step; `engagementRate`, `engagementTier` and `postsAnalysed` make good structured-output fields for a creator-vetting chain.

The Apify platform's own integrations — webhooks, scheduling, Zapier, Slack, Google Sheets, Google Drive, Airbyte — also work with this Actor's dataset without extra code.

### Is it legal to scrape Instagram?

Scraping publicly accessible Instagram data is generally lawful in the jurisdictions where Apify Actors commonly run, but what you do with the data afterwards is what regulators care about. This Actor returns only publicly available data: it performs no login, holds no credentials, and reads nothing that a logged-out visitor cannot see. Private accounts are not accessed.

The output contains personal data — usernames, full names, biographies, profile pictures and comment text belonging to identifiable people. That brings it within the GDPR in the EU/UK and the CCPA in California. You need a lawful basis before you store, enrich or profile that data, you need to be able to honour access and erasure requests, and legitimate-interest assessments should be documented before bulk collection, not after.

Consult legal counsel for commercial use cases involving bulk personal data.

### ❓ Frequently asked questions

#### Does this Instagram scraper work without an Instagram account?

Yes. No Instagram account, password, cookie or `sessionid` is required or accepted — the input schema contains no credential field at all. The Actor reads public endpoints over plain HTTP with rotating browser TLS fingerprints. You only need an Apify account to run the Actor itself.

#### How is the engagement rate actually calculated?

`(likesCount + commentsCount) / followersCount * 100`, per post, rounded to 4 decimal places, expressed as a percentage. Only likes and comments are in the numerator. The denominator is the post owner's follower count, fetched once per account and cached for the run. If the follower count is missing, zero or negative the result is `null`, never `0`. Full detail — including the tier bands and the rollup rules — is in the engagement-rate section above.

#### How often is the scraped data updated?

Every run fetches live from Instagram. Nothing is cached between runs. Within a single run, one profile lookup per username is cached and reused for all of that account's posts, so a 24-post run costs one profile request, not 24. Schedule the Actor in the Apify Console for recurring snapshots.

#### What happens when a post is deleted, or an account is private or blocked?

Deleted posts simply do not appear in the feed response — the Actor returns fewer items rather than erroring. If a profile lookup fails or times out, its posts are still returned with `followersCount`, `engagementRate`, `engagementTier`, `likesPerFollower` and `commentsPerFollower` all `null`, plus an uncharged `Accounting` row carrying `errorReason: "profile_enrichment_unavailable"`. Three consecutive failed pages abort that profile and the run moves to the next input. A run with no usable targets ends with zero items rather than a crash.

#### Can I scrape private Instagram accounts or login-gated content?

No. Only publicly accessible content is returned. Private accounts, follower and following lists, direct messages, and anything behind Instagram's login wall are out of scope — the Actor never authenticates, so it cannot see them. Instagram search is also affected: the Actor's own log notes that `/api/v1/web/search/topsearch/` has required auth since 2025, so the `search` input may return few or no rows on a logged-out run.

#### Where do I run this Actor, and what do I need?

On the Apify platform — from the Apify Console UI or through the Apify API with your Apify API token. There is no separate signup and no service-specific key. Pricing is pay-per-event on `row_result`; the current rate is shown on the Actor's Apify Store listing.

#### Does this work for AI agent workflows and LLM pipelines?

Yes. The Actor is callable as an HTTP endpoint by any agent framework — LangChain, LlamaIndex, a custom tool-use loop, or an n8n/Make automation. Every response is typed, normalized JSON with stable field names: no HTML, no selectors, no parsing step before passing rows to an LLM, and nested structures (`latestComments`, `childPosts`, `owner`, `parentData`) are real JSON objects rather than stringified blobs. Filter `type != "Accounting"` for a clean stream, use `section` to route entity types, index `caption` and `hashtags` into a vector store with `engagementRate` and `timestamp` as metadata filters, and treat `null` on `engagementRate` as genuinely unknown rather than as a low score.

#### How does the Actor handle Instagram's anti-bot system?

With three countermeasures, all implemented in the source. **TLS impersonation** — the enrichment client tries `chrome124`, `chrome120` and `chrome110` fingerprints in order. **A proxy escalation ladder** — the run starts direct, escalates to an Apify datacenter pool on a blocking status (401, 403, 407, 429, 451, 503), then to residential, and once on residential it stays there rotating among six pre-warmed endpoints. **Endpoint fallbacks** — profile data falls back from the JSON API to HTML extraction; post detail falls back from GraphQL to the media info endpoint to embedded page JSON. Randomized delays sit between pages and URLs, and 429/503 responses back off progressively.

#### How does it compare to other Instagram scrapers on the Apify Store?

The observable difference is entity breadth plus the follower denominator. Neither `apify/instagram-followers-count-scraper` nor `scraping_solutions/instagram-scraper-followers-following-no-cookies` returns a per-post `engagementRate`, an `engagementTier`, or a rollup row carrying its own `postsAnalysed` — see the comparison section above for what their listings do advertise, as reviewed on 2026-07-25. This Actor returns seven billable entity types in one run and publishes the engagement formula rather than a black-box score.

#### Can I use this without managing proxies?

Yes. The default `proxyConfiguration` is `{"useApifyProxy": false}` — the Actor talks to Instagram directly and escalates to Apify's datacenter and then residential pools only when Instagram pushes back. Both fallback pools are warmed at startup whether or not you enable proxies, so no configuration is needed. To force a starting tier, enable Apify Proxy and pick a group: a `RESIDENTIAL` group starts and stays residential; any other group starts on datacenter.

#### What happens when Instagram changes its structure or blocks the scraper?

The Actor is maintained, and the dataset schema is the contract — field names, types and the `null`-not-zero rule stay stable on your end, so ingest jobs and dashboards keep working across transport changes. Multiple fallback paths already exist for the endpoints most likely to move. No numeric turnaround time is promised here.

### Your feedback

Found a bug, hit an empty section, or missing a field you need? We want to know — concrete reports are what keep this Actor working. Open an issue on the **Issues tab** of the Actor in the Apify Console, and include the run ID, the exact input JSON, and the field or section that behaved unexpectedly. Feature requests for additional output keys or `resultsType` modes are welcome on the same channel.

# Actor input Schema

## `accountsToAnalyse` (type: `array`):

✨ Instagram profiles, posts or reels to measure. Bare usernames work.
• 👤 `humansofny` or `https://www.instagram.com/humansofny/`
• 📷 `https://www.instagram.com/p/<shortcode>/`
• 🎬 `https://www.instagram.com/reel/<shortcode>/`

Engagement rate is calculated per account, so profile URLs give the richest output.

## `computeEngagementRate` (type: `boolean`):

Adds `engagementRate` to every post using the industry-standard formula **(likes + comments) ÷ followers × 100**, expressed as a percentage.
Example: 4,200 likes + 130 comments on a 120,000-follower account → `engagementRate` = 3.6083 (%). Turn off to skip the maths and the follower lookup entirely. Default: on.

## `postsToAverage` (type: `integer`):

How many of the newest posts per account feed the `AccountSummary` rollup row (`avgEngagementRate`, `avgLikes`, `avgComments`).
The rollup always publishes `postsAnalysed` beside the averages so you can see the real sample size. Example: 12 → averages over the 12 newest scraped posts. Default: 12.

## `minEngagementRate` (type: `integer`):

Only keep posts whose `engagementRate` reaches this percentage. Example: 3 keeps posts at 3% or better and drops the rest.
⚠️ Posts whose follower count could not be recovered have a null rate and are also dropped while this is above 0. Leave at 0 (default) to keep everything.

## `fetchFollowerCounts` (type: `boolean`):

Looks each account up once per run to recover **exact** `followersCount`, `followsCount`, `postsCount`, `biography`, `externalUrl` (unwrapped from Instagram's `l.instagram.com` redirect), `isVerifiedAccount` and `isPrivateAccount` — fields the original actor left permanently null.
One request per account, not per post. Default: on.

## `profileFetchTimeout` (type: `integer`):

How long to wait for a single profile lookup before giving up on it. On timeout the posts are still returned, with `engagementRate` null and an uncharged accounting row explaining why. Default: 25 seconds.

## `directUrls` (type: `array`):

Accepted for backwards compatibility with the original Instagram API Scraper input. Anything here is **merged with** 📊 Accounts to analyse above (de-duplicated) — neither field overrides the other, so existing saved inputs keep working unchanged.

## `resultsType` (type: `string`):

Choose the output type for each URL above.
• 📷 **posts** — recent posts from a profile / hashtag / single post
• 💬 **comments** — public comments under a post or reel
• 🪪 **details** — profile or post metadata only
• 🔖 **mentions** — extract `@mentions` from posts, or list hashtag posts
• 🎬 **reels** — reels feed from a profile
• 📖 **stories** — story-side fetch (best-effort, public only)

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

Maximum items to return for each URL. Note: Instagram's own pagination stops somewhere near 2,400 items per profile, so values far above that will not return more.

## `onlyPostsNewerThan` (type: `string`):

📆 Limit how far back to scrape. Pick an absolute date (`YYYY-MM-DD`) or a relative window (`7 days`, `2 months`, `3 years`). Only items posted on or after this cutoff are returned. Leave empty for no filter. All times are UTC.

## `addParentData` (type: `boolean`):

If enabled, each result item also includes a `parentData` field with the source profile/hashtag info.

## `search` (type: `string`):

Run an Instagram search instead of (or in addition to) URLs. Leave empty to disable search.

## `searchType` (type: `string`):

What the search query targets.

## `searchLimit` (type: `integer`):

Number of search results to return (1–250).

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

🌐 Default: **no proxy** — the actor talks to Instagram directly.
If Instagram rejects or blocks requests, the actor automatically escalates: 🔁 datacenter proxy → 🏠 residential proxy (3 retries). Once it falls back to residential, it sticks with residential for the rest of the run.
You can override the default by enabling Apify Proxy below.

## Actor input object example

```json
{
  "accountsToAnalyse": [
    "https://www.instagram.com/humansofny/"
  ],
  "computeEngagementRate": true,
  "postsToAverage": 12,
  "minEngagementRate": 0,
  "fetchFollowerCounts": true,
  "profileFetchTimeout": 25,
  "resultsType": "posts",
  "resultsLimit": 10,
  "addParentData": false,
  "search": "",
  "searchType": "hashtag",
  "searchLimit": 1,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# 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 = {
    "accountsToAnalyse": [
        "https://www.instagram.com/humansofny/"
    ],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("api-empire/instagram-api-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 = {
    "accountsToAnalyse": ["https://www.instagram.com/humansofny/"],
    "proxyConfiguration": { "useApifyProxy": False },
}

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

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

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

```

## CLI example

```bash
echo '{
  "accountsToAnalyse": [
    "https://www.instagram.com/humansofny/"
  ],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call api-empire/instagram-api-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=api-empire/instagram-api-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/6HlZOJ9YHV4aOIoun/builds/sraJ3eNuhOR6eDSHn/openapi.json
