# TikTok Hashtag Analytics (`coregent/tiktok-hashtag-analytics`) Actor

Analyze TikTok hashtags: total views, video volume, average and median engagement, top creators and videos, creator concentration, related hashtags, sounds, posting activity, and trend tracking. One report per hashtag.

- **URL**: https://apify.com/coregent/tiktok-hashtag-analytics.md
- **Developed by:** [Delowar Munna](https://apify.com/coregent) (community)
- **Security:** Limited permissions
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $30.00 / 1,000 hashtag analytics 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

## TikTok Hashtag Analytics

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

**Views, engagement, top creators, related hashtags & trend insights — one decision-ready report per TikTok hashtag.**

TikTok Hashtag Analytics turns TikTok hashtag data into structured hashtag intelligence. For each hashtag you give it, the Actor reads TikTok's global hashtag counters (total views, total video count), collects a bounded sample of the hashtag's public videos, and returns **one analytics report**: average and median performance, percentiles, engagement / share / save rates, viral concentration, creator diversity and concentration, related hashtags, top sounds, posting activity, content freshness, format and duration mix, ad / AI-labelled share, and optional trend tracking over time.

It answers questions like:

- Is `#booktok` too broad, or still worth targeting?
- Which of my candidate hashtags has the strongest **median** video performance?
- Is one viral post inflating the average?
- Which creators dominate this hashtag, and how concentrated is it?
- Which hashtags appear alongside it, and which sounds are most used?
- Is the hashtag getting more active over time?

No TikTok login, no cookies, no proxy setup.

***

### How is this different from a TikTok Hashtag Scraper?

| | TikTok Hashtag Scraper | **TikTok Hashtag Analytics** |
|---|---|---|
| Question it answers | *Which videos use this hashtag?* | *How large, active, concentrated, competitive and performant is this hashtag?* |
| One result row | one video | **one hashtag report** |
| Output | raw video records | medians, percentiles, rates, concentration, creator mix, related hashtags, sounds, activity, trends |

If you need the raw videos, use a hashtag scraper. If you need to **decide** something about a hashtag, use this Actor. (You can still save the sampled videos alongside the report — see *Save the sampled videos* below.)

***

### What does one result row represent?

**One row = one hashtag analytics report.** A run with 5 hashtags returns 5 rows. Hashtags that can't be analyzed (not found, invalid, no videos, provider error) still get a row with a `status` and `statusMessage`, so nothing disappears silently — those rows are free.

***

### Global metrics vs. sample metrics

This is the most important thing to understand about the report:

- **`global`** — TikTok's own hashtag-level counters: `totalViews`, `videoCount`, `description`, `isCommerce`, plus `viewsPerVideoGlobal` (`totalViews / videoCount`, a coarse aggregate, not a prediction of the views a new video will get). These describe the **whole hashtag**.
- **Everything else** (`performance`, `concentration`, `creators`, `activity`, `content`, `sounds`, `topCreators`, `topVideos`, `relatedHashtags`, `topSounds`) is computed from the **bounded video sample**.

TikTok's hashtag video feed exposes a **bounded, ranked sample**, not every video that ever used the hashtag. So `medianViews`, `topCreators`, `relatedHashtags` or the format mix describe **the observed sample**, not the entire TikTok hashtag universe. Activity fields are deliberately named `sampleVideosLast24h`, `sampleVideosLast7d`… for the same reason: they count videos *in the sample*, not TikTok-wide publishing volume.

#### What does `BOUNDED_SAMPLE` mean?

`coverage.status` tells you how the sample ended:

| Status | Meaning |
|---|---|
| `BOUNDED_SAMPLE` | The requested number of videos was collected. It is still a sample, never the full hashtag. |
| `LIMITED` | TikTok's feed (or your date filter) ran out before the requested size. |
| `PARTIAL` | An upstream error cut the sample short. The report says so in `statusMessage`. |
| `NO_RESULTS` | No videos were returned. |

There is deliberately **no `FULL` status**: the Actor never claims to have seen every video using a hashtag.

***

### What's in a report

| Section | Fields (highlights) |
|---|---|
| Identity | `inputHashtag` (as you typed it), `hashtag` (canonical, lower-case, no `#`), `hashtagId`, `hashtagUrl`, `region`, `mode`, `status` |
| `global` | `totalViews`, `videoCount`, `description`, `isCommerce`, `viewsPerVideoGlobal` |
| `sample` | `videosRequested`, `videosFetched`, `duplicatesRemoved`, `filteredByDate`, `videosSampled`, `uniqueCreators`, `sampleStartAt`, `sampleEndAt` |
| `performance` | avg **and median** views, likes, comments, shares, saves; `p25Views`, `p75Views`, `p90Views`, `p90Likes`, `p90Shares`; avg / median `engagementRateByViews`, `saveRateByViews`, `shareRateByViews` |
| `concentration` | `top1ViewSharePct`, `top5ViewSharePct`, `top10ViewSharePct` (viral concentration); `topCreatorVideoSharePct`, `top5CreatorVideoSharePct`, `top10CreatorVideoSharePct` (creator concentration) |
| `creators` | `uniqueCreators`, `creatorDiversityRatio`, `verifiedCreators`, `verifiedCreatorSharePct`; with enrichment: avg / median / p90 creator followers and the nano → mega tier mix |
| `activity` | `oldestSampleVideoAt`, `newestSampleVideoAt`, `sampleVideosLast24h/7d/30d`, age buckets (0–1d, 1–7d, 7–30d, 30d+), `medianVideoAgeHours`, `newContentSharePct` |
| `content` | video / photo / slideshow share, avg & median views by format, avg & median duration, duration bands (≤15s, 16–30s, 31–60s, 60s+) with median views & engagement, `adSharePct`, `aiGeneratedSharePct`, `topLocations` |
| `sounds` | `uniqueSounds`, `originalSoundSharePct`, `topSoundVideoSharePct`, `top5SoundVideoSharePct` |
| Lists | `topCreators[]`, `topVideos[]`, `relatedHashtags[]`, `topSounds[]` |
| `signals[]` | explainable labels such as `FRESH_CONTENT`, `CREATOR_CONCENTRATED`, `VIRAL_POST_CONCENTRATED` (see below) |
| `comparison` | COMPARE mode: this hashtag's rank within the run on 8 metrics |
| `history` | saved-snapshot comparisons and trend (TREND mode / comparison periods) |
| `dataQuality` | coverage % for views, likes, comments, shares, saves, timestamps, duration, sound, creator followers, ad label, AI label, location |
| `coverage` | `status`, `videosSampled`, `videosRequested`, `note` |

#### Why median metrics?

TikTok performance is extremely skewed: one viral post can have more views than the other 99 videos combined. An **average** then describes nobody. The **median** is the typical video. Every performance metric is returned as both, plus percentiles (`p25` / `p75` / `p90` views), and the concentration section shows how much of the sample's views come from the top 1, 5 and 10 videos — so an outlier is visible rather than hidden.

#### How is engagement rate calculated?

Per sampled video: `engagementRateByViews = (likes + comments + shares) / views`. The report gives the average and the median across the sample. Similarly `saveRateByViews = saves / views` and `shareRateByViews = shares / views`. These are Coregent-derived indicators from public counters, **not official TikTok metrics**.

#### How are top creators identified?

Videos are grouped by creator ID (not username — usernames can change). For each creator in the sample you get `videosInSample`, `sampleViews`, `sampleLikes`, `sampleShares` and `medianVideoViews`. The list is ranked by sample views by default; you can rank by videos in the sample, sample likes, or followers (with enrichment).

#### How is creator concentration calculated?

`top5CreatorVideoSharePct` = share of the sampled videos posted by the 5 creators with the most videos in the sample (same for top 1 and top 10). `creatorDiversityRatio` = `uniqueCreators / videosSampled` (1.0 = every video from a different creator).

#### What are related hashtags?

All other hashtags used in the sampled captions, excluding the hashtag itself (`#BookTok`, `booktok` and `BOOKTOK` count as one). For each: `count` (sampled videos using it), `sharePct` of the sample, `sampleViews` and `medianViews` of those videos. They are derived from the sample, not an official TikTok "related" list.

#### How are top sounds calculated?

Videos are grouped by TikTok's sound ID (not the title — titles repeat). For each sound: `videosInSample`, `sampleViews`, `sampleEngagement`, and whether it's an original sound. `originalSoundSharePct` shows how much of the sample uses creators' own audio rather than a shared track.

#### Opportunity signals

Signals are plain thresholds on metrics that are already in the report, so each one is explainable. They need at least 10 sampled videos.

| Signal | Rule |
|---|---|
| `HIGH_ACTIVITY` | ≥ 10% of the sample posted in the last 24 hours |
| `FRESH_CONTENT` | `newContentSharePct` (sample videos < 7 days old) ≥ 50% |
| `MATURE_HASHTAG` | ≥ 70% of the sample is older than 30 days |
| `HIGH_CREATOR_DIVERSITY` | `creatorDiversityRatio` ≥ 0.9 |
| `CREATOR_CONCENTRATED` | `top5CreatorVideoSharePct` ≥ 30% |
| `VIRAL_POST_CONCENTRATED` | `top1ViewSharePct` ≥ 30% |
| `HIGH_SHARE_RATE` | `medianShareRateByViews` ≥ 0.005 |
| `HIGH_SAVE_RATE` | `medianSaveRateByViews` ≥ 0.01 |
| `RISING_HASHTAG` / `DECLINING_HASHTAG` | trend classification (below) is `RISING` / `FALLING` |

***

### Input

| Field | Default | Description |
|---|---|---|
| `hashtags` | `["booktok"]` | Required. `booktok`, `#booktok` or `https://www.tiktok.com/tag/booktok`. Up to 100 per run. |
| `mode` | `SNAPSHOT` | `SNAPSHOT`, `COMPARE` (adds within-run ranks) or `TREND` (compares with saved history). |
| `videosPerHashtag` | `40` | Sample size per hashtag, 20–1000. TikTok serves 20 videos per page, so multiples of 20 work best (20, 40, 100, 200, 500, 1000). |
| `videosPublishedAfter` | empty | Only analyze videos published on/after a date: `2026-10-01`, `7 days ago`, `30 days`. |
| `region` | `US` | Region for TikTok's hashtag feed: US, GB, FR, JP, VN or SG. |
| `includeHashtagMetadata` | `true` | Global counters (`global` section). |
| `includeCreatorAnalytics` | `true` | Creator diversity, concentration, verified share, top creators. |
| `includeRelatedHashtags` | `true` | Co-occurring hashtags. |
| `includeSoundAnalytics` | `true` | Sound section and top sounds. |
| `includeTopVideos` | `true` | Most-viewed sampled videos. |
| `topVideosLimit` / `topCreatorsLimit` / `relatedHashtagsLimit` / `topSoundsLimit` | 10 / 10 / 20 / 10 | List lengths. |
| `topCreatorsSortBy` | `sampleViews` | `sampleViews`, `videosInSample`, `sampleLikes` or `followers`. |
| `enrichCreatorProfiles` | `false` | Look up the top creators' profiles to add follower statistics and the creator tier mix (billed per creator profile). |
| `maxCreatorsToEnrich` | `10` | Creators per hashtag to enrich (most-viewed first). |
| `comparisonPeriods` | `[]` | `PREVIOUS`, `7_DAYS`, `30_DAYS`, `90_DAYS`. TREND mode defaults to previous + 7 + 30 days. |
| `trackerKey` | empty | Name a tracking project; history is kept per tracker key. |
| `saveHistory` | `true` | Save a compact snapshot per report for later trend runs. |
| `includeSampleVideos` | `false` | Save every sampled video to the run's key-value store (`HASHTAG_SAMPLE_VIDEOS`). |
| `maxConcurrency` | `5` | Hashtags analyzed in parallel (1–15). |

#### Example inputs

**Run with defaults** — one hashtag, a 40-video sample, one report:

```json
{
  "hashtags": ["booktok"]
}
```

**Compare several hashtags** (standard 100-video samples, within-run ranks):

```json
{
  "hashtags": ["booktok", "reading", "bookrecommendations"],
  "mode": "COMPARE",
  "videosPerHashtag": 100
}
```

**Campaign research** — recent videos only, deeper sample, follower stats for the top creators:

```json
{
  "hashtags": ["brandcampaign", "competitorcampaign"],
  "videosPublishedAfter": "30 days ago",
  "videosPerHashtag": 200,
  "enrichCreatorProfiles": true,
  "topCreatorsLimit": 20,
  "maxCreatorsToEnrich": 20
}
```

**Benchmark a whole niche** — the run behind the screenshot and sample output below: 20 hashtags ranked against each other, follower stats for the top 3 creators of each, history started for later trend runs:

```json
{
  "hashtags": ["booktok", "romantasy", "cooking", "recipe", "fitness", "gymtok", "skincare", "makeup", "outfitinspo", "travel",
               "dogsoftiktok", "catsoftiktok", "diy", "gardening", "cleantok", "football", "nba", "photography", "learnontiktok", "fashion"],
  "mode": "COMPARE",
  "videosPerHashtag": 40,
  "topVideosLimit": 5,
  "topCreatorsLimit": 5,
  "relatedHashtagsLimit": 10,
  "topSoundsLimit": 5,
  "enrichCreatorProfiles": true,
  "maxCreatorsToEnrich": 3,
  "comparisonPeriods": ["PREVIOUS"],
  "trackerKey": "readme-demo"
}
```

**Historical tracking** — schedule this daily or weekly:

```json
{
  "hashtags": ["aitools", "generativeai"],
  "mode": "TREND",
  "trackerKey": "ai-hashtags",
  "videosPerHashtag": 100,
  "comparisonPeriods": ["PREVIOUS", "7_DAYS", "30_DAYS"]
}
```

***

### Output

Every dataset tab shows the same rows (one report per hashtag), with a different set of columns. The samples below are real records from the 20-hashtag *Benchmark a whole niche* run above (2026-11-02, US). Each shows the fields that tab displays, with lists cut to one or two items.

#### Overview

![Overview table view](https://raw.githubusercontent.com/coregentdevspace/tiktok-hashtag-analytics-assets/main/tiktok-hashtag-analytics-output-overview-table-view.png)

```json
{
  "hashtag": "catsoftiktok",
  "status": "SUCCESS",
  "global": { "totalViews": 774398775128, "videoCount": 58454065 },
  "sample": { "videosSampled": 40 },
  "performance": { "medianViews": 17241483, "avgViews": 31701798, "medianLikes": 3172280, "medianEngagementRateByViews": 0.1998 },
  "creators": { "uniqueCreators": 39 },
  "concentration": { "top5ViewSharePct": 46.6 },
  "activity": { "newContentSharePct": 5 },
  "signals": ["MATURE_HASHTAG", "HIGH_CREATOR_DIVERSITY", "HIGH_SHARE_RATE", "HIGH_SAVE_RATE"],
  "coverage": { "status": "BOUNDED_SAMPLE" },
  "hashtagUrl": "https://www.tiktok.com/tag/catsoftiktok",
  "scrapedAt": "2026-11-02T04:25:32.852Z"
}
```

#### Performance

The average is about 21% above the median, the 90th-percentile video has 2.5× the median's views, and the top 10 videos hold 56.5% of the sample's views:

```json
{
  "hashtag": "recipe",
  "sample": { "videosSampled": 40 },
  "performance": {
    "avgViews": 14346776, "medianViews": 11819738, "p25Views": 3906346, "p75Views": 17548694, "p90Views": 30004887,
    "avgLikes": 662394, "medianLikes": 379861, "avgComments": 3962, "medianComments": 1554,
    "avgShares": 110242, "medianShares": 80214, "avgSaves": 234688, "medianSaves": 132538,
    "medianEngagementRateByViews": 0.0467, "medianShareRateByViews": 0.0064, "medianSaveRateByViews": 0.0142
  },
  "concentration": { "top1ViewSharePct": 10.4, "top10ViewSharePct": 56.5 }
}
```

#### Creators

A creator-concentrated hashtag: the official `@nba` account posted 6 of the 40 sampled videos. The follower counts come from creator enrichment:

```json
{
  "hashtag": "nba",
  "creators": { "uniqueCreators": 26, "creatorDiversityRatio": 0.65, "verifiedCreatorSharePct": 34.6, "medianCreatorFollowers": 1678617, "creatorsEnriched": 3 },
  "concentration": { "topCreatorVideoSharePct": 15, "top5CreatorVideoSharePct": 45, "top10CreatorVideoSharePct": 60 },
  "topCreators": [
    { "username": "nba", "followers": 27417507, "tier": "MEGA", "verified": true, "videosInSample": 6, "sampleViews": 44007645 },
    { "username": "houseofdunk", "followers": 1089766, "tier": "MEGA", "verified": false, "videosInSample": 1, "sampleViews": 41979440 }
  ]
}
```

#### Content & sounds

```json
{
  "hashtag": "outfitinspo",
  "activity": { "sampleVideosLast24h": 6, "sampleVideosLast7d": 10, "medianVideoAgeHours": 554.6 },
  "content": { "videoSharePct": 100, "slideshowSharePct": 0, "medianDurationSeconds": 14.2, "adSharePct": 0, "aiGeneratedSharePct": 5 },
  "sounds": { "uniqueSounds": 40, "originalSoundSharePct": 85 },
  "relatedHashtags": [
    { "hashtag": "fyp", "count": 12, "sharePct": 30, "medianViews": 666195 },
    { "hashtag": "ootd", "count": 9, "sharePct": 22.5, "medianViews": 405509 }
  ],
  "topSounds": [
    { "musicId": "7141385471120886533", "title": "original sound - xjustmxl", "author": "mel 🎸⚡️", "original": true, "videosInSample": 1, "sampleViews": 16458852 }
  ]
}
```

#### Trends & comparison

In COMPARE mode every report is ranked against the other hashtags in the run (1 = highest). `#catsoftiktok` ranked 1st of the 20 for median views, median engagement rate and share rate. This was the tracker's first run, so the history comparison says `INSUFFICIENT_HISTORY` until a later run has a snapshot to compare against:

```json
{
  "hashtag": "catsoftiktok",
  "mode": "COMPARE",
  "history": {
    "trend": "INSUFFICIENT_HISTORY",
    "trendBasis": null,
    "comparisonCompatibility": "NO_HISTORY",
    "snapshotsStored": 0,
    "historySaved": true,
    "comparisons": { "PREVIOUS": { "status": "INSUFFICIENT_HISTORY" } }
  },
  "comparison": {
    "hashtagsCompared": 20,
    "ranks": { "totalViews": 9, "videoCount": 8, "medianViews": 1, "medianEngagementRate": 1, "creatorDiversity": 2, "newContentShare": 15, "shareRate": 1, "saveRate": 3 }
  },
  "signals": ["MATURE_HASHTAG", "HIGH_CREATOR_DIVERSITY", "HIGH_SHARE_RATE", "HIGH_SAVE_RATE"],
  "scrapedAt": "2026-11-02T04:25:32.852Z"
}
```

On a later run with the same `trackerKey` and sample settings, `history.comparisons.PREVIOUS` returns `"status": "OK"` with `totalViewsDelta`, `videoCountDelta`, `medianViewsDelta` and the other deltas described in the FAQ.

#### Full report

The complete `#booktok` record from the same run (lists cut to one or two items, description shortened):

```json
{
  "rowType": "tiktok_hashtag_analytics",
  "status": "SUCCESS",
  "statusMessage": null,
  "mode": "COMPARE",
  "inputHashtag": "booktok",
  "hashtag": "booktok",
  "hashtagId": "1622962893630470",
  "hashtagUrl": "https://www.tiktok.com/tag/booktok",
  "region": "US",
  "global": {
    "totalViews": 563687245637,
    "videoCount": 88399598,
    "description": "Calling all book creators! Share your favorite reads, recomm…",
    "isCommerce": false,
    "viewsPerVideoGlobal": 6377,
    "source": "tiktok_hashtag_metadata"
  },
  "sample": {
    "sampleMode": "TOP_OR_AVAILABLE",
    "videosRequested": 40,
    "videosFetched": 40,
    "duplicatesRemoved": 0,
    "filteredByDate": 0,
    "videosPublishedAfter": null,
    "videosSampled": 40,
    "uniqueCreators": 38,
    "sampleStartAt": "2023-08-23T15:45:51.000Z",
    "sampleEndAt": "2026-11-01T17:04:58.000Z"
  },
  "performance": {
    "totalSampleViews": 207071043,
    "avgViews": 5176776,
    "medianViews": 2508752,
    "p25Views": 567294,
    "p75Views": 6751075,
    "p90Views": 16294187,
    "avgLikes": 598680,
    "medianLikes": 300704,
    "avgComments": 2487,
    "medianComments": 1443,
    "avgShares": 45341,
    "medianShares": 17930,
    "avgSaves": 44012,
    "medianSaves": 19221,
    "p90Likes": 1180904,
    "p90Shares": 109709,
    "avgEngagementRateByViews": 0.1274,
    "medianEngagementRateByViews": 0.1223,
    "avgSaveRateByViews": 0.0119,
    "medianSaveRateByViews": 0.0071,
    "avgShareRateByViews": 0.0092,
    "medianShareRateByViews": 0.0071
  },
  "concentration": {
    "top1ViewSharePct": 13.7,
    "top5ViewSharePct": 49,
    "top10ViewSharePct": 70.9,
    "topCreatorVideoSharePct": 5,
    "top5CreatorVideoSharePct": 17.5,
    "top10CreatorVideoSharePct": 30
  },
  "creators": {
    "uniqueCreators": 38,
    "creatorDiversityRatio": 0.95,
    "verifiedCreators": 4,
    "verifiedCreatorSharePct": 10.5,
    "creatorsWithFollowerCount": 3,
    "avgCreatorFollowers": 641233,
    "medianCreatorFollowers": 24320,
    "p90CreatorFollowers": 1509786,
    "nanoCreatorSharePct": 0,
    "microCreatorSharePct": 66.7,
    "midCreatorSharePct": 0,
    "macroCreatorSharePct": 0,
    "megaCreatorSharePct": 33.3,
    "topCreatorsRankedBy": "sampleViews",
    "creatorsEnriched": 3,
    "followerSource": "creator_profile_enrichment"
  },
  "activity": {
    "oldestSampleVideoAt": "2023-08-23T15:45:51.000Z",
    "newestSampleVideoAt": "2026-11-01T17:04:58.000Z",
    "sampleVideosLast24h": 2,
    "sampleVideosLast7d": 4,
    "sampleVideosLast30d": 6,
    "videosAge0to1d": 2,
    "videosAge1to7d": 2,
    "videosAge7to30d": 2,
    "videosAge30dPlus": 34,
    "medianVideoAgeHours": 3007,
    "newContentSharePct": 10
  },
  "content": {
    "videoSharePct": 100,
    "photoPostSharePct": 0,
    "slideshowSharePct": 0,
    "avgViewsByFormat": {
      "video": 5176776,
      "photo": null,
      "slideshow": null
    },
    "medianViewsByFormat": {
      "video": 2508752,
      "photo": null,
      "slideshow": null
    },
    "avgDurationSeconds": 60.8,
    "medianDurationSeconds": 15,
    "durationBands": [
      {
        "band": "0-15s",
        "videos": 21,
        "sharePct": 52.5,
        "medianViews": 2972742,
        "medianEngagementRateByViews": 0.1398
      }
    ],
    "adVideoCount": 0,
    "adSharePct": 0,
    "aiGeneratedVideoCount": 0,
    "aiGeneratedSharePct": 0,
    "topLocations": [
      {
        "location": "US",
        "videos": 20,
        "sharePct": 50
      },
      {
        "location": "GB",
        "videos": 14,
        "sharePct": 35
      }
    ]
  },
  "sounds": {
    "uniqueSounds": 40,
    "originalSoundSharePct": 70,
    "topSoundVideoSharePct": 2.5,
    "top5SoundVideoSharePct": 12.5
  },
  "topCreators": [
    {
      "username": "therealemilyea1",
      "creatorId": "63845873289306112",
      "nickname": "Emily EA",
      "profileUrl": "https://www.tiktok.com/@therealemilyea1",
      "followers": 24320,
      "verified": false,
      "tier": "MICRO",
      "videosInSample": 1,
      "sampleViews": 28342534,
      "sampleLikes": 4598969,
      "sampleShares": 108376,
      "medianVideoViews": 28342534
    }
  ],
  "topVideos": [
    {
      "videoId": "7634733634385235230",
      "url": "https://www.tiktok.com/@therealemilyea1/video/7634733634385235230",
      "caption": "Booktok hates to see us coming #viral #trend #BookTok  ",
      "createdAt": "2026-05-01T01:46:03.000Z",
      "views": 28342534,
      "likes": 4598969,
      "comments": 5307,
      "shares": 108376,
      "saves": 159072,
      "engagementRateByViews": 0.1663,
      "durationSeconds": 7.5,
      "format": "video",
      "creator": {
        "username": "therealemilyea1",
        "creatorId": "63845873289306112",
        "verified": false
      },
      "music": {
        "musicId": "7624368171760257800",
        "title": "No One Else",
        "author": "prodshushy"
      },
      "hashtags": [
        "viral",
        "trend",
        "booktok"
      ]
    }
  ],
  "relatedHashtags": [
    {
      "hashtag": "fyp",
      "count": 12,
      "sharePct": 30,
      "sampleViews": 38530470,
      "medianViews": 2716130,
      "hashtagUrl": "https://www.tiktok.com/tag/fyp"
    },
    {
      "hashtag": "bookish",
      "count": 7,
      "sharePct": 17.5,
      "sampleViews": 8241828,
      "medianViews": 574558,
      "hashtagUrl": "https://www.tiktok.com/tag/bookish"
    }
  ],
  "relatedHashtagsDistinct": 84,
  "topSounds": [
    {
      "musicId": "7624368171760257800",
      "title": "No One Else",
      "author": "prodshushy",
      "original": false,
      "videosInSample": 1,
      "sampleViews": 28342534,
      "sampleEngagement": 4712652
    }
  ],
  "signals": [
    "MATURE_HASHTAG",
    "HIGH_CREATOR_DIVERSITY",
    "HIGH_SHARE_RATE"
  ],
  "comparison": {
    "hashtagsCompared": 20,
    "ranks": {
      "totalViews": 12,
      "videoCount": 4,
      "medianViews": 9,
      "medianEngagementRate": 5,
      "creatorDiversity": 5,
      "newContentShare": 10,
      "shareRate": 6,
      "saveRate": 11
    }
  },
  "history": {
    "trackerKey": "readme-demo",
    "snapshotsStored": 0,
    "compatibleSnapshots": 0,
    "comparisonCompatibility": "NO_HISTORY",
    "comparisonCompatibilityNote": null,
    "comparisons": {
      "PREVIOUS": {
        "status": "INSUFFICIENT_HISTORY"
      }
    },
    "trend": "INSUFFICIENT_HISTORY",
    "trendBasis": null,
    "historySaved": true,
    "historyNote": null
  },
  "dataQuality": {
    "viewCoveragePct": 100,
    "likeCoveragePct": 100,
    "commentCoveragePct": 100,
    "shareCoveragePct": 100,
    "saveCoveragePct": 100,
    "timestampCoveragePct": 100,
    "durationCoveragePct": 100,
    "soundCoveragePct": 100,
    "creatorFollowerCoveragePct": 7.9,
    "adLabelCoveragePct": 100,
    "aiLabelCoveragePct": 100,
    "locationCoveragePct": 100
  },
  "coverage": {
    "status": "BOUNDED_SAMPLE",
    "videosSampled": 40,
    "videosRequested": 40,
    "note": "Bounded sample of 40 videos from TikTok's hashtag feed — not every video using the hashtag."
  },
  "configHash": "ce183453d3d5",
  "tikhubCalls": 6,
  "scrapedAt": "2026-11-02T04:25:01.703Z",
  "runId": "GKAC6xkPmuKuVr2g1"
}
```

A run summary (hashtags requested / succeeded / failed, videos requested / sampled / unique, reports created, trend comparisons, history snapshots saved) is stored in the run's key-value store as `RUN_SUMMARY`.

***

### FAQ

**Can I compare multiple hashtags?** Yes. Put them all in `hashtags` and choose `COMPARE` mode. Each report gets `comparison.ranks` — its rank within the run (1 = highest) on total views, video count, median views, median engagement rate, creator diversity, new-content share, share rate and save rate. There is no single "best hashtag" score; you see the rank on every dimension.

**Can I track hashtags over time?** Yes. Every successful report saves a compact snapshot (unless you turn `saveHistory` off). Run `TREND` mode on a schedule and each report gets `history.comparisons` against the previous run and/or about 7, 30 or 90 days ago: `totalViewsDelta`, `videoCountDelta`, `videoCountGrowthPct`, `medianViewsDelta`, `medianEngagementRateDelta`, `creatorDiversityDelta`, `newContentShareDelta`. History starts with your first run; until a snapshot that old exists the period says `INSUFFICIENT_HISTORY`. Use `trackerKey` to keep separate histories per project.

Snapshots are only compared when they were taken with the same sample settings (sample size, date filter, region, enrichment) — `history.comparisonCompatibility` says `HIGH` or `LOW`. Runs less than 30 minutes apart don't create duplicate snapshots, and partial (error-truncated) samples are never saved.

**What is trend momentum?** `history.trend` is `RISING`, `STABLE`, `FALLING` or `INSUFFICIENT_HISTORY`, from the longest requested period that has a baseline. `RISING` = the hashtag's global video count grew by ≥ 0.5% per day **and** the sample is at least as fresh as before. `FALLING` = video-count growth ≤ 0.05% per day **and** the sample got less fresh. Anything in between is `STABLE`. It is a Coregent-derived classification, not TikTok trend data. Note that `totalViewsDelta` is the change in TikTok's cumulative counter between two observations — it is **not** "views earned by videos posted this week".

**Does it require a TikTok login or cookies?** No. It uses public TikTok data only, with no customer login, cookies or proxy configuration.

**Does it use TikHub?** Yes. TikTok data is fetched through TikHub's hosted TikTok API, browserlessly. That's why there's nothing to configure and no proxy section.

**Are save / share metrics always available?** Usually, but not guaranteed for every video. Check `dataQuality` — e.g. `saveCoveragePct: 85` means 15% of the sampled videos didn't expose a save count.

**How are missing metrics handled?** Missing is **never** counted as zero. A median or average is computed only over the videos that carry that value, and `dataQuality` gives the coverage. A metric with no known values is `null`. Example: TikTok's hashtag feed doesn't include creator follower counts, so follower statistics stay `null` (with `creatorFollowerCoveragePct: 0`) unless you turn on creator enrichment — they're never reported as "0 followers".

**What about AI-generated content and ads?** `aiGeneratedSharePct` uses only TikTok's own AI-generated label — the Actor never infers it — and is computed over videos where TikTok exposed the label (`aiLabelCoveragePct`). `adSharePct` uses TikTok's ad flag the same way.

**What's in `topLocations`?** The creation region TikTok attaches to each video. It is creator/video metadata, **not audience geography**.

**Can I get the sampled videos too?** Turn on `includeSampleVideos`. Every normalized sampled video is saved in the run's key-value store as `HASHTAG_SAMPLE_VIDEOS` (grouped by hashtag). The dataset stays one report per hashtag.

**What happens if one hashtag fails?** The run continues. The failed hashtag gets a row with a `status` such as `HASHTAG_NOT_FOUND`, `INVALID_HASHTAG`, `NO_RESULTS`, `RATE_LIMITED`, `VIDEOS_UNAVAILABLE` or `PROVIDER_ERROR`, and isn't charged. Only exact hashtag names are analyzed: if TikTok has no hashtag with exactly that name, you get `HASHTAG_NOT_FOUND` rather than a report about a similar-sounding tag.

**Can I use it via API or MCP?** Yes — like any Apify Actor, run it from the Apify API, the JavaScript / Python clients, integrations (Make, Zapier, n8n…), or as a tool for AI agents through the Apify MCP server. Example:

```bash
curl -X POST "https://api.apify.com/v2/acts/coregent~tiktok-hashtag-analytics/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"hashtags": ["booktok", "reading"], "mode": "COMPARE", "videosPerHashtag": 100}'
```

***

### How does pricing work?

Pay per event — you pay for analytics reports delivered, not for compute or proxies:

- **Hashtag analysis** — charged per delivered report, one event per started 100 sampled videos. A report on up to 100 videos is one event; 101–200 videos is two; a 1,000-video report is ten.
- **Creator enrichment** (optional) — charged per creator profile looked up when `enrichCreatorProfiles` is on.

Not charged: hashtags that are not found, invalid, return no videos or fail; history snapshots; the run summary; the saved sample videos. A report whose sample an upstream error cut short (`PARTIAL_SAMPLE`) is charged only for the videos it actually analyzed. See the **Pricing** tab for current rates — this README deliberately doesn't quote figures, because live prices are set on the platform. If you set a maximum cost per run, the Actor respects it: it shrinks or skips samples before doing work it couldn't charge for.

***

### Notes and limitations

- Unofficial product; not affiliated with, endorsed by, or connected to TikTok or ByteDance.
- Hashtag video feeds are bounded, ranked samples. Sample analytics do not represent every TikTok video using the hashtag; global views and video counts are separate TikTok metadata.
- TikTok's feed is ranked, not chronological: with a strict `videosPublishedAfter` the sample may be smaller than requested (`coverage.status: LIMITED`).
- Creator follower counts change over time and are only available with enrichment.
- Save / share counts and AI / ad labels may be missing for some videos — see `dataQuality`.
- Trend classifications are Coregent-derived. Historical tracking starts only when you begin saving snapshots.
- The Actor does **not** provide official TikTok Analytics, a complete hashtag census, audience demographics, reach, impressions, watch time, retention, conversion rates or search volume.
- Use the data in line with TikTok's terms and applicable data-protection law.

# Actor input Schema

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

One hashtag per line: booktok, #booktok or https://www.tiktok.com/tag/booktok all work. Case doesn't matter and duplicates are removed. You get one analytics report per hashtag. Up to 100 hashtags per run.

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

SNAPSHOT: one current report per hashtag. COMPARE: same reports, plus each hashtag's rank within this run on total views, video count, median views, median engagement rate, creator diversity, content freshness, share rate and save rate. TREND: compares each report with earlier saved snapshots of the same hashtag (previous run, 7 and 30 days by default). Trend history starts with your first run.

## `videosPerHashtag` (type: `integer`):

How many of the hashtag's videos to analyze. TikTok serves the feed 20 videos per page, so multiples of 20 work best: 20, 40, 100, 200, 500 or 1000. Bigger samples give steadier medians and more related hashtags and sounds. Each report is billed per started 100 sampled videos. Small hashtags may return fewer; the report says how many were sampled.

## `videosPublishedAfter` (type: `string`):

Optional. Only analyze sampled videos published on or after this date, e.g. 2026-10-01, "7 days ago" or "30 days". TikTok's hashtag feed is ranked, not chronological, so a strict date filter may return a smaller sample. Leave empty for no date limit.

## `region` (type: `string`):

Region TikTok uses when serving the hashtag feed. TikTok's hashtag API supports these regions only.

## `includeHashtagMetadata` (type: `boolean`):

TikTok's own hashtag counters: total views, total video count, description and commerce flag, plus views per video. These are global, not computed from the sample.

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

Unique creators, creator diversity, creator concentration, verified-creator share and the top creators in the sample.

## `includeRelatedHashtags` (type: `boolean`):

Other hashtags used in the sampled captions, with how many sampled videos use each, their share of the sample, and their sample views.

## `includeSoundAnalytics` (type: `boolean`):

Unique sounds, original-sound share, sound concentration and the top sounds in the sample.

## `includeTopVideos` (type: `boolean`):

The most-viewed sampled videos with link, caption, date, engagement, creator, sound and hashtags.

## `topVideosLimit` (type: `integer`):

How many top videos to list per report.

## `topCreatorsLimit` (type: `integer`):

How many top creators to list per report.

## `topCreatorsSortBy` (type: `string`):

How top creators are ranked. 'Followers' only makes sense with creator profile enrichment on.

## `relatedHashtagsLimit` (type: `integer`):

How many related hashtags to list per report, most frequent first.

## `topSoundsLimit` (type: `integer`):

How many top sounds to list per report.

## `enrichCreatorProfiles` (type: `boolean`):

TikTok's hashtag feed doesn't include creator follower counts. Turn this on to look up the profiles of the top creators in each sample and add follower statistics (average / median / 90th-percentile followers) and the creator tier mix (nano to mega). Billed per creator profile fetched.

## `maxCreatorsToEnrich` (type: `integer`):

With enrichment on: how many creators per hashtag to look up, starting with the creators that have the most views in the sample.

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

Compare each report with a saved snapshot from: the previous run, or about 7, 30 or 90 days ago. Defaults to previous + 7 + 30 days in TREND mode. A period shows INSUFFICIENT_HISTORY until a snapshot that old exists.

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

Optional name for this tracking project, e.g. ai-hashtags. History is kept separately per tracker key, so the same hashtag can be tracked for several projects. Leave empty to use the default tracker.

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

Save a compact snapshot of each report so later TREND runs can compare against it. Snapshots are only compared when the sample settings match, and runs less than 30 minutes apart don't create duplicates.

## `includeSampleVideos` (type: `boolean`):

Also save every sampled video (normalized) to the run's key-value store as HASHTAG_SAMPLE_VIDEOS. The dataset stays one report per hashtag.

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

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

## Actor input object example

```json
{
  "hashtags": [
    "booktok"
  ],
  "mode": "SNAPSHOT",
  "videosPerHashtag": 40,
  "videosPublishedAfter": "",
  "region": "US",
  "includeHashtagMetadata": true,
  "includeCreatorAnalytics": true,
  "includeRelatedHashtags": true,
  "includeSoundAnalytics": true,
  "includeTopVideos": true,
  "topVideosLimit": 10,
  "topCreatorsLimit": 10,
  "topCreatorsSortBy": "sampleViews",
  "relatedHashtagsLimit": 20,
  "topSoundsLimit": 10,
  "enrichCreatorProfiles": false,
  "maxCreatorsToEnrich": 10,
  "comparisonPeriods": [],
  "trackerKey": "",
  "saveHistory": true,
  "includeSampleVideos": false,
  "maxConcurrency": 5
}
```

# Actor output Schema

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

No description

## `performance` (type: `string`):

No description

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

No description

## `contentAndSounds` (type: `string`):

No description

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

No description

## `full` (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 = {
    "hashtags": [
        "booktok"
    ],
    "videosPerHashtag": 40
};

// Run the Actor and wait for it to finish
const run = await client.actor("coregent/tiktok-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": ["booktok"],
    "videosPerHashtag": 40,
}

# Run the Actor and wait for it to finish
run = client.actor("coregent/tiktok-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": [
    "booktok"
  ],
  "videosPerHashtag": 40
}' |
apify call coregent/tiktok-hashtag-analytics --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,coregent/tiktok-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/nYAcmhnEsWEbzdRUb/builds/ybwAr01GhDXLGNFwY/openapi.json
