# Instagram Hashtag Analytics (`coregent/instagram-hashtag-analytics`) Actor

Instagram hashtag analytics with Reel and creator insights. One report per hashtag from up to 500 sampled posts and Reels: Reel share, plays, length and original audio; unique, verified and top creators and how concentrated they are; median engagement, media mix and co-occurring hashtags.

- **URL**: https://apify.com/coregent/instagram-hashtag-analytics.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 $30.00 / 1,000 hashtag reports

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 Hashtag Analytics

![Instagram Hashtag Analytics](https://raw.githubusercontent.com/coregentdevspace/instagram-hashtag-analytics-assets/main/thumbnail-instagram-hashtag-analytics.png)

See how Reel-heavy an Instagram hashtag is and **who actually posts in it**. Each hashtag gets Reel analytics, creator analytics and median engagement from a sample of up to 500 public posts and Reels, exported as **one clean analytics report per hashtag** in JSON, CSV or Excel.

### What this actor does

**Instagram Hashtag Analytics** takes one or more hashtags and returns **one report row per hashtag**, not thousands of raw posts:

- 🎬 **Reels**: Reel share of the sample, plus Reel likes, comments, views, plays, length and original-audio share, so you can tell a Reel-driven hashtag from a photo one
- 👥 **Creators**: unique creators, verified creators and their share, the share of the sample posted by the 10 busiest creators, and the top 10 creators with their post counts and average engagement
- 📊 **Engagement**: average **and median** likes, comments, video views and plays, totals, and an observed engagement rate for Reels/videos
- 🔬 **Deep samples**: 10 to 500 posts per hashtag, from the Top feed, the Recent feed or both
- 🧮 **Honest denominators**: every metric says how many sampled posts it was computed from (`likesAvailableFor`, `playsAvailableFor`, …)
- 🙈 **No fake zeros**: hidden likes and missing views are left out of the math, never counted as `0`
- 🖼️ **Content mix**: Reels, images, carousels and other videos with their shares
- 📈 **Volume**: the post count Instagram displays for the hashtag
- \#️⃣ **Co-occurring hashtags**: which other hashtags appear alongside it in the sampled captions, and how often
- 🏆 **Top posts**: compact examples of the best-performing sampled posts
- 🔎 **Coverage metadata**: sample mode, how many posts were requested and returned, Top/Recent overlap, oldest and newest sampled post, and a coverage status

Several hashtags in one run give you directly comparable rows, so you can sort them side by side in a spreadsheet. The run summary also names the leaders: largest hashtag, highest average likes, highest Reel share, most diverse creators.

### How it works

1. **Normalize hashtags.** `fitness`, `#Fitness` and `https://www.instagram.com/explore/tags/fitness/` all become `fitness`. Duplicates are removed. Invalid entries get a free `INVALID_HASHTAG` row.
2. **Sample.** For each hashtag the actor reads Instagram's **Top** feed, its **Recent** feed, or both (the default), until it has the sample size you asked for.
3. **Deduplicate.** A post that appears in both Top and Recent is counted once. Its `appearedInTop` / `appearedInRecent` flags are kept, and the Recent feed tops the sample back up so the overlap doesn't shrink it.
4. **Analyze.** Averages, medians, mixes, creator stats and co-occurring hashtags are computed from the sample, using only the values Instagram actually shows.
5. **Report.** One row per hashtag, plus free status rows for hashtags that couldn't be analyzed.

It runs without a browser, and needs no Instagram login and no cookies of yours.

### FAQ

**What does this actor analyze?**
Public Instagram hashtags. For each one you get volume, engagement, content mix, Reel, creator and co-occurring-hashtag analytics built from a sample of its public posts and Reels.

**How is it different from Instagram Hashtag Scraper?**
[Instagram Hashtag Scraper](https://apify.com/coregent/instagram-hashtag-scraper) returns the raw posts, one row per post. This actor uses the same extraction code but returns **one summary report per hashtag**. Use the scraper when you need every post, and this actor when you need the answer: which hashtag is bigger, more engaged, more Reel-heavy or dominated by a few creators.

**Is this complete historical hashtag data?**
No. Instagram does not expose a complete public history for a hashtag. Every metric is an **observed sample metric** built from the posts that were sampled, and every report says exactly what that sample was: mode, size, time range and coverage. The one exception is `availablePostsCount`, which is the count Instagram itself displays. It is never derived from the sample, and it is `null` when the feed doesn't return it.

**What does sample size mean?**
`sampleSizePerHashtag` is the total number of unique posts and Reels to analyze per hashtag (10–500, default 100). In `TOP_AND_RECENT` mode it is split about 50/50 between the two feeds. Small or niche hashtags may return fewer. The report shows `sampleRequested` and `sampleReturned`, and `coverageStatus` explains any gap.

**What is TOP vs RECENT?**
They are Instagram's two hashtag tabs. **TOP** is ranked by Instagram and gives a quality, high-engagement snapshot. **RECENT** is the newest posts and shows current activity. **TOP_AND_RECENT** (the default) combines them for a balanced research view.

**Are averages based on visible values only?**
Yes. Each average and median uses only the posts where Instagram shows that number, and the `…AvailableFor` field next to it gives the count. For example, `avgLikes: 1851` with `likesAvailableFor: 36` means 36 sampled posts had visible likes.

**Are hidden likes treated as zero?**
Never. A post with hidden likes is left out of the like metrics and counted in `likesHiddenCount`. Missing views and plays are handled the same way.

**Are Reels included?**
Yes. Reels are identified from Instagram's own labelling, and `reelShare` tells you how Reel-heavy a hashtag is. A video Instagram doesn't mark as a Reel is counted as `videosNonReel`, not guessed.

**Are views and plays included?**
Yes, separately, because Instagram reports them as different metrics: `avgPlays` / `medianPlays` and `avgViews` / `medianViews`, plus Reel-only versions. `engagementRateObserved` = (likes + comments) ÷ plays over Reels and videos where all three are visible. It is a Coregent-derived sample metric, not an Instagram-native rate, and it is `null` when fewer than 5 videos qualify.

**Are creator analytics included?**
Yes: `uniqueCreators`, `verifiedCreators`, `verifiedCreatorShare`, `top10CreatorShare` (the share of sampled posts made by the 10 most frequent creators) and up to 10 `topCreators` with their post counts and average engagement. They are computed from the sampled posts only. The actor does no extra profile lookups.

**What are co-occurring hashtags?**
The other hashtags used in the sampled captions, with how many sampled posts used each (`count`) and the share of the sample (`sampleShare`). They are case-normalized and exclude the hashtag you're analyzing.

**Are related hashtags platform-provided or sample-derived?**
Sample-derived. `cooccurringHashtags` comes from the captions of the posts in this sample. The actor does not mix in any Instagram-provided "related hashtags" list.

**How many hashtags can I analyze?**
Up to 100 per run, analyzed in parallel (`maxConcurrency`, default 5).

**How does pricing work?**
You pay **per completed hashtag report**, not per sampled post: one charge per started 100 sampled posts, so a standard 100-post report is one charge and a 500-post report is five. Invalid hashtags, hashtags with no public posts, partial samples cut short by an upstream error, and error rows are free. The rate can differ by Apify plan; see the **Pricing** tab for the current rate on yours. This README deliberately doesn't quote figures.

**Can I use the API or MCP?**
Yes. Like every Apify Actor it can be run through the [Apify API](https://docs.apify.com/api/v2), the JavaScript and Python clients, integrations (Make, Zapier, n8n…) and the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp), so AI agents can call it as a tool.

**Can I schedule recurring reports?**
Yes. Create an Apify **Schedule** (for example daily or weekly) with your hashtag list and compare the reports over time. Each run is an independent snapshot.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `hashtags` | string\[] | `["fitness"]` | Hashtags, with or without `#`, or `/explore/tags/<tag>/` URLs. Up to 100 |
| `sampleMode` | enum | `TOP_AND_RECENT` | `TOP`, `RECENT` or `TOP_AND_RECENT` (split ~50/50) |
| `sampleSizePerHashtag` | integer | `100` | Total unique posts to analyze per hashtag, 10–500 (common: 25, 50, 100, 250, 500) |
| `minPostDate` | string | empty | Only sample posts from this date on: ISO date (`2026-09-01`) or a period (`7 days`) |
| `includeMediaMix` | boolean | `true` | Reels / images / carousels / other videos and their shares |
| `includeReelAnalytics` | boolean | `true` | Reel likes, comments, plays, views, length, original-audio share |
| `includeCreatorAnalytics` | boolean | `true` | Unique / verified creators, top-10 share, top creators |
| `includeCooccurringHashtags` | boolean | `true` | Hashtags used alongside the analyzed one |
| `maxRelatedHashtags` | integer | `25` | Co-occurring hashtags listed per report (0–100) |
| `includeTopExamples` | boolean | `true` | Best-performing sampled posts |
| `maxTopExamples` | integer | `10` | Top posts per report (0–50) |
| `includeRawSample` | boolean | `false` | Attach every sampled post (compact form) to its report as `samplePosts` |
| `maxConcurrency` | integer | `5` | Hashtags analyzed in parallel (1–8) |
| `debugMode` | boolean | `false` | Verbose logging |

Engagement and coverage are always included. Turning a section off leaves its columns `null`, so every row keeps the same columns.

> **Tip:** if you paste JSON into the Console's JSON editor, include the five `include…` switches as in Example 2. Switches missing from pasted JSON can be saved as off, which leaves those report sections empty. Through the API, missing switches default to on.

#### Example 1: One hashtag

```json
{
  "hashtags": ["fitness"],
  "sampleMode": "TOP_AND_RECENT",
  "sampleSizePerHashtag": 100
}
```

#### Example 2: Compare 20 hashtags

The run behind the comparison table under [Output](#output): one report per hashtag.

```json
{
  "hashtags": [
    "fitness", "travel", "food", "photography", "fashion",
    "art", "nature", "music", "beauty", "gym",
    "yoga", "skincare", "coffee", "dogsofinstagram", "catsofinstagram",
    "makeup", "streetstyle", "foodie", "wanderlust", "interiordesign"
  ],
  "sampleMode": "TOP_AND_RECENT",
  "sampleSizePerHashtag": 50,
  "includeMediaMix": true,
  "includeReelAnalytics": true,
  "includeCreatorAnalytics": true,
  "includeCooccurringHashtags": true,
  "includeTopExamples": true,
  "maxConcurrency": 5
}
```

#### Example 3: Recent campaign analysis

```json
{
  "hashtags": ["brandcampaign2026"],
  "sampleMode": "RECENT",
  "sampleSizePerHashtag": 250,
  "minPostDate": "30 days",
  "includeTopExamples": true
}
```

#### Example 4: Deep research on one hashtag

```json
{
  "hashtags": ["skincare"],
  "sampleMode": "TOP_AND_RECENT",
  "sampleSizePerHashtag": 500,
  "maxRelatedHashtags": 50,
  "maxTopExamples": 20
}
```

### Output

One row per hashtag. Report rows have `rowType: "hashtag_analytics"`; hashtags that couldn't be analyzed get a free row with `rowType: "error"`. Every row has the same keys, and a section you turn off leaves its columns `null`, so CSV and Excel exports keep stable columns. Top-level metrics are flat for easy comparison; creators, co-occurring hashtags and examples are nested arrays.

The dataset has six tabs:

| Tab | What it shows |
|---|---|
| **Hashtag reports** | The headline comparison: volume, sample size, likes, comments, plays, Reel share, creators |
| **Engagement** | Averages, medians, totals and denominators for likes, comments, views and plays |
| **Content mix & Reels** | Reels / images / carousels / other videos, shares and Reel metrics |
| **Creators** | Unique and verified creators, top-10 share, top creators |
| **Co-occurring hashtags & top posts** | Hashtags used alongside and the best-performing sampled posts |
| **Sample coverage & status** | Mode, requested/returned, Top/Recent split, time range, coverage status, errors |

The samples below are real rows from runs with `sampleSizePerHashtag: 50` on October 16, 2026. Nested arrays are shortened to their first entries.

#### Engagement table view: 20 hashtags in one run

![Instagram Hashtag Analytics output: Engagement table view](https://raw.githubusercontent.com/coregentdevspace/instagram-hashtag-analytics-assets/main/instagram-hashtag-analytics-output-engagement-table-view.png)

#### Comparing hashtags

The same 20-hashtag run, sorted by observed engagement rate. Medians far below averages show where a few viral posts carry a hashtag: `#fitness` averages 338.2 likes but its median post has 3.

| Hashtag | Avg likes | Median likes | Hidden likes | Avg plays | Median plays | Engagement rate (videos) |
|---|---|---|---|---|---|---|
| #travel | 1,063.8 | 287 | 7 | 21,766.5 | 9,336 | 9.4% (14) |
| #catsofinstagram | 626.8 | 134 | 3 | 13,255 | 10,648.5 | 9.0% (10) |
| #makeup | 657.4 | 309 | 12 | 16,027.7 | 7,164.5 | 8.3% (14) |
| #nature | 363 | 5.5 | 4 | 11,293.7 | 10,293.5 | 7.5% (15) |
| #music | 1,430.3 | 8 | 6 | 57,197 | 6,338.5 | 5.8% (14) |
| #wanderlust | 859.8 | 2 | 10 | 57,926 | 14,314 | 5.8% (5) |
| #fitness | 338.2 | 3 | 4 | 10,899.7 | 7,189 | 5.8% (14) |
| #yoga | 526.8 | 3 | 9 | 29,888.4 | 14,974 | 5.5% (14) |
| #skincare | 559 | 1 | 1 | 24,691.1 | 11,807 | 5.5% (18) |
| #art | 241.3 | 1 | 8 | 9,753.2 | 3,626 | 5.2% (11) |
| #dogsofinstagram | 330.2 | 3 | 4 | 17,783 | 17,831 | 4.2% (18) |
| #coffee | 757 | 267.5 | 4 | 41,796.4 | 47,279.5 | 4.2% (14) |
| #gym | 1,318.8 | 142.5 | 6 | 96,886 | 8,153 | 3.8% (13) |
| #interiordesign | 671.4 | 4 | 1 | 53,549.8 | 18,568.5 | 3.5% (12) |
| #food | 350 | 3 | 3 | 24,936.3 | 16,745 | 3.3% (19) |
| #photography | 300.7 | 150 | 8 | 16,034.4 | 10,507 | 2.9% (8) |
| #foodie | 330.5 | 144 | 2 | 27,883.6 | 20,435 | 2.9% (18) |
| #streetstyle | 495.9 | 5 | 15 | 48,857.7 | 10,344 | 2.3% (5) |
| #beauty | 538.9 | 391.5 | 6 | 52,708.8 | 24,669.5 | 2.3% (15) |
| #fashion | 382.1 | 23 | 9 | 21,263.4 | 13,933 | 2.2% (9) |

Engagement rate = (likes + comments) ÷ plays, over the Reels and videos in the bracketed count.

#### Full record: #fitness (a separate one-hashtag run with every section on)

```json
{
  "rowType": "hashtag_analytics",
  "status": "SUCCESS",
  "errorMessage": null,
  "hashtag": "fitness",
  "url": "https://www.instagram.com/explore/tags/fitness/",
  "input": "fitness",
  "queryIndex": 0,
  "availablePostsCount": null,
  "sampleMode": "TOP_AND_RECENT",
  "sampleRequested": 50,
  "sampleReturned": 50,
  "topReturned": 25,
  "recentReturned": 25,
  "duplicatesRemoved": 0,
  "oldestObservedAt": "2026-10-15T16:34:28.000Z",
  "newestObservedAt": "2026-10-16T13:45:41.000Z",
  "coverageStatus": "FULL_REQUESTED_SAMPLE",
  "coverageNote": null,
  "avgLikes": 331.6,
  "medianLikes": 37,
  "likesAvailableFor": 45,
  "likesHiddenCount": 4,
  "totalVisibleLikes": 14924,
  "avgComments": 14.2,
  "medianComments": 1,
  "commentsAvailableFor": 49,
  "totalComments": 695,
  "avgViews": null,
  "medianViews": null,
  "viewsAvailableFor": 0,
  "avgPlays": 15408.4,
  "medianPlays": 13879,
  "playsAvailableFor": 17,
  "engagementRateObserved": 0.0485,
  "engagementRateAvailableFor": 17,
  "reels": 17,
  "images": 15,
  "carousels": 18,
  "videosNonReel": 0,
  "reelShare": 0.34,
  "imageShare": 0.3,
  "carouselShare": 0.36,
  "videoNonReelShare": 0,
  "reelCount": 17,
  "avgReelLikes": 720.7,
  "medianReelLikes": 495,
  "avgReelComments": 26.9,
  "avgReelViews": null,
  "medianReelViews": null,
  "avgReelPlays": 15408.4,
  "medianReelPlays": 13879,
  "avgReelDurationSeconds": 41.3,
  "reelDurationAvailableFor": 17,
  "originalAudioShare": 0.625,
  "reelAudioAvailableFor": 16,
  "uniqueCreators": 49,
  "creatorsAvailableFor": 50,
  "verifiedCreators": 15,
  "verifiedCreatorShare": 0.3061,
  "top10CreatorShare": 0.22,
  "topCreators": [
    {
      "username": "msk_fitness_coach",
      "userId": "2296923244",
      "profileUrl": "https://www.instagram.com/msk_fitness_coach/",
      "postsInSample": 2,
      "reelsInSample": 0,
      "avgLikes": 8,
      "avgComments": 0,
      "avgPlays": null,
      "avgViews": null,
      "isVerified": false
    },
    {
      "username": "jaquelineoliv",
      "userId": "361363753",
      "profileUrl": "https://www.instagram.com/jaquelineoliv/",
      "postsInSample": 1,
      "reelsInSample": 1,
      "avgLikes": 2110,
      "avgComments": 35,
      "avgPlays": 63849,
      "avgViews": null,
      "isVerified": true
    }
  ],
  "cooccurringHashtags": [
    {
      "hashtag": "gym",
      "count": 12,
      "sampleShare": 0.24
    },
    {
      "hashtag": "motivation",
      "count": 6,
      "sampleShare": 0.12
    }
  ],
  "cooccurringHashtagsDistinct": 127,
  "recentSampleObservedHours": 0.07,
  "recentSamplePostsPerDay": 8674.7,
  "topExamples": [
    {
      "id": "4009224371956556526",
      "shortCode": "DejoFMTTyLu",
      "url": "https://www.instagram.com/reel/DejoFMTTyLu/",
      "contentType": "REEL",
      "mediaType": "VIDEO",
      "captionPreview": "Apaga agora 😭😂 @fernandaferraz.nutrologa @brunosetenta @fauvalentenutri Vocês fazem parte disso, obrigada! . . . #fitness #meme #humor #w…",
      "creatorUsername": "jaquelineoliv",
      "likesCount": 2110,
      "likesHidden": false,
      "commentsCount": 35,
      "videoPlayCount": 63849,
      "videoViewCount": null,
      "timestamp": "2026-10-16T13:21:52.000Z",
      "appearedInTop": true,
      "appearedInRecent": false
    },
    {
      "id": "4008626304747787586",
      "shortCode": "DehgGKzx1VC",
      "url": "https://www.instagram.com/reel/DehgGKzx1VC/",
      "contentType": "REEL",
      "mediaType": "VIDEO",
      "captionPreview": "Imagine being more bothered by a woman’s gym outfit than your own lack of discipline. 😘 #explorepage #fyp #fitnessmotivation #fitness #wom…",
      "creatorUsername": "ttheasiawilliams",
      "likesCount": 1837,
      "likesHidden": false,
      "commentsCount": 31,
      "videoPlayCount": 17435,
      "videoViewCount": null,
      "timestamp": "2026-10-15T17:33:36.000Z",
      "appearedInTop": true,
      "appearedInRecent": false
    }
  ],
  "samplePosts": null,
  "scrapedAt": "2026-10-16T13:46:01.010Z"
}
```

#### Hashtag reports tab

```json
{
  "hashtag": "fitness",
  "availablePostsCount": null,
  "sampleReturned": 50,
  "coverageStatus": "FULL_REQUESTED_SAMPLE",
  "avgLikes": 331.6,
  "medianLikes": 37,
  "avgComments": 14.2,
  "medianPlays": 13879,
  "reelShare": 0.34,
  "uniqueCreators": 49,
  "top10CreatorShare": 0.22,
  "engagementRateObserved": 0.0485,
  "url": "https://www.instagram.com/explore/tags/fitness/",
  "status": "SUCCESS"
}
```

#### Engagement tab: hidden likes left out of the math

Seven of the 50 sampled #travel posts hide their likes, so the like metrics use the other 43 (`likesAvailableFor`). No sampled post exposed a view count, so views are `null` rather than `0`.

```json
{
  "hashtag": "travel",
  "sampleReturned": 50,
  "avgLikes": 1063.8,
  "medianLikes": 287,
  "likesAvailableFor": 43,
  "likesHiddenCount": 7,
  "totalVisibleLikes": 45742,
  "avgComments": 11.9,
  "medianComments": 0.5,
  "commentsAvailableFor": 50,
  "totalComments": 595,
  "avgViews": null,
  "medianViews": null,
  "viewsAvailableFor": 0,
  "avgPlays": 21766.5,
  "medianPlays": 9336,
  "playsAvailableFor": 15,
  "engagementRateObserved": 0.0936,
  "engagementRateAvailableFor": 14
}
```

#### Content mix & Reels tab

```json
{
  "hashtag": "fitness",
  "sampleReturned": 50,
  "reels": 17,
  "images": 15,
  "carousels": 18,
  "videosNonReel": 0,
  "reelShare": 0.34,
  "imageShare": 0.3,
  "carouselShare": 0.36,
  "avgReelLikes": 720.7,
  "avgReelPlays": 15408.4,
  "medianReelPlays": 13879,
  "avgReelDurationSeconds": 41.3,
  "originalAudioShare": 0.625
}
```

#### Creators tab

```json
{
  "hashtag": "fitness",
  "sampleReturned": 50,
  "uniqueCreators": 49,
  "creatorsAvailableFor": 50,
  "verifiedCreators": 15,
  "verifiedCreatorShare": 0.3061,
  "top10CreatorShare": 0.22,
  "topCreators": [
    {
      "username": "msk_fitness_coach",
      "userId": "2296923244",
      "profileUrl": "https://www.instagram.com/msk_fitness_coach/",
      "postsInSample": 2,
      "reelsInSample": 0,
      "avgLikes": 8,
      "avgComments": 0,
      "avgPlays": null,
      "avgViews": null,
      "isVerified": false
    },
    {
      "username": "jaquelineoliv",
      "userId": "361363753",
      "profileUrl": "https://www.instagram.com/jaquelineoliv/",
      "postsInSample": 1,
      "reelsInSample": 1,
      "avgLikes": 2110,
      "avgComments": 35,
      "avgPlays": 63849,
      "avgViews": null,
      "isVerified": true
    }
  ]
}
```

#### Co-occurring hashtags & top posts tab

```json
{
  "hashtag": "fitness",
  "sampleReturned": 50,
  "cooccurringHashtagsDistinct": 127,
  "cooccurringHashtags": [
    {
      "hashtag": "gym",
      "count": 12,
      "sampleShare": 0.24
    },
    {
      "hashtag": "motivation",
      "count": 6,
      "sampleShare": 0.12
    }
  ],
  "topExamples": [
    {
      "id": "4009224371956556526",
      "shortCode": "DejoFMTTyLu",
      "url": "https://www.instagram.com/reel/DejoFMTTyLu/",
      "contentType": "REEL",
      "mediaType": "VIDEO",
      "captionPreview": "Apaga agora 😭😂 @fernandaferraz.nutrologa @brunosetenta @fauvalentenutri Vocês fazem parte disso, obrigada! . . . #fitness #meme #humor #w…",
      "creatorUsername": "jaquelineoliv",
      "likesCount": 2110,
      "likesHidden": false,
      "commentsCount": 35,
      "videoPlayCount": 63849,
      "videoViewCount": null,
      "timestamp": "2026-10-16T13:21:52.000Z",
      "appearedInTop": true,
      "appearedInRecent": false
    },
    {
      "id": "4008626304747787586",
      "shortCode": "DehgGKzx1VC",
      "url": "https://www.instagram.com/reel/DehgGKzx1VC/",
      "contentType": "REEL",
      "mediaType": "VIDEO",
      "captionPreview": "Imagine being more bothered by a woman’s gym outfit than your own lack of discipline. 😘 #explorepage #fyp #fitnessmotivation #fitness #wom…",
      "creatorUsername": "ttheasiawilliams",
      "likesCount": 1837,
      "likesHidden": false,
      "commentsCount": 31,
      "videoPlayCount": 17435,
      "videoViewCount": null,
      "timestamp": "2026-10-15T17:33:36.000Z",
      "appearedInTop": true,
      "appearedInRecent": false
    }
  ]
}
```

#### Sample coverage & status tab

`recentSamplePostsPerDay` extrapolates from the time span the Recent sample covers. On a very busy hashtag 25 Recent posts can cover only a few minutes (`recentSampleObservedHours: 0.05` here), so treat it as an order of magnitude; larger samples give a steadier figure.

```json
{
  "hashtag": "travel",
  "status": "SUCCESS",
  "errorMessage": null,
  "sampleMode": "TOP_AND_RECENT",
  "sampleRequested": 50,
  "sampleReturned": 50,
  "topReturned": 25,
  "recentReturned": 25,
  "duplicatesRemoved": 0,
  "oldestObservedAt": "2026-10-15T17:39:46.000Z",
  "newestObservedAt": "2026-10-16T16:40:39.000Z",
  "recentSampleObservedHours": 0.05,
  "recentSamplePostsPerDay": 11020.41,
  "coverageStatus": "FULL_REQUESTED_SAMPLE",
  "coverageNote": null,
  "input": "travel",
  "scrapedAt": "2026-10-16T16:40:54.676Z"
}
```

#### Coverage status

| Value | Meaning |
|---|---|
| `FULL_REQUESTED_SAMPLE` | The requested sample size was reached |
| `LOW_RESULT_HASHTAG` | Instagram returned fewer public posts than requested |
| `BOUNDED_SAMPLE` | The `minPostDate` window ended the sample early |
| `PARTIAL_SAMPLE` | An upstream error cut the sample short. The report is delivered **free** |
| `NO_RESULTS` | No public posts were found |

#### Status values

| Status | Meaning | Charged |
|---|---|---|
| `SUCCESS` | Report built | Yes, one charge per started 100 sampled posts |
| `PARTIAL_SAMPLE` | Report built from an interrupted sample | No |
| `NO_RESULTS` | The hashtag has no public posts (in the date window) | No |
| `INVALID_HASHTAG` | Not a valid hashtag | No |
| `HASHTAG_UNAVAILABLE` | Instagram has no public hashtag by that name (hidden or restricted) | No |
| `RATE_LIMITED` / `BLOCKED` / `PROVIDER_ERROR` | Temporary upstream problem, safe to retry | No |
| `ANALYTICS_ERROR` | The sample could not be analyzed | No |

One failed hashtag never fails the run.

#### Run summary and retry input

The key-value store record `RUN_SUMMARY` holds the run totals (hashtags requested, succeeded, failed, reports saved, sampled posts processed, Top/Recent duplicates removed), metric availability rates, and a **comparison** of the hashtags in the run (largest hashtag, highest average and median likes, highest Reel share, most diverse creators, highest observed engagement rate). Hashtags that failed for a temporary reason are saved in `RETRY_INPUT`, ready to re-run.

### Use it from code (API)

The actor works with the Apify API and the official clients. Get your API token in Apify Console under **Settings → API & Integrations**. Results are the same rows as in the dataset tabs above.

**JavaScript** (`npm install apify-client`):

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

const client = new ApifyClient({ token: '<YOUR_API_TOKEN>' });
const run = await client.actor('coregent/instagram-hashtag-analytics').call({
  hashtags: ['fitness', 'workout', 'gym'],
  sampleSizePerHashtag: 100,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
const reports = items.filter((row) => row.rowType === 'hashtag_analytics'); // status rows have rowType "error"
reports.sort((a, b) => (b.medianLikes ?? 0) - (a.medianLikes ?? 0));
```

**Python** (`pip install apify-client`):

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")
run = client.actor("coregent/instagram-hashtag-analytics").call(run_input={
    "hashtags": ["fitness", "workout", "gym"],
    "sampleSizePerHashtag": 100,
})
items = client.dataset(run["defaultDatasetId"]).list_items().items
reports = [row for row in items if row["rowType"] == "hashtag_analytics"]  # status rows have rowType "error"
```

**HTTP** (one call that runs the actor and returns its rows; suits small runs that finish within the API's 5-minute synchronous limit):

```bash
curl -X POST "https://api.apify.com/v2/acts/coregent~instagram-hashtag-analytics/run-sync-get-dataset-items" \
  -H "Authorization: Bearer <YOUR_API_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"hashtags": ["fitness", "travel"], "sampleSizePerHashtag": 50}'
```

For larger runs, start the actor asynchronously (`POST /v2/acts/coregent~instagram-hashtag-analytics/runs`) and read the dataset when it finishes, or use the client libraries above, which wait for you. The run's `RUN_SUMMARY` record (key-value store) holds the cross-hashtag comparison.

### Pricing

Pay per event: completed hashtag reports are charged **once per started 100 sampled posts**. Deeper samples need more upstream work, so a 100-post report is one charge, a 250-post report three and a 500-post report five. A hashtag with fewer posts than requested is charged for the posts actually sampled. Sampled posts are never charged individually, and free rows (see the status table) cost nothing. The run only starts a hashtag your spending limit can cover at its full sample size, and stops cleanly when the limit is reached. The rate can differ by Apify plan; see the **Pricing** tab for the current rate on yours. This README deliberately doesn't quote figures.

### Limitations

- Metrics describe the **sample**, not every post that ever used the hashtag. Engagement counts also change over time.
- Instagram decides what its Top and Recent tabs show. Small, new or restricted hashtags may return fewer posts than requested.
- `availablePostsCount` is Instagram's displayed count where it is exposed, not an independently verified total.
- Some sampled posts may show no creator username when Instagram's fallback feed only names the creator's numeric id. Creator metrics still count them by id, and `creatorsAvailableFor` shows the coverage.
- The actor reports what Instagram shows and does not invent scores. There is no proprietary "difficulty" or trend prediction.

### Related actors

- [Instagram Hashtag Scraper](https://apify.com/coregent/instagram-hashtag-scraper): the raw posts and Reels behind each hashtag
- [Instagram Search Scraper](https://apify.com/coregent/instagram-search-scraper): discover hashtags to analyze
- [Instagram Profile Scraper](https://apify.com/coregent/instagram-profile-scraper): deep profiles of the top creators you find

### Legal and privacy

This is an unofficial product and is not affiliated with Instagram or Meta. It processes public content only, and analytics are based on available public samples, not guaranteed platform-wide totals. Public creator data can still be personal data: you are responsible for lawful use of the results, including GDPR/CCPA where they apply.

### Local development

```bash
cd actor
npm install
npm test     # unit tests
npm start    # runs INPUT.json; needs TIKHUB_API_KEY or the local "tikhub-config" key-value store
```

For an offline smoke test that spends no upstream balance, point `TIKHUB_BASE_URL` at a local mock server and set `TIKHUB_API_KEY=fake`.

# Actor input Schema

## `hashtags` (type: `array`):

One hashtag per line, with or without # (fitness, #travel). Hashtag URLs such as https://www.instagram.com/explore/tags/ai/ work too. Case doesn't matter and duplicates are removed. You get one analytics report per hashtag. Up to 100 hashtags per run.

## `sampleMode` (type: `string`):

Which Instagram hashtag feed to sample. TOP is Instagram's ranked feed (a quality / high-engagement snapshot). RECENT is the newest posts (current activity). TOP_AND_RECENT splits the sample about 50/50 between the two for a balanced view; a post that shows up in both is counted once.

## `sampleSizePerHashtag` (type: `integer`):

Total number of unique posts/Reels to analyze for each hashtag (split between Top and Recent in TOP_AND_RECENT mode). Bigger samples give steadier medians and more co-occurring hashtags, and take longer. Common values: 25, 50, 100, 250, 500. Small or niche hashtags may return fewer; the report says how many were actually sampled.

## `minPostDate` (type: `string`):

Optional. Only sample posts published on or after this date, for recent-campaign analysis. Use an ISO date (2026-09-01) or a period like "7 days" or "24 hours". The Recent feed stops early once it reaches older posts; the Top feed is ranked, so older posts there are skipped. Leave empty for no date limit.

## `includeMediaMix` (type: `boolean`):

Count Reels, images, carousels and other videos in the sample, with their shares.

## `includeReelAnalytics` (type: `boolean`):

Average and median Reel likes, comments, plays and views, average Reel length, and the share of Reels using original audio.

## `includeCreatorAnalytics` (type: `boolean`):

Unique creators, verified creators, the share of the sample posted by the 10 most frequent creators, and up to 10 top creators. Built from the sampled posts only; no extra profile lookups.

## `includeCooccurringHashtags` (type: `boolean`):

Other hashtags used in the sampled captions, with how many sampled posts use each. Derived from the sample, not an Instagram-provided related-hashtags list.

## `maxRelatedHashtags` (type: `integer`):

How many co-occurring hashtags to list per report, most frequent first.

## `includeTopExamples` (type: `boolean`):

Compact examples of the best-performing sampled posts (by visible likes + comments): link, type, caption preview, creator, likes, comments, plays/views and date.

## `maxTopExamples` (type: `integer`):

How many top posts to include per report.

## `includeRawSample` (type: `boolean`):

Also attach every sampled post to its report (compact form: link, type, creator, engagement, date and whether it came from Top, Recent or both). Off by default to keep reports small.

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

How many hashtags are analyzed in parallel. One hashtag's pages are always fetched in order.

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

Verbose logging for troubleshooting.

## Actor input object example

```json
{
  "hashtags": [
    "fitness"
  ],
  "sampleMode": "TOP_AND_RECENT",
  "sampleSizePerHashtag": 50,
  "minPostDate": "",
  "includeMediaMix": true,
  "includeReelAnalytics": true,
  "includeCreatorAnalytics": true,
  "includeCooccurringHashtags": true,
  "maxRelatedHashtags": 25,
  "includeTopExamples": true,
  "maxTopExamples": 10,
  "includeRawSample": false,
  "maxConcurrency": 5,
  "debugMode": false
}
```

# Actor output Schema

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

No description

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

No description

## `contentMix` (type: `string`):

No description

## `creators` (type: `string`):

No description

## `hashtags` (type: `string`):

No description

## `coverage` (type: `string`):

No description

## `summary` (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 = {
    "hashtags": [
        "fitness"
    ],
    "sampleSizePerHashtag": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("coregent/instagram-hashtag-analytics").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 = {
    "hashtags": ["fitness"],
    "sampleSizePerHashtag": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("coregent/instagram-hashtag-analytics").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 '{
  "hashtags": [
    "fitness"
  ],
  "sampleSizePerHashtag": 50
}' |
apify call coregent/instagram-hashtag-analytics --silent --output-dataset

```

## MCP server setup

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

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/G49MuMF7BjHFDzNus/builds/pXe3yCuOnf2KkLVAO/openapi.json
