# Instagram Growth Tracker (`coregent/instagram-growth-tracker`) Actor

Track public Instagram accounts over time. Monitor follower gains, following changes, posting activity, engagement trends, growth velocity, 7/30/90-day comparisons, milestones and alerts from stored historical snapshots. Built for scheduled runs.

- **URL**: https://apify.com/coregent/instagram-growth-tracker.md
- **Developed by:** [Delowar Munna](https://apify.com/coregent) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.50 / 1,000 profile snapshots

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

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

## What's an Apify Actor?

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

## Instagram Growth Tracker

![Instagram Growth Tracker](https://raw.githubusercontent.com/coregentdevspace/instagram-growth-tracker-assets/main/thumbnail-instagram-growth-tracker.png)

Track public Instagram accounts over time. Monitor follower gains, following changes, posting activity, engagement trends, growth velocity, milestones and historical snapshots. Ideal for creator monitoring, competitor research, agencies and influencer analytics.

### What this actor does

**Instagram Growth Tracker** observes a list of public Instagram profiles on every run, stores each observation, and compares it with the account's own history. One row = **one profile × the current observation**, with:

- 📈 **Deltas**: followers, following and posts gained or lost since the previous observation, plus growth percentage
- ⏱️ **Velocity**: followers per day / week / 30 days, normalized for uneven run intervals
- 🗓️ **Period comparisons**: 24 hours, 7, 30 and 90 days, each against a real stored baseline
- 🧭 **Trend**: `SURGING`, `GROWING`, `STABLE`, `DECLINING`, `SHARP_DECLINE` and **acceleration** (`ACCELERATING` / `STEADY` / `DECELERATING`)
- 🏁 **Milestones**: `CROSSED_1K` … `CROSSED_1M`
- 🔔 **Signals and alerts**: follower surges and drops, posting and engagement changes, profile changes, plus `alertTriggered` / `alertReasons` ready for webhooks, Zapier, Make, n8n, Slack or email
- 💬 **Engagement** (optional): average and median likes, comments and views over recent posts, engagement rate, and its change over time
- 📝 **Posting activity**: latest post, posts in the last 7 / 30 days, posts per week and month
- 🪪 **Profile changes**: bio, name, category, link, verification and account type, with old and new values
- 🔁 **Username changes**: tracked through the stable Instagram user ID, so a rename doesn't break history

#### How is this different from a profile scraper?

A profile scraper answers *"how many followers does this account have now?"*. This actor answers *"how is this account changing over time?"*. It keeps **persistent history** in your Apify account and turns repeated observations into growth analytics. For one-off profile data, use [Instagram Profile Scraper](https://apify.com/coregent/instagram-profile-scraper) instead.

### How it works

1. **Normalize.** Usernames, `@handles` and profile URLs are reduced to one username. Duplicates are removed. Post or Reel URLs get a free `INVALID_INPUT` row, because this actor tracks accounts.
2. **Observe.** The current public profile is fetched: followers, following, posts, user ID and metadata. With engagement on, recent posts are analyzed too.
3. **Look up history.** The stored state for that profile is loaded under your `trackerKey`, keyed on the Instagram user ID.
4. **Compare.** Deltas, growth %, velocity, period baselines, trend, acceleration, milestones, profile changes, signals and alerts are calculated.
5. **Persist.** The observation is saved as an immutable snapshot, after validation, so a failed fetch can never overwrite good history.
6. **Output.** One row per profile in the run's dataset, plus a `RUN_SUMMARY` record.

### Scheduling (recommended)

Growth only exists between two observations, so run the same input on a schedule: create a **Task** with your profile list and attach an Apify **Schedule**.

| Cadence | Good for |
|---|---|
| **Daily** (recommended) | Most monitoring, agency reporting, competitor watch |
| Every 12 hours | Fast-moving creators, campaign weeks |
| Weekly | Large watchlists, long-term benchmarking |

Hourly runs are rarely useful: follower counts move slowly, and Instagram rounds or caches them. **Uneven intervals are fine.** Velocity is always divided by the real elapsed time, so a missed day doesn't distort `followersPerDay`.

### First run: the baseline

The first observation of a profile creates its **baseline**. Growth can't exist yet, so the row says so honestly:

```json
{ "status": "BASELINE_CREATED", "isBaseline": true, "growth": { "followersDelta": null, "followersGrowthPct": null }, "trend": { "growthTrend": "INSUFFICIENT_HISTORY" } }
```

The actor never reports zero growth when there is nothing to compare with. **History starts at the first observation.** No history from before your first run is available or estimated.

### Formulas

| Field | Formula |
|---|---|
| `followersDelta` | current followers − previous followers (same for following and posts) |
| `followersGrowthPct` | (current − previous) ÷ previous × 100; `null` when previous is 0 or unknown |
| `followersPerDay` | `followersDelta` ÷ elapsed days; `null` under one hour of elapsed time |
| `followersPerWeek` / `followersPer30Days` | `followersPerDay` × 7 / × 30 |
| `engagementRate` | (avg likes + avg comments) ÷ followers, over the analyzed posts |
| `engagementGrowthPct` | change in engagement rate vs the previous observation, in % |

### 7 / 30 / 90-day comparisons

Each period in `growthPeriods` (`previous`, `day1`, `day7`, `day30`, `day90`) is compared with the stored snapshot **closest to that age**, and only if that snapshot falls inside a tolerance window:

| Period | Baseline must be within |
|---|---|
| 24 hours | ±6 hours |
| 7 days | ±24 hours |
| 30 days | ±3 days |
| 90 days | ±7 days |

No suitable snapshot means `null`, not a guess. A 30-day comparison appears about 30 days after you start tracking. Each period reports `baselineAt`, `elapsedHours`, `followersDelta`, `followersGrowthPct`, `followersPerDay`, `followingDelta`, `postsDelta` and `engagementRateDelta`.

### Growth classifications

**Trend** uses follower growth normalized to one week, measured against a ~7-day baseline (±2 days), or else the previous observation if it is at least 12 hours old:

| Trend | Weekly follower growth |
|---|---|
| `SURGING` | ≥ 3% (and at least 20 followers/week) |
| `GROWING` | ≥ 0.1% |
| `STABLE` | between −0.1% and 0.1% |
| `DECLINING` | ≤ −0.1% |
| `SHARP_DECLINE` | ≤ −2% (and at least 20 followers/week) |
| `INSUFFICIENT_HISTORY` | no usable baseline yet |

**Acceleration** compares follower velocity over the last 7 days with the 7 days before (or the last 24h with the 24h before, or the last two intervals between runs). `ACCELERATING` and `DECELERATING` need a change of at least 20% (minimum 1 follower/day).

**Signals** (`trend.signals`): `FOLLOWER_SURGE`, `FOLLOWER_DROP`, `ACCELERATING_GROWTH`, `DECELERATING_GROWTH`, `POSTING_INCREASE`, `POSTING_DECREASE`, `ENGAGEMENT_INCREASE`, `ENGAGEMENT_DECREASE`, `FOLLOWERS_GROWING_ENGAGEMENT_FALLING`, `NEW_MILESTONE`, `PROFILE_CHANGED`, `USERNAME_CHANGED`, `POSTS_REMOVED_OR_ARCHIVED`.

A negative `postsDelta` means posts were removed **or archived**. The actor can't tell which, so it doesn't claim deletions.

### Alerts and webhooks

Every row has `alertTriggered` (boolean) and `alertReasons` (list). Set any of:

- `growthAlertThresholdPct`, e.g. `5` → `FOLLOWERS_GROWTH_PCT>=5`
- `growthAlertThresholdAbsolute`, e.g. `10000` → `FOLLOWERS_DELTA>=10000`
- `dropAlertThresholdPct`, e.g. `3` or `-3` → `FOLLOWERS_GROWTH_PCT<=-3`

A `SHARP_DECLINE` trend and every new milestone (`MILESTONE_CROSSED_100K`) always trigger an alert.

To act on alerts, add an Apify **webhook** on *Run succeeded* that calls Zapier / Make / n8n / Slack. In the receiving step, read the run's dataset and keep rows with `alertTriggered: true`:

```
GET https://api.apify.com/v2/datasets/{defaultDatasetId}/items?clean=true&fields=username,alertReasons,growth,trend
```

### Historical storage

History lives in **your own Apify account**, in two named storages that persist between runs:

| Storage | Holds |
|---|---|
| Key-value store `instagram-growth-state` | Latest state per profile: identity, profile metadata, and the snapshots used as baselines, thinned to one per day after 3 days and kept for `maxHistoryDays` |
| Dataset `instagram-growth-history` | Every saved observation, one immutable row each, append-only. Export it for charts or BI tools |

Each run's own dataset holds that run's growth rows. Deleting the named storages resets tracking.

#### trackerKey isolation

`trackerKey` namespaces the history. Track the same account for two clients (`agency-client-a`, `agency-client-b`) and the two histories never mix. Changing a task's `trackerKey` starts a fresh history for it. Default: `default`.

#### Repeated and overlapping runs

A run within `minSnapshotIntervalMinutes` (default 30) of the last saved observation still returns a row, compared with the snapshot before it (or with that recent observation itself, when there is nothing older), but adds no history entry (`historySkipReason: "MIN_SNAPSHOT_INTERVAL"`). If two scheduled runs overlap, every save re-checks the stored version first. A run that lost the race recomputes against the fresh state, so the delta chain stays correct.

### Private profiles, unavailable profiles and failures

- **Private profiles**: public counters are tracked as normal (`status: PROFILE_PRIVATE`). Recent-post engagement and posting metrics are not available. The actor never bypasses privacy.
- **Not found**: `PROFILE_NOT_FOUND`. If the username was tracked before, the status is `USERNAME_CHANGED_UNKNOWN` instead: the account was renamed, deleted or deactivated.
- **Temporary failures** (`RATE_LIMITED`, `BLOCKED`, `TEMPORARY_FETCH_ERROR`, `PROVIDER_ERROR`) are retried on a fresh session with backoff. If they persist, the profile gets a free error row and its stored history is **left untouched**. The row shows the last known observation in `previous`, never zeros. Retryable failures are saved to the `RETRY_INPUT` record for an easy re-run.
- **Missing counters**: a profile returned without its follower or following count gets `PARTIAL_PROFILE`. It isn't saved and isn't charged.
- **Posts count**: some data sources don't report an account's total post count. That observation then has `postsCount: null` and `postsDelta: null`, never zero, while followers, following and posting cadence from recent posts are still tracked.
- One failing profile never fails the run.

### Username changes

Identity is the Instagram **user ID**, not the username. If an account renames itself and you track it under its new username, history continues and the row reports `usernameChanged: true` and `previousUsername`. If you keep tracking the old username, it stops resolving and gets `USERNAME_CHANGED_UNKNOWN`.

### Engagement availability

Engagement is calculated from up to `postsForEngagement` recent public posts (default 12). Pinned posts are excluded because they are often months old, so an account with 3 pinned posts typically has 9 of its latest 12 analyzed. `postsAnalyzed` always shows the real number. Hidden like counts are skipped, **never counted as zero**, and views count only for videos and Reels. If recent posts can't be fetched, the counters are still tracked and the row says `POSTS_UNAVAILABLE`. Posting counts for a 7- or 30-day window are `null` when the analyzed posts don't reach back that far, rather than an undercount.

### Login and cookies

None. You don't provide a login, password, cookies, session ID or Meta token. The actor reads only public information.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `profiles` | string\[] | — | Usernames, `@handles` or profile URLs (max 1,000 per run) |
| `mode` | enum | `TRACK` | `TRACK`: compare with previous + selected periods. `COMPARE`: all periods. `SNAPSHOT`: record only |
| `trackerKey` | string | `default` | History namespace |
| `comparisonPeriods` | enum\[] | `PREVIOUS`, `7_DAYS`, `30_DAYS` | Also `24_HOURS`, `90_DAYS` |
| `includeEngagementMetrics` | bool | `true` | Recent-post engagement (recent-posts add-on) |
| `postsForEngagement` | int | `12` | Recent posts analyzed (1–48); billed per block of 12 |
| `includePostingMetrics` | bool | `true` | Posting cadence (recent-posts add-on) |
| `includeProfileMetadata` | bool | `true` | Name, bio, category, link, picture |
| `saveHistory` | bool | `true` | Off = read-only comparison, nothing saved |
| `maxHistoryDays` | int | `365` | Baseline retention in the state store |
| `minSnapshotIntervalMinutes` | int | `30` | Repeat-run protection |
| `growthAlertThresholdPct` | number | — | Alert on growth ≥ this % |
| `growthAlertThresholdAbsolute` | int | — | Alert on gain ≥ this many followers |
| `dropAlertThresholdPct` | number | — | Alert on a drop ≥ this % |
| `proxyConfiguration` | object | Apify Proxy on | See [Proxy policy](#-proxy-policy) |
| `maxConcurrency` | int | `10` | Profiles fetched in parallel |
| `maxRetries` | int | `3` | Retries per profile on temporary errors |
| `debugMode` | bool | `false` | Verbose logging |

An empty `profiles` list runs the example profile `natgeo`.

#### Example 1: Minimal

```json
{
  "profiles": ["nasa", "natgeo"]
}
```

#### Example 2: Daily competitor tracker (schedule it)

```json
{
  "profiles": ["competitor1", "competitor2", "competitor3"],
  "trackerKey": "competitor-watch",
  "mode": "TRACK",
  "comparisonPeriods": ["PREVIOUS", "7_DAYS", "30_DAYS"],
  "includeEngagementMetrics": true,
  "postsForEngagement": 12,
  "growthAlertThresholdPct": 5,
  "dropAlertThresholdPct": 3
}
```

#### Example 3: Lightweight profile-only tracking

```json
{
  "profiles": ["brand1", "brand2"],
  "trackerKey": "brand-growth",
  "includeEngagementMetrics": false,
  "includePostingMetrics": false,
  "saveHistory": true
}
```

### Output

Tabs: **Growth**, **Period comparisons**, **Trends, signals & alerts**, **Engagement & posting**, **Profile**, **Status & errors**. Every row, snapshot or error, has the same keys. Unavailable values are `null`, never zero.

![Instagram Growth Tracker: Growth table view](https://raw.githubusercontent.com/coregentdevspace/instagram-growth-tracker-assets/main/instagram-growth-tracker-output-growth-table-view.png)

The records below come from a real run with this input: 20 brand and media accounts, the second observation of each, 35 minutes after the first. Over that short gap the deltas are real, but velocity (`followersPerDay`, under one hour), the trend (needs a previous observation at least 12 hours old) and the 24h/7d/30d periods are still `null`. They fill in as the schedule builds history. Profile picture URLs are shortened with `…`.

```json
{
  "profiles": [
    "nasa", "natgeo", "nike", "apple", "spotify",
    "netflix", "starbucks", "redbull", "lego", "tesla",
    "google", "adidas", "cocacola", "gopro", "airbnb",
    "duolingo", "nba", "bbcnews", "louvremuseum", "nasahubble"
  ],
  "mode": "TRACK",
  "trackerKey": "readme-demo",
  "comparisonPeriods": ["PREVIOUS", "24_HOURS", "7_DAYS", "30_DAYS"],
  "includeEngagementMetrics": true,
  "postsForEngagement": 12,
  "includePostingMetrics": true,
  "growthAlertThresholdPct": 0.05,
  "dropAlertThresholdPct": 0.05
}
```

#### Full record: a compared profile

```json
{
  "rowType": "instagram_growth_snapshot",
  "status": "SUCCESS",
  "errorMessage": null,
  "trackerKey": "readme-demo",
  "mode": "TRACK",
  "input": "redbull",
  "username": "redbull",
  "userId": "476322",
  "observedAt": "2026-10-23T12:48:59.931Z",
  "isBaseline": false,
  "historySaved": true,
  "historySkipReason": null,
  "profile": {
    "username": "redbull",
    "userId": "476322",
    "fullName": "Red Bull",
    "profileUrl": "https://www.instagram.com/redbull/",
    "profileImageUrl": "https://scontent-den2-1.cdninstagram.com/v/t51.2885-19/33020…",
    "bio": "watch the World Of Red Bull👇\n#givesyouwiiings",
    "category": "Personal Goods & General Merchandise Stores",
    "isVerified": true,
    "isPrivate": false,
    "isBusinessAccount": true,
    "isProfessionalAccount": true,
    "accountType": "business",
    "followersCount": 34222405,
    "followingCount": 2148,
    "postsCount": 11667,
    "externalUrl": "https://onein.bio/redbull"
  },
  "previous": { "observedAt": "2026-10-23T12:14:21.733Z", "followersCount": 34221836, "followingCount": 2148, "postsCount": 11667, "engagementRate": 0.005391 },
  "growth": {
    "followersDelta": 569,
    "followersGrowthPct": 0.002,
    "followingDelta": 0,
    "followingGrowthPct": 0,
    "postsDelta": 0,
    "elapsedHours": 0.58,
    "followersPerDay": null,
    "followersPerWeek": null,
    "followersPer30Days": null,
    "engagementRateDelta": 0.000045,
    "engagementGrowthPct": 0.835,
    "avgLikesDelta": 1559,
    "avgCommentsDelta": 6.5,
    "avgViewsDelta": 24982.4
  },
  "growthPeriods": {
    "previous": { "baselineAt": "2026-10-23T12:14:21.733Z", "elapsedHours": 0.58, "followersDelta": 569, "followersGrowthPct": 0.002, "followersPerDay": null, "followingDelta": 0, "postsDelta": 0, "engagementRateDelta": 0.000045 },
    "day1": null,
    "day7": null,
    "day30": null,
    "day90": null
  },
  "trend": { "growthTrend": "INSUFFICIENT_HISTORY", "weeklyGrowthPct": null, "trendBaselineAt": null, "growthAcceleration": "INSUFFICIENT_HISTORY", "recentFollowersPerDay": null, "priorFollowersPerDay": null, "signals": [], "milestones": [] },
  "activity": { "postsAnalyzed": 9, "latestPostAt": "2026-10-23T08:02:08.000Z", "postsInLast7Days": null, "postsInLast30Days": null, "postsPerWeek": 15, "postsPerMonth": 64.29 },
  "engagement": {
    "postsAnalyzed": 9,
    "postsWithHiddenLikes": 0,
    "avgLikes": 185374.2,
    "medianLikes": 35195,
    "avgComments": 664.2,
    "medianComments": 155,
    "avgViews": 3331798.2,
    "medianViews": 753043,
    "avgEngagement": 186038.4,
    "medianEngagement": 35350,
    "engagementRate": 0.005436
  },
  "changedFields": [],
  "profileChanges": {},
  "usernameChanged": false,
  "previousUsername": null,
  "alertTriggered": false,
  "alertReasons": [],
  "history": { "firstTrackedAt": "2026-10-23T12:14:21.733Z", "lastObservedAt": "2026-10-23T12:48:59.931Z", "snapshotCount": 2 },
  "source": "direct"
}
```

`postsInLast7Days` is `null` here because Red Bull posts about 15 times a week, so its 9 analyzed posts cover less than 7 days.

#### Growth tab

```json
{
  "username": "lego",
  "observedAt": "2026-10-23T12:49:23.887Z",
  "profile": { "profileImageUrl": "https://scontent-lga3-2.cdninstagram.com/v/t51.2885-19/45642…", "followersCount": 14087576, "followingCount": 918, "postsCount": 6379 },
  "growth": { "followersDelta": 219, "followersGrowthPct": 0.002, "followersPerDay": null, "followingDelta": 0, "postsDelta": 0 },
  "trend": { "growthTrend": "INSUFFICIENT_HISTORY" },
  "alertTriggered": false,
  "isBaseline": false,
  "status": "SUCCESS"
}
```

#### Period comparisons tab

On the second observation only `previous` has a baseline. `day1`, `day7`, `day30` and `day90` stay `null` until a stored snapshot of roughly that age exists.

```json
{
  "username": "apple",
  "profile": { "followersCount": 38111738 },
  "growthPeriods": {
    "previous": { "followersDelta": 396, "elapsedHours": 0.58 },
    "day1": null,
    "day7": null,
    "day30": null,
    "day90": null
  },
  "history": { "firstTrackedAt": "2026-10-23T12:14:12.901Z", "snapshotCount": 2 }
}
```

#### Trends, signals & alerts tab

The trend needs a previous observation at least 12 hours old, so this short-gap run has none yet. The long-history example further down shows the filled version.

```json
{
  "username": "nike",
  "trend": { "growthTrend": "INSUFFICIENT_HISTORY", "weeklyGrowthPct": null, "growthAcceleration": "INSUFFICIENT_HISTORY", "recentFollowersPerDay": null, "priorFollowersPerDay": null, "signals": [], "milestones": [] },
  "alertTriggered": false,
  "alertReasons": [],
  "changedFields": [],
  "usernameChanged": false,
  "previousUsername": null
}
```

#### Engagement & posting tab

Coca-Cola posts about 1.4 times a week, so its 12 analyzed posts reach back past 30 days and both window counts are filled.

```json
{
  "username": "cocacola",
  "engagement": { "postsAnalyzed": 12, "avgLikes": 12064.8, "medianLikes": 3823.5, "avgComments": 313.3, "medianComments": 313, "avgViews": 31202, "engagementRate": 0.003658 },
  "growth": { "engagementRateDelta": 0.000006, "engagementGrowthPct": 0.164 },
  "activity": { "latestPostAt": "2026-10-22T18:33:27.000Z", "postsInLast7Days": 2, "postsInLast30Days": 5, "postsPerWeek": 1.43 }
}
```

#### Profile tab

```json
{
  "username": "nasa",
  "userId": "528817151",
  "profile": {
    "profileImageUrl": "https://scontent-lga3-2.cdninstagram.com/v/t51.2885-19/29090…",
    "fullName": "NASA",
    "profileUrl": "https://www.instagram.com/nasa/",
    "category": "Government Agencies",
    "accountType": "business",
    "isVerified": true,
    "isPrivate": false,
    "isBusinessAccount": true,
    "externalUrl": "https://www.nasa.gov/",
    "bio": "Making the seemingly impossible, possible. ✨"
  }
}
```

#### Status & errors tab: a private profile

The `tesla` handle is a small private account (not the company's), so it shows the private-profile row: counters are tracked and charged as a snapshot, with no engagement.

```json
{
  "input": "tesla",
  "username": "tesla",
  "rowType": "instagram_growth_snapshot",
  "status": "PROFILE_PRIVATE",
  "errorMessage": "Private profile: public counters are tracked; recent-post engagement and posting metrics are not available.",
  "trackerKey": "readme-demo",
  "mode": "TRACK",
  "historySaved": true,
  "historySkipReason": null,
  "source": "direct",
  "previous": { "observedAt": "2026-10-23T12:14:27.137Z", "followersCount": 178 },
  "observedAt": "2026-10-23T12:49:21.255Z"
}
```

#### After a few months of daily runs

A compared row then looks like this (illustrative values, trimmed):

```json
{
  "rowType": "instagram_growth_snapshot",
  "status": "SUCCESS",
  "trackerKey": "competitor-watch",
  "username": "examplecreator",
  "userId": "123456789",
  "observedAt": "2026-10-17T08:00:00.000Z",
  "isBaseline": false,
  "historySaved": true,
  "profile": { "followersCount": 148500, "followingCount": 612, "postsCount": 1284, "isVerified": true, "isPrivate": false, "accountType": "creator" },
  "previous": { "observedAt": "2026-10-16T08:00:00.000Z", "followersCount": 146900, "followingCount": 610, "postsCount": 1283 },
  "growth": { "followersDelta": 1600, "followersGrowthPct": 1.089, "followingDelta": 2, "postsDelta": 1, "elapsedHours": 24, "followersPerDay": 1600 },
  "growthPeriods": {
    "day7": { "baselineAt": "2026-10-10T08:00:00.000Z", "followersDelta": 7200, "followersGrowthPct": 5.095, "followersPerDay": 1028.57 },
    "day30": { "baselineAt": "2026-09-17T08:00:00.000Z", "followersDelta": 18400, "followersGrowthPct": 14.143, "followersPerDay": 613.33 }
  },
  "trend": { "growthTrend": "SURGING", "weeklyGrowthPct": 5.095, "growthAcceleration": "ACCELERATING", "signals": ["FOLLOWER_SURGE", "ACCELERATING_GROWTH"], "milestones": [] },
  "activity": { "postsAnalyzed": 12, "latestPostAt": "2026-10-16T19:12:00.000Z", "postsInLast7Days": 4, "postsPerWeek": 4.2 },
  "engagement": { "postsAnalyzed": 12, "avgLikes": 9200, "medianLikes": 7100, "avgComments": 318, "medianComments": 244, "engagementRate": 0.0641 },
  "changedFields": [],
  "alertTriggered": true,
  "alertReasons": ["FOLLOWERS_GROWTH_PCT>=1"],
  "history": { "firstTrackedAt": "2026-07-17T08:00:00.000Z", "lastObservedAt": "2026-10-17T08:00:00.000Z", "snapshotCount": 93 },
  "source": "direct"
}
```

#### Status values

| Status | Meaning | Charged |
|---|---|---|
| `SUCCESS` | Observation compared with history | ✅ |
| `BASELINE_CREATED` | First observation saved | ✅ |
| `PROFILE_PRIVATE` | Private account: counters tracked, no engagement | ✅ |
| `POSTS_UNAVAILABLE` | Counters tracked; recent posts couldn't be fetched | ✅ |
| `STATE_WRITE_ERROR` | Observation returned but couldn't be saved | free |
| `PROFILE_NOT_FOUND` / `USERNAME_CHANGED_UNKNOWN` / `PROFILE_UNAVAILABLE` | Account missing, renamed or unavailable | free |
| `PARTIAL_PROFILE` | Counters missing; nothing saved | free |
| `RATE_LIMITED` / `BLOCKED` / `TEMPORARY_FETCH_ERROR` / `PROVIDER_ERROR` | Fetch failed after retries; history untouched | free |
| `STATE_READ_ERROR` | Stored history couldn't be read | free |
| `INVALID_INPUT` | Not a profile username/URL | free |

#### Run summary

The `RUN_SUMMARY` record has: profiles requested and succeeded, baselines created, profiles compared, growing, stable and declining counts, follower surges, milestones, alerts, profile and username changes, failed and private profiles, history rows written, state conflicts, duration and fetch latency.

### Scale and performance

Up to **1,000 profiles per run**. Profile-only tracking (engagement and posting metrics off) is the fastest: one profile lookup each. Engagement adds up to one feed page per 12 posts analyzed (often none for 12 or fewer, when Instagram includes the latest posts with the profile). For very large watchlists, split them into several tasks with their own `trackerKey`, or schedule them at different times.

### Pricing

Pay per event:

- **Profile snapshot**: per profile successfully observed (see the ✅ statuses above).
- **Recent-posts add-on**: per **block of up to 12 recent posts analyzed** for engagement and posting metrics. 12 posts is one block and 24 is two. It's charged only when `includeEngagementMetrics` or `includePostingMetrics` is on and posts were actually analyzed. It's never charged for private profiles or when posts couldn't be fetched. Turn both off for cheaper, counters-only tracking.

Not charged: invalid inputs, duplicates, missing, unavailable or failed profiles, and rows whose history couldn't be saved. There is no start fee. Rates can differ by Apify plan, so see the **Pricing** tab for the current rates on yours. This README deliberately doesn't quote figures.

The actor respects the spending limit you set for a run. Before each profile it checks that the limit still covers a snapshot plus the planned add-on blocks. When the limit covers only the snapshot, recent posts are skipped, with a note on the row. When it covers nothing more, the run stops cleanly.

#### 🚦 Proxy policy

Use **Apify Datacenter** proxy (the default) or **no proxy** for normal runs. Both work for Instagram profile lookups at this actor's conservative concurrency.

**Apify Residential proxy is not supported.** The actor fails at startup if `apifyProxyGroups` includes `RESIDENTIAL`. In pay-per-event actors, residential bandwidth is billed per GB to the developer, not the run user, so a single bandwidth-heavy run could cost more than the per-result revenue.

If you need residential routing, supply your own residential provider in the proxy editor's **Custom proxy URLs** field. That traffic goes through your provider, not Apify, and is unaffected:

```
http://user:pass@proxy.iproyal.com:12321
http://user:pass@proxy.brightdata.com:22225
http://user:pass@proxy.oxylabs.io:7777
```

### API and MCP

Call it like any Apify actor: through the Apify API, an Apify client library (JavaScript, Python), or Apify's MCP server so AI agents can use it as a tool. See the **API** tab for ready-made snippets. A typical integration runs a saved Task on a schedule, then reads the run's dataset (or the `instagram-growth-history` dataset for full time series) from a webhook.

### Interpretation and limitations

- **Unofficial.** This is not Instagram or Meta analytics.
- **Discrete observations.** Metrics are what Instagram showed publicly at each run. Growth between runs is a difference, not an exact time of acquisition.
- **History begins at your first observation.**
- **Instagram rounds and caches** some counters, so very small changes on very large accounts can lag.
- **Deleted or archived posts** change the total post count.
- **Engagement depends on public post metrics**, which Instagram may hide.
- **Trends, signals and milestones are Coregent-derived analytics.**
- **Not provided:** follower demographics, organic vs paid follower growth, reach, impressions, story metrics, or any history before tracking started.

### Related actors

- [Instagram Profile Scraper](https://apify.com/coregent/instagram-profile-scraper): one-off profile metadata and public business contact
- [Instagram Posts Scraper](https://apify.com/coregent/instagram-posts-scraper): posts, carousels and Reels with full engagement
- [Instagram Creator Leads Scraper](https://apify.com/coregent/instagram-creator-leads-scraper): find creators by niche

### Legal and privacy

This is an unofficial tool. It is not affiliated with, endorsed by or sponsored by Instagram or Meta. It processes only publicly accessible information and never accesses private content. Profile data can be personal data. You are responsible for having a lawful basis for collecting, storing and processing it (for example under GDPR or CCPA), including the history this actor keeps in your account, and for complying with Instagram's terms in your use of the data.

### Local development

```bash
cd actor
npm install
npm test       # unit tests
npm start      # runs with INPUT.json
```

History is stored locally under `actor/storage/` in the same named stores, so repeated local runs produce real deltas.

# Actor input Schema

## `profiles` (type: `array`):

Usernames, @handles or profile URLs to track (natgeo, @nasa, https://www.instagram.com/nasa/). Duplicates are removed. Up to 1,000 profiles per run. Post/Reel URLs are not accepted — this Actor tracks accounts.

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

TRACK (default): compare with the previous observation and the comparison periods below. COMPARE: compare with every period (previous, 24h, 7d, 30d, 90d). SNAPSHOT: record current metrics only (creates the baseline, no growth calculations).

## `trackerKey` (type: `string`):

Project namespace for stored history, e.g. agency-client-a, competitor-monitor, creator-watchlist. The same profile tracked under two keys has two independent histories. Lowercase letters, digits, - and \_ (other characters are replaced).

## `comparisonPeriods` (type: `array`):

Historical baselines to compare with in TRACK mode. A period is null until a stored observation exists inside its tolerance window (24h ±6h, 7d ±24h, 30d ±3d, 90d ±7d).

## `includeEngagementMetrics` (type: `boolean`):

Analyze recent public posts for average/median likes, comments, views and engagement rate, and track engagement growth. Billed with posting metrics as the recent-posts add-on, per block of up to 12 posts analyzed. Hidden likes are never counted as zero.

## `postsForEngagement` (type: `integer`):

Up to how many of the most recent posts the engagement metrics use. Pinned posts are excluded, so slightly fewer may be analyzed. Billed per block of 12 (12 = one block, 24 = two). Common values: 12, 24.

## `includePostingMetrics` (type: `boolean`):

Latest post date, posts in the last 7 / 30 days, posts per week and per month. Uses recent posts, so it is billed as the recent-posts add-on (one block) when engagement metrics are off.

## `includeProfileMetadata` (type: `boolean`):

Return full name, bio, category, external URL and profile picture URL. Profile changes are detected either way.

## `saveHistory` (type: `boolean`):

Store this observation so future runs can compare with it. Turn off for a read-only check that compares with stored history without adding to it.

## `maxHistoryDays` (type: `integer`):

How far back snapshots are kept for baseline lookups in the latest-state store (older ones are thinned to one per day). The append-only history dataset keeps every observation regardless.

## `minSnapshotIntervalMinutes` (type: `integer`):

A run within this many minutes of the last stored observation still returns a row, but does not add a history entry (protects against repeated or overlapping runs).

## `growthAlertThresholdPct` (type: `number`):

Flag the row (alertTriggered) when follower growth since the previous observation is at least this many percent, e.g. 5. Leave empty to disable.

## `growthAlertThresholdAbsolute` (type: `integer`):

Flag the row when the follower gain since the previous observation is at least this many followers, e.g. 10000. Leave empty to disable.

## `dropAlertThresholdPct` (type: `number`):

Flag the row when followers fell by at least this many percent since the previous observation (enter -3 or 3 for a 3% drop). Leave empty to disable. A SHARP_DECLINE trend and a new follower milestone always flag the row.

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

Apify Proxy configuration. Defaults to Apify Proxy (datacenter) enabled, which rotates IPs between sessions to avoid Instagram's per-IP limits. Apify Residential is not supported — use Custom proxy URLs for your own residential provider.

## `maxConcurrency` (type: `integer`):

Maximum number of profiles fetched in parallel.

## `maxRetries` (type: `integer`):

How many times a profile is retried on a fresh session/IP after rate limits, login walls or temporary Instagram errors. Profiles Instagram still throttles after the first retry are fetched from an alternative source instead. Not-found profiles are never retried, and a failed fetch never changes stored history.

## `debugMode` (type: `boolean`):

Enable verbose logging.

## Actor input object example

```json
{
  "profiles": [
    "nasa",
    "natgeo"
  ],
  "mode": "TRACK",
  "trackerKey": "default",
  "comparisonPeriods": [
    "PREVIOUS",
    "7_DAYS",
    "30_DAYS"
  ],
  "includeEngagementMetrics": true,
  "postsForEngagement": 12,
  "includePostingMetrics": true,
  "includeProfileMetadata": true,
  "saveHistory": true,
  "maxHistoryDays": 365,
  "minSnapshotIntervalMinutes": 30,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "maxConcurrency": 10,
  "maxRetries": 3,
  "debugMode": false
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `periods` (type: `string`):

No description

## `trends` (type: `string`):

No description

## `engagement` (type: `string`):

No description

## `profile` (type: `string`):

No description

## `errors` (type: `string`):

No description

## `runSummary` (type: `string`):

No description

# 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 = {
    "profiles": [
        "nasa",
        "natgeo"
    ],
    "trackerKey": "default",
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("coregent/instagram-growth-tracker").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 = {
    "profiles": [
        "nasa",
        "natgeo",
    ],
    "trackerKey": "default",
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("coregent/instagram-growth-tracker").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 '{
  "profiles": [
    "nasa",
    "natgeo"
  ],
  "trackerKey": "default",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call coregent/instagram-growth-tracker --silent --output-dataset

```

## MCP server setup

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

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/E3WSHIDTNtQUgUOk5/builds/F8ovNwukaH4M3YPUf/openapi.json
