# TikTok Hashtag Stats Scraper & Monitor - Views & Video Growth (`neverempty/tiktok-hashtag-stats-scraper`) Actor

Track how fast TikTok hashtags grow: exact view and video counts per hashtag, average views per video, and with monitoring on, the views and videos added since the last run and per hour. The rounded view count is shown beside the exact one.

- **URL**: https://apify.com/neverempty/tiktok-hashtag-stats-scraper.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** Social media, Videos, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.73 / 1,000 hashtag stats row returneds

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 Stats Scraper & Monitor - Views & Video Growth

Track how fast TikTok hashtags grow: view and video counts for any hashtag, and with monitoring on, **the views and videos added since your last run and the growth per hour** (`viewCountDelta`, `videoCountDelta`, `viewsPerHour`, `videosPerHour`), so you can compare hashtags or report on a campaign without keeping your own history.

TikTok sends two view counts for a hashtag: a rounded one and an exact one. This Actor returns the exact figure as `viewCount` and puts the rounded one beside it, because they differ. Measured on 2026-09-14 from Apify, **7 of 7 hashtags** had a rounded view count that differed from the exact one, by up to 48,136,872 views - and not always downwards:

| Hashtag | Rounded view count | Exact view count | Rounded minus exact |
|---|---|---|---|
| #booktok | 550,700,000,000 | 550,651,863,128 | +48,136,872 |
| #roblox | 1,574,800,000,000 | 1,574,810,245,517 | -10,245,517 |

A rounded count only moves when the growth crosses its rounding step (100,000,000 views for #booktok), so tracking built on it reports "no growth" through changes smaller than that. This Actor compares the exact figures.

The **rounded video count TikTok sends is 0 in all 10 responses measured** (for hashtags with 1,976,565 to 9,276,060,801 videos), so it is not returned at all - you will never get a hashtag with "0 videos" from it. `videoCount` is the exact figure.

### Best for

- **Hashtag research and niche sizing** - compare how big hashtags really are, and how many views an average video gets (`averageViewsPerVideo`).
- **Campaign and branded-hashtag reporting** - schedule it daily and get the views and videos added since the previous report.
- **Trend tracking** - `viewsPerHour` and `videosPerHour` show which hashtags are accelerating, without diffing anything yourself.

### What each row contains

- **What you asked for and what TikTok returned** - `inputHashtag`, `hashtag`, `hashtagMatchesInput`. TikTok normalises names itself: on 2026-09-14 `skin care` came back as #skincare and `a` as #A. Both names are on every row, and a hashtag that two of your entries resolve to is read and charged once.
- **Identity** - `challengeId`, `hashtagUrl`, `description`, `isCommerce`.
- **Exact counts** - `viewCount`, `videoCount`, `averageViewsPerVideo`.
- **The rounded figure** - `viewCountRounded` and `viewCountRoundingGap` (rounded minus exact).
- **Monitoring** - `change`, `isFirstCheck`, `previousCheckedAt`, `previousViewCount`, `previousVideoCount`, `viewCountDelta`, `videoCountDelta`, `hoursSincePreviousCheck`, `viewsPerHour`, `videosPerHour`.

### Monitoring, and what it costs

Turn **monitoring mode** on and the Actor remembers each hashtag's exact counts. On later runs a hashtag comes back only when they moved:

- **Any change** (default) - the video count moved, or the view count moved by at least `minViewIncrease` (0 = any movement).
- **New videos only** - the video count moved.

The comparison is always against the **last row you received** for that hashtag. A movement too small to cross your threshold is not remembered, so small movements add up until they cross it instead of being lost. The first run returns every hashtag once (`change: "first-check"`) to set the baseline; later rows say `change: "changed"`.

Counts are remembered **per hashtag, not per list**, so adding a hashtag does not reset the others. In monitoring mode `maxResults` does not cut changes: every change among the hashtags read is returned. If the run's maximum total charge stops a changed hashtag from being delivered, it is not remembered, so its counts are still compared with the last row you received the next time it is read.

**Large hashtags come back on almost every run, in both modes.** Their counts move all the time: on 2026-09-14 two monitoring runs of #booktok started about 7 seconds apart returned +14,424 views and +8 videos. Neither `minViewIncrease` nor "new videos only" makes such a hashtag quiet, because new videos keep arriving. Monitoring saves rows for small and slow hashtags, and gives every row the growth since the previous one.

**Do not put the same hashtag in two schedules that can run at the same time.** Each run writes back only the hashtags it delivered, merged into the counts as they are at that moment, then reads them again and writes once more if another run overwrote them - so two overlapping runs over different hashtags keep each other's history. But Apify's key-value store has no atomic update, so this cannot be made watertight: runs that finish at the same instant, or read the same hashtag at the same time, can still overwrite each other's record, and the next run may return a `first-check` or a change a second time.

#### What it costs

- **The row price** for every row returned - in monitoring mode, that is `first-check` and `changed` rows only.
- **In monitoring mode, $0.30 per 1,000 hashtag checks** - charged for every hashtag whose counts were read, whether or not they moved. Names TikTok returns nothing for and bot checks are not charged. Example: 50 hashtags checked every hour is 36,000 checks a month, $10.80, plus the rows returned.

The Actor reads only as many hashtags as the run's maximum total charge can pay for with both a check and a change row each, so a low limit cannot use up the budget on check fees and leave nothing for the rows. Hashtags beyond that are not read; a `budget-reached` row says how many, and raising the maximum total charge lets them be read.

### Rows that are never charged

- `no-hashtag-found` - TikTok returned no hashtag for that name (code 10205, a 205-byte answer). That is the answer it gave on 2026-09-14 for made-up names; whether it gives the same answer for a hashtag that exists but is hidden has not been measured, so the row does not claim the hashtag has no videos.
- `blocked` - every TikTok address answered with a bot check or an empty response. No count is reported.
- `unreadable` - the response could not be read, carried a status code whose meaning has not been measured here, or did not contain the exact counts. A response without the exact counts is never returned with the rounded ones in their place.
- `no-change` - monitoring ran and no hashtag moved enough. (The check fee for the hashtags read still applies.)
- `invalid-input` - the text is not a hashtag (for example it contains `/`, `?` or `&`), so nothing was requested.
- `budget-reached` - the run's maximum charge was reached: the row says how many ready rows fit, or how many hashtags were not read in monitoring mode.
- `not-checked` - the end of your list was not read, because `maxResults` hashtags had already been read (monitoring off), or because more than 1,000 hashtags were given. The row says how many.

### Which addresses this Actor requests

TikTok serves the same hashtag data at `m.tiktok.com`, `t.tiktok.com` and `www.tiktok.com`. Measured on 2026-09-14, from Apify's network `www.tiktok.com` answered this request with **an empty response (HTTP 200, 0 bytes) on every attempt**, with and without a proxy, while `m.tiktok.com` answered with the data every time. So each hashtag is requested at `m.tiktok.com` first, then the other two; an empty answer is treated as a failed request and never as a hashtag with no views. Only if all three fail does the run retry them through a proxy (unless `useProxy` is off).

### How fast

Hashtags are read one at a time with a short pause between requests, about 1.3 seconds each. One run reads up to 1,000 hashtags.

### Input

| Field | What it does |
|---|---|
| `hashtags` | The hashtags to read: `booktok`, `#booktok`, or `https://www.tiktok.com/tag/booktok`. If empty, the example hashtags booktok and skincare are read. |
| `maxResults` | How many charged rows to return in total (up to 1,000). |
| `monitoringMode` | Remember the counts and return a hashtag only when they move. |
| `monitorMode` | Views or videos changed, or new videos only. |
| `minViewIncrease` | Ignore view-count movements smaller than this. |
| `resetMonitoringState` | Forget every remembered count and start a fresh baseline. |
| `useProxy` | Retry through a proxy if every TikTok address answers with a bot check or nothing. |

### Questions

**Why is `viewCount` different from a rounded number like 550.7B?**
Because TikTok sends both a rounded and an exact figure. This Actor returns the exact figure as `viewCount` and the rounded one next to it in `viewCountRounded`, so you can see the gap for every hashtag. Which of the two a given TikTok screen displays has not been measured here.

**Why is there no rounded video count?**
Because the rounded video count TikTok sends was 0 in every response measured. Returning it would report hashtags with tens of millions of videos as having none.

**Does monitoring charge me when nothing changed?**
A hashtag whose watched counts have not moved enough is not returned and costs no row price; the run returns a single free `no-change` row. Each hashtag read in monitoring mode costs the check fee ($0.30 per 1,000), moved or not. Large hashtags move on almost every run, so expect a row for them each time.

**What happens to a hashtag that does not exist?**
It comes back as `no-hashtag-found` with TikTok's own code, and it is not charged.

**Can I get the videos inside a hashtag?**
No. This Actor returns one row of counts per hashtag. It does not list videos.

### Example input

```json
{
  "hashtags": ["booktok", "skincare", "tiktokmademebuyit"],
  "monitoringMode": true,
  "monitorMode": "any-change",
  "minViewIncrease": 100000,
  "maxResults": 100
}
```

# Actor input Schema

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

Hashtags to read: booktok, #booktok, or https://www.tiktok.com/tag/booktok. One per line (commas also work). TikTok normalises names itself - 'skin care' is read as #skincare - and every row shows both what you typed and what TikTok returned. The same hashtag is read and charged once per run. If you leave this empty, the example hashtags booktok and skincare are read and every row says so in inputHashtag.

## `maxResults` (type: `integer`):

How many charged rows to return in total when monitoring is off: reading stops once this many hashtags have been read, and a free row says how many entries at the end of the list were not read. In monitoring mode it does not limit the changes returned - every change among the hashtags read comes back. Up to 1,000 hashtags are read per run (about 1.3 seconds each).

## `monitoringMode` (type: `boolean`):

Off = every hashtag you listed comes back with its current counts, charged per row. On = the Actor remembers each hashtag's counts and, on later runs, returns it only when they moved, with the change since the last delivered row (viewCountDelta, videoCountDelta, viewsPerHour). The first run returns every hashtag once to set the baseline. Large hashtags move between almost any two runs (#booktok: +8 videos in about 7 seconds on 2026-09-14), so they come back on almost every run. **In monitoring mode every hashtag read costs $0.30 per 1,000 checks, moved or not** (names TikTok returns nothing for and bot checks are free), plus the row price for the rows returned. The Actor reads only as many hashtags as the run's maximum total charge can pay for with a check and a change row each. Counts are remembered per hashtag; do not put the same hashtag in two schedules that can run at the same time.

## `monitorMode` (type: `string`):

Any change = the hashtag comes back when its video count moves or its view count moves (by at least 'Minimum view change', if set). New videos only = it comes back only when the video count moves. Both compare the exact figures, not the rounded ones.

## `minViewIncrease` (type: `integer`):

In 'any change' mode, ignore view-count movements smaller than this (in either direction). A hashtag whose video count moved still comes back. The comparison is always against the last row that was delivered, so small movements add up until they cross the threshold instead of being lost. 0 = any movement counts.

## `resetMonitoringState` (type: `boolean`):

Clears every remembered count for this Actor, so the next monitoring run returns each hashtag once again. This affects all your monitoring runs, because counts are stored per hashtag rather than per list.

## `useProxy` (type: `boolean`):

Every hashtag is requested directly first, trying TikTok's m.tiktok.com, t.tiktok.com and www.tiktok.com addresses in that order, because from Apify www.tiktok.com answered this request with an empty response on every attempt measured on 2026-09-14 while m.tiktok.com answered with the data. If every address answers with a bot check or nothing, this switches to a proxy session and tries them again. With it off, that answer is returned as a free 'blocked' row.

## Actor input object example

```json
{
  "hashtags": [
    "booktok",
    "skincare"
  ],
  "maxResults": 100,
  "monitoringMode": false,
  "monitorMode": "any-change",
  "minViewIncrease": 0,
  "resetMonitoringState": false,
  "useProxy": true
}
```

# Actor output Schema

## `results` (type: `string`):

One row per TikTok hashtag: the name you typed and the name TikTok resolved it to, with a flag saying whether they match; the challenge id, hashtag URL, description and commerce flag; the exact view count and video count, the average views per video, the rounded view count TikTok sends alongside and the gap between the two; and, in monitoring mode, the previous counts, the change since the last delivered row, the hours in between and the views and videos per hour. Names TikTok returns nothing for, bot checks and invalid names come back as their own free rows; in monitoring mode unchanged hashtags are not returned.

# 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",
        "skincare"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/tiktok-hashtag-stats-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "hashtags": [
        "booktok",
        "skincare",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("neverempty/tiktok-hashtag-stats-scraper").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",
    "skincare"
  ]
}' |
apify call neverempty/tiktok-hashtag-stats-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neverempty/tiktok-hashtag-stats-scraper"
        }
    }
}
```

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/Z0lY8XhuMpVicG6aL/builds/yOg4M8RCwNF6UiPxc/openapi.json
