Tiktok Trending Hashtags Analytics Scraper By Country
Pricing
$19.99/month + usage
Tiktok Trending Hashtags Analytics Scraper By Country
Scrape trending TikTok hashtags with detailed analytics, including views, growth, related tags, and top content insights. This actor delivers structured data for trend tracking, marketing research, and performance analysis in JSON or CSV formats.
Pricing
$19.99/month + usage
Rating
5.0
(1)
Developer
Scraper Engine
Maintained by CommunityActor stats
2
Bookmarked
17
Total users
2
Monthly active users
2 days ago
Last modified
Categories
Share
TikTok Trending Hashtags Scraper — Rank, Views, Industry by Country
TikTok Trending Hashtags Scraper returns TikTok Creative Center's trending-hashtag board as structured JSON: the hashtag, its board rank, TikTok's own video-view and post counts for the window, the industry labels TikTok attaches, a daily popularity curve and the top creators using it. Sweep up to 27 countries and the 7, 30 and 90-day windows in one run, and every row tells you how many of those countries the hashtag is trending in. Nothing is required — open it and press Start.
⚠️ Read the section on what an unconfigured run actually does before you wire this into anything. Nothing in the input schema is required, the Console form and an empty API request produce different runs, and TikTok serves only three hashtags per country and window — so the ceiling on a run is set by how wide you sweep, not by the row cap you type in. All three are explained below.
What is TikTok Trending Hashtags Scraper?
TikTok Trending Hashtags Scraper is an Apify Actor that reads TikTok's own Creative Center trends surface — the page loader behind https://ads.tiktok.com/creative/creativeCenter/trends/hashtag, called with a region and a period query parameter — and shapes each board row into one flat, typed record. It is not a third-party trends aggregator, and it is not TikTok's Research API. One HTTP GET per country-and-window slice, no browser, no pagination.
No TikTok account, login, cookie, ads-account or API key is required, and none is accepted. Requests go out logged-out and, by the Actor's own preference, on a direct connection first. The only credential in play is your Apify token.
It is built for social and content teams tracking what is breaking in specific markets, agencies comparing hashtag momentum across countries, and developers feeding trend signals into dashboards, alerting jobs or agent tools.
What TikTok trending-hashtag data is publicly available to scrape?
TikTok's Creative Center publishes, to any logged-out visitor, a ranked trending-hashtag board per country and per time window, with each hashtag's view and post totals for that window, the industry categories TikTok has classified it under, a daily popularity curve and a short list of creators posting it. What it does not hand a logged-out request is depth: the board stops at three rows per country and window, and nothing pages past it.
| Data Category | Served to a logged-out request | Not served on this route |
|---|---|---|
| Hashtag name and hashtag id | ✅ Public | — |
| Board rank within a country and window | ✅ Public | — |
| Video views and post count for that window | ✅ Public | — |
| Industry ids TikTok has classified the hashtag under | ✅ Public | — |
| Daily popularity curve for the window | ✅ Public | — |
| Top creators on the hashtag — handle, nickname, follower count | ✅ Public | — |
| Rows four and beyond for the same country and window | ❌ | Exactly three rows are served per slice; the route does not page past them |
| Audience age, interest or region breakdowns per hashtag | ❌ | Not on the board route this Actor reads |
| Related hashtags and related videos per hashtag | ❌ | Not on the board route this Actor reads |
| A rank-change or "new on the board" marker | ❌ | No such marker exists in this payload |
TikTok Trending Hashtags Scraper only returns publicly visible data — what any logged-out visitor is served. Nothing behind a login wall.
⚠️ Only 27 country codes are real, and an empty run is not what you think
Three behaviours deserve their own section rather than a footnote in the input table.
Only 27 country codes are real; every other one silently returns the United States board. The single-country dropdown offers 76 values, including ALL. The Actor's own supported list is 27 codes — US FR DE IT ES GB AR AU BR CA CO EG ID IL JP KR MY MX PH SA SG ZA TW TH TR AE VN — and the code's comment records that this was verified against 49 codes. Anything outside that list, ALL included, is accepted, run, and stamped countrySupported: false. The run succeeds and the rows are charged, so check countrySupported on the first row before trusting a market-specific result. A second flag, countryFallbackApplied, compares the country TikTok echoed back against the one you asked for — it is the sharper signal when TikTok echoes a code, but it degrades to false when the echo is absent, so countrySupported is the flag to assert on.
The Console form and an empty API body are different runs. Nothing is required, but two array fields ship a prefill rather than a default: regions prefills to ["US","GB","DE","BR","JP"] and windowDays prefills to ["7","30","90"]. A Console user who opens the Actor and presses Start therefore submits a 15-slice sweep — five countries by three windows. An API caller who posts {} hits the code's own fallback chain instead and gets one slice: the United States, 7-day window, because country falls back to "US" and top100_period falls back to 7. That single-slice run returns three hashtags, every one of them carrying regionSpread: 1 and isLocalTrend: true — which looks like a finding about local trends and is actually just the default. If you call this over the API, set regions or allRegions explicitly.
Two inputs are accepted but have no upstream effect, and they say so on every row. analytics_period offers 7, 30, 120, 365 and 1095 days; curve length is decided entirely by the ranking window, so whatever you pick, every row comes back with analyticsPeriodSupported: false. top100_new_on_board is likewise inert — no "new on board" marker exists in this payload — and every row comes back newOnBoardSupported: false. Both flags are hardcoded false whether or not you touch the fields. They are kept so older run configurations still validate rather than being quietly dropped.
What data can I extract with TikTok Trending Hashtags Scraper?
Every charged row carries six things: which hashtag it is, where it ranked, how much volume TikTok reports for it, what industries TikTok filed it under, how its popularity curve is shaped, and — when you sweep more than one slice — how far across countries and windows it reaches. Sixty-one top-level keys are written on every row, including the nested analytics, dailyTrend and appearances structures. The default dataset view shows 37 of them, so keys such as regionRequested, curveVolatility, industryIdsUnmapped and appearances are in the JSON but not in that table; a second view, Coverage & truth-in-inputs, surfaces the provenance keys instead.
| Field Name | Description |
|---|---|
hashtag_id | TikTok's own hashtag id, as a string. "" when TikTok omits it — the hashtag name is then used as the dedupe key instead |
hashtag_name | The hashtag without a leading #, exactly as published |
hashtagUrl | Canonical tag page, rebuilt as https://www.tiktok.com/tag/{name}. null when the name is missing |
rank | Board position TikTok publishes for this hashtag, coerced to an integer. Lower is better. Taken from the headline slice — see the tie-break rule below |
video_views | TikTok's view total for the window, passed through exactly as served and not retyped |
publish_cnt | TikTok's post count for the window, passed through exactly as served |
videoViewsInt | video_views parsed to an integer, null when it cannot be parsed. Exists because the raw value is not reliably numeric |
publishCountInt | publish_cnt parsed to an integer, null when it cannot be parsed |
viewsPerPost | Actor-derived: videoViewsInt / publishCountInt, rounded to 2 decimal places. null when either side is missing or zero |
regionCode | Country code of the headline slice |
regionName | Human-readable country name for regionCode, from the Actor's own map |
periodDays | Window in days of the headline slice — 7, 30 or 90 |
regionRequested | The country code you actually asked for |
regionUsed | The country code TikTok echoed back in the payload's own filter object, falling back to regionRequested when no echo is present |
countrySupported | true when regionRequested is one of the 27 codes TikTok genuinely serves. The reliable coverage flag |
countryFallbackApplied | true when regionUsed differs from regionRequested |
periodRequested | The window in days you asked for, including a value that does not exist upstream |
periodUsed | The window TikTok actually served |
periodAliasApplied | true when the requested window is not one of 7, 30, 90, or when the served window differs from the requested one |
industryIds | TikTok's industry ids for the hashtag, stringified. Empty array when TikTok labels none |
industryNames | Labels for those ids the Actor has a verified mapping for — ten ids in total |
industryIdsUnmapped | Ids with no known label, emitted raw rather than guessed at |
primaryIndustryId | First entry of industryIds, or null |
primaryIndustryName | First entry of industryNames, or null |
dailyTrend | Array of { "date", "timestamp", "value" } points. date is UTC YYYY-MM-DD derived from the point's epoch; value is TikTok's own popularity figure, unchanged. [] in top100 mode |
trendPointCount | Number of points in dailyTrend |
trendExpectedPoints | The window in days — what a complete curve would contain |
trendComplete | true when trendPointCount >= trendExpectedPoints |
trendDirection | Actor-derived verdict: "rising", "flat" or "fading" — see the rule below. null when the curve has fewer than three points |
momentum | Actor-derived percentage change from the first third of the curve to the last third, rounded to 2 decimal places |
peakDayIndex | Index of the highest value in the curve |
peakDate | The date of the point at peakDayIndex |
daysSincePeak | How many curve points fall after the peak |
curveVolatility | Actor-derived coefficient of variation — standard deviation over mean, 4 decimal places. null when the mean is not positive |
topCreatorHandles | Handles of the creators TikTok lists on this hashtag. Entries with no handle are skipped. [] in top100 mode |
topCreatorNicknames | Display names of the same creators, positionally aligned; an entry is null when TikTok omits the nickname |
topCreatorFollowers | Follower counts of the same creators, parsed to integers |
topCreatorCount | How many creators TikTok listed |
regionSpread | Actor-derived: how many distinct countries this hashtag appeared in across the sweep. null when the rollup is off |
regionsTrendingIn | Those country codes, in best-rank-first order |
regionNamesTrendingIn | The same list as human-readable names |
windowsTrendingIn | Sorted list of the windows this hashtag appeared in |
isGlobalTrend | Actor-derived: true when regionSpread is 3 or more |
isLocalTrend | Actor-derived: true when regionSpread is exactly 1 |
bestRank | Actor-derived: the lowest rank this hashtag reached across every slice it appeared in |
bestRankRegion | The country that best rank came from |
bestRankPeriod | The window that best rank came from |
sliceCount | How many country-and-window slices were merged into this row. Written even when the rollup is off |
appearances | Per-slice breakdown, one object per appearance: regionCode, regionName, periodDays, rank, videoViews, publishCount. [] when the rollup is off |
analytics | The original-format blob: video_views, publish_cnt, rank (TikTok's raw value), industry_ids, trend (TikTok's popularity curve untouched) and creators (TikTok's creator objects untouched). {} in top100 mode |
analyticsPeriodSupported | Always false — the analytics_period input has no upstream consumer |
newOnBoardSupported | Always false — TikTok publishes no "new on board" marker on this route |
industryFilterApplied | true only when at least one industry you selected mapped to a known id and the filter actually ran |
industryFilterMode | "client_side" when you selected any industry, null when you selected none |
industryFilterUnsupported | true when at least one industry you selected has no id TikTok has ever attached to a live row |
boardRowsPerSlice | 3 — how many hashtags TikTok serves per country and window |
rowType | "hashtag" on every charged row, "accounting" on the uncharged diagnostic row. This is the row-type marker to filter on |
sourceUrl | The loader URL rebuilt from regionRequested and periodRequested — what you asked for, not necessarily what was fetched. For an aliased window it names the window you requested |
scrapedAt | UTC ISO-8601 timestamp for when the row was built |
errorReason | Typed failure reason on an accounting row. Present and null on every charged row, not omitted |
errorMessage | Full explanation on an accounting row, null on every charged row |
Hashtag identity, rank and provenance
hashtag_name, hashtag_id and hashtagUrl identify the tag; rank, regionCode and periodDays say where that rank came from. When a hashtag appears in several slices and dedupeAcrossSlices is on, one slice becomes the headline and supplies those values: the slice with the lowest rank, with ties broken in favour of the shorter window so the freshest board wins. Every other appearance is still preserved in appearances, so nothing is thrown away by the merge.
regionRequested and regionUsed are deliberately separate, as are periodRequested and periodUsed. The first of each pair is what you asked for; the second is what TikTok served. Compare them rather than trusting your own input — that pair, plus countrySupported and periodAliasApplied, is the whole coverage story for a row.
Volume, industry and derived metrics
video_views, publish_cnt and rank are TikTok's own published figures for that country and window, passed through untouched. videoViewsInt, publishCountInt and viewsPerPost are the Actor's typed derivations on top of them, and bestRank is the Actor's cross-slice selection, not a figure TikTok publishes. There is no lifetime or all-time volume field on this route — the counts are scoped to the window you asked for.
Industry classification comes from TikTok's own ids on each board row, which the Actor maps to labels for ten ids it has verified: Education, Financial Services, Beauty & Personal Care, Travel, Pets, Apparel & Accessories, News & Entertainment, Games, Food & Beverage, and Sports & Outdoor. Any id outside that ten lands in industryIdsUnmapped as a raw string rather than being guessed at, so industryNames never carries an invented label.
Trend curve, momentum and top creators
dailyTrend is TikTok's popularity curve reshaped into one dated point per entry; the value is TikTok's own figure and is not rescaled. trendDirection and momentum are computed from it by the Actor: the mean of the last third of the curve is compared against the mean of the first third, expressed as a percentage, and a hashtag is called rising at momentum >= 15, fading at momentum <= -15 and flat in between. A curve with fewer than three points returns null for the whole shape block rather than a fabricated verdict.
topCreatorHandles, topCreatorNicknames and topCreatorFollowers are three positionally aligned arrays built from the creator objects TikTok attaches to the board row, and analytics.creators carries those same objects verbatim. All four are empty in top100 mode.
Cross-country rollup and coverage flags
regionSpread is the field this Actor exists for. A hashtag on the board in nine countries at once is a different signal from one on the board in a single market, and no per-country scrape can tell you which you are looking at. regionsTrendingIn, windowsTrendingIn, isGlobalTrend, isLocalTrend and appearances are the detail behind that number, and bestRank / bestRankRegion / bestRankPeriod tell you where it performed best. Turn includeRegionRollup off and these keys are still written — as null and empty arrays, never dropped — so the schema stays the same shape either way.
The six flag keys — analyticsPeriodSupported, newOnBoardSupported, industryFilterApplied, industryFilterMode, industryFilterUnsupported, boardRowsPerSlice — are identical on every row of a run. They describe how the run was configured and what the upstream route honoured, so a stored dataset stays self-describing months later.
🤖 Add-on: Need additional TikTok data?
Trending hashtags tell you what is rising; they do not tell you who is riding it well. TikTok Profile Scraper takes a handle out of topCreatorHandles and returns that creator's full post feed with per-post engagement, and Tiktok Video Scraper (Creator Profile & Contact) works from individual video URLs when you want the creator and their public contact details attached. For the conversation under a trending post, TikTok Comments Scraper By Real Comment Filter goes a level deeper.
Why not build this yourself?
One URL, one query string, three rows. The reason this is not an afternoon's work is that the route fails while returning HTTP 200, and the input surface is full of values that look real and are not.
The Creative Center page loader is TLS-fingerprint gated. A plain HTTP client — including a browser automation framework's own request API, which uses its runtime's network stack rather than a browser's — receives HTTP 200 with a 49-byte empty payload: a dehydratedState carrying no queries at all. Nothing about that response says "blocked". It reads as "no trending hashtags today", which is the wrong conclusion and the expensive one. This Actor sends its requests through curl_cffi with a Chrome impersonation profile, treats any 200 under 200 bytes as a transport refusal rather than an empty board, and runs a preflight request before it pushes anything so the failure surfaces at the start of a run instead of as a silently empty dataset.
Then the input traps. The country parameter accepts dozens of codes that return the United States board without complaint. A 120-day window is accepted and silently served as 7 days. Half the industry categories in TikTok's own filter vocabulary are never attached to a live board row, so filtering on one of them cannot match anything. The route reads region, not the countryCode an older integration would send. And the three-rows-per-slice ceiling means any tool promising depth from a single request is promising something the surface does not serve.
TikTok's Creative Center is a public marketing surface rather than a documented API, and TikTok's Research API — the closest official route to this kind of data — is application-gated and oriented to academic and non-profit use. Check TikTok's current published developer documentation if you want the supported path; those terms change and no quota or rate figure is reproduced here.
How to use TikTok Trending Hashtags Scraper
TikTok Trending Hashtags Scraper runs on Apify. Start it from the Apify Console or call it through the Apify API — the only credential involved is your Apify token.
- Open TikTok Trending Hashtags Scraper on Apify and click Try for free
- Pick your countries. Countries to sweep (
regions) is the field to use — it is a closed list of the 27 codes TikTok genuinely serves. Sweep all 27 supported countries (allRegions) is the one-click version, and Country preset (regionPreset) offers ready-made bundles - Pick your windows in Time windows to sweep (
windowDays) — 7, 30 and 90 days each return a genuinely different board, and 90 is the deepest one this route serves - Choose a Result type (
result_type): ranking only, ranking plus the curve and creators, or a custom hashtag list - Optionally narrow with Keep only these industries (
industryIn) and raise Max hashtags to return (total_hashtags) above its default of 10 - Click Start, then export the dataset as JSON, CSV or Excel, or read it through the Apify API
What an empty run does, exactly. With no input at all the Actor resolves result_type to top100_with_analytics, finds no regions, no preset and no allRegions, falls back to country — which itself falls back to "US" — finds no windowDays and falls back to top100_period, which falls back to 7. That is one slice: the United States, 7-day board, capped at 10 rows, rollup on, dedupe on, eight-way concurrency, direct transport first. TikTok serves three hashtags for that slice, so you get three rows. Remember that the Console form arrives prefilled with five countries and three windows, so pressing Start there is a 15-slice run, not this one.
How to scale to bulk hashtag extraction
Bulk here means slices, not pages. A run fetches one request per country-and-window pair, so the maximum sweep is 27 countries by 3 windows — 81 slices — reached with allRegions: true and windowDays: ["7","30","90"]. Requests run in parallel up to sweepConcurrency, which the code clamps to 1–12 and defaults to 8. Because TikTok serves exactly three rows per slice, the number of unique hashtags a run can possibly return is bounded by how wide you sweep and not by total_hashtags; the shipped input schema records a full sweep yielding roughly 150 unique hashtags, so a cap above that simply returns everything the sweep found. Failed slices do not stop the run — they are logged and skipped while the rest continue.
What can you do with TikTok trending hashtag data?
- 🌍 A social media manager planning a multi-market campaign runs
allRegionsacross all three windows and sorts onregionSpreadandisGlobalTrendto separate hashtags worth a global brief from the ones that only make sense in one country. - 📈 A content strategist deciding what to shoot this week filters on
trendDirection: "rising"and lowdaysSincePeak, then readsviewsPerPostto prefer tags where the audience is actually watching rather than just posting. - 🎮 A category marketer at a games or beauty brand sets
industryInto their vertical and tracksbestRankandmomentumfor those hashtags run over run, usingindustryFilterUnsupportedto confirm the filter really engaged. - 🌏 A market-entry researcher sweeps a
regionPresetbundle such as APAC or MENA, readsregionsTrendingInandappearancesto see which tags cross borders inside that bloc, and checkscountrySupportedbefore reporting any single market. - 👤 A creator-partnerships lead pulls
topCreatorHandlesandtopCreatorFollowersfrom the rising hashtags in a target country and feeds those handles straight into a creator-vetting run. - 🤖 An AI engineer building a trend-briefing agent indexes
hashtag_name,primaryIndustryNameandregionNamesTrendingIninto a vector store withperiodDays,trendDirectionandscrapedAtas metadata filters, so the agent answers "what was rising in Brazil last week" from observed rows rather than stale training data.
Every one of these is callable from an agent framework over the Apify API, since the Actor is a standard HTTP-triggered run.
How does TikTok Trending Hashtags Scraper handle rate limits and blocking?
Transport is curl_cffi with a Chrome TLS-and-HTTP fingerprint impersonation profile — chrome131. That is not a nicety: the loader route answers a non-impersonated client with an empty 200, so a conventional HTTP client returns zero rows no matter how many times it retries. There is no headless browser anywhere in the flow, and no login, cookie or session token.
The Actor prefers the cheapest rung. Every run starts on the container's direct connection and only escalates to your Apify proxy configuration if a preflight request fails there. That preflight is a single United States, 7-day request made before anything is pushed; if it fails on direct it is retried through the proxy, and if both fail the run writes one uncharged diagnostic row explaining that the failure was a transport refusal and not a consequence of the country or window you picked, then exits. If no usable proxy configuration can be built at all, the Actor warns and continues direct rather than failing.
Each slice gets up to 3 attempts with exponential backoff — roughly one second, then two — and a 45-second per-request timeout. A response counts as good only when it is a 200 carrying more than 200 bytes and parsing as JSON; anything smaller is recorded as empty_payload_<N>B and retried, never reported as an empty board. There is no CAPTCHA solving in this Actor, and none is claimed. When individual slices fail, the run continues with the rest and the failures are named in the log; only when every slice comes back without rows does the run write an uncharged accounting row instead of data.
⬇️ Input
Seventeen parameters, none of them required, read straight from the input schema as shipped. Precedence matters more than usual here, so the resolution order is stated under the table.
| Parameter | Required | Type | Description | Example Value |
|---|---|---|---|---|
result_type | No | string | Output shape. Enum: top100 (ranking only — analytics stays {}, no curve, no creators), top100_with_analytics (ranking plus curve and creators), analytics (keep only the hashtags named in hashtag_list). An unrecognised value logs a warning and reverts to the default. Default "top100_with_analytics". | "top100_with_analytics" |
country | No | string | Single-country mode. Closed enum of 76 values, of which 27 are genuinely served by TikTok; the rest, ALL included, return the United States board and are flagged. Used only when regions, regionPreset and allRegions are all untouched. Default "US". | "BR" |
regions | No | array | Multi-select over the 27 supported country codes (select editor). Values are uppercased and de-duplicated. Overrides regionPreset and country. Default [], but the Console form prefills ["US","GB","DE","BR","JP"]. | ["US", "GB", "JP"] |
regionPreset | No | string | Ready-made bundles built from the 27 supported codes. Enum: none, all_supported (27), anglo (4), europe (5), latam (4), apac (10), mena (5). Ignored when regions is filled. Default "none". | "apac" |
allRegions | No | boolean | Sweep all 27 supported countries. Overrides regions, regionPreset and country. Default false. | true |
windowDays | No | array | Time windows to sweep (select editor). Enum: 7, 30, 90 — the three windows this route actually serves. Overrides top100_period. Default [], but the Console form prefills ["7","30","90"]. | ["7", "30", "90"] |
top100_period | No | string | Single-window mode, used only when windowDays is empty. Enum: 7, 30, 90, 120. 120 does not exist upstream — the 7-day board is served for it and rows are flagged. Default "7". | "30" |
analytics_period | No | string | Legacy. Enum: 7, 30, 120, 365, 1095. Has no upstream consumer; curve length follows the ranking window. Every row reports analyticsPeriodSupported: false regardless. Default "30". | "30" |
industryIn | No | array | Keep only hashtags TikTok labelled with these industries (select editor). Enum of 20 labels, of which 10 map to ids the Actor has verified; the other 10 match nothing. Client-side filter applied after the sweep. Overrides top100_industry. Default []. | ["Games", "Sports & Outdoor"] |
top100_industry | No | string | Single-industry version of the above, same 20-value enum plus "" for all industries. Used only when industryIn is empty. Default "". | "Beauty & Personal Care" |
top100_new_on_board | No | boolean | Legacy. Not honoured upstream — no such marker exists in the payload. Every row reports newOnBoardSupported: false regardless. Default false. | false |
hashtag_list | No | array | Hashtag names without # (stringList editor). Only consulted when result_type is analytics; in the other two modes it is read and never used. Matched case-insensitively against the whole sweep, not one board. Default []. | ["worldcup", "summervibes"] |
total_hashtags | No | integer | Cap on rows written to the dataset. Minimum 1, maximum 500, default 10. Clamped in code rather than rejected: 0 or a non-numeric value becomes 10, a negative becomes 1, and anything above 500 becomes 500. Applied after sorting. | 150 |
includeRegionRollup | No | boolean | Add the cross-country rollup values. When false the rollup keys are still written, as null and empty arrays. Default true. | true |
dedupeAcrossSlices | No | boolean | true merges every appearance of a hashtag into one row keyed on its id; false writes one row per hashtag per slice. Default true. | true |
sweepConcurrency | No | integer | How many country-and-window requests run at once. Minimum 1, maximum 12, default 8. Clamped in code; 0 or a non-numeric value becomes 8. | 8 |
proxyConfiguration | No | object | Apify Proxy settings (proxy editor). Default {"useApifyProxy": true}. The Actor still attempts a direct connection first and uses the proxy only if the direct preflight fails. | {"useApifyProxy": true} |
Resolution order, which decides what a run actually fetches.
- Countries, first match wins:
allRegions→regions→regionPreset→country. A lower field is not merged in, it is ignored entirely. - Windows, first match wins:
windowDays→top100_period. - Industries, first match wins:
industryIn→top100_industry. - Countries and windows are server-side — they become the
regionandperiodquery parameters and decide which slices get fetched. Industries andhashtag_listare client-side, applied to the merged rows afterwards. The two are fully independent: neither overrides the other, and the industry filter runs first, then the hashtag list, then the sort, then the cap.
Four more things the schema alone does not show:
- Selecting only an unlabelled industry returns everything, not nothing. Ten of the twenty enum entries are labels TikTok has never attached to a live board row. If every industry you pick is one of those, no ids match, the filter never runs, and you get the full unfiltered sweep with
industryFilterUnsupported: trueandindustryFilterApplied: false. Assert onindustryFilterAppliedbefore treating the result as filtered. Mixing a supported label with an unsupported one filters on the supported one and sets both flags. total_hashtagscuts the tail of a sorted list, not the tail of the fetch order. Rows are sorted byregionSpreaddescending, then bybestRankascending, and only then truncated. A cap of 10 gives you the 10 widest-spread hashtags in the sweep, which is usually what you want and is not the first 10 the board returned.- De-duplication is per run and per hashtag id. With
dedupeAcrossSliceson, the key ishashtag_id, falling back to the lowercasedhashtag_namewhen TikTok omits the id. With it off, the key is the hashtag plus the served country plus the served window. Nothing is de-duplicated across separate runs. hashtag_listnames that never appeared are reported, not silently dropped. Inanalyticsmode the Actor logs every requested name that was not on any board in the sweep, along with the advice to widen the sweep. Those names produce no rows.
Example input
A five-country, three-window sweep restricted to two industries, with the full ranking-plus-analytics output shape. Because regions and windowDays are filled, country, regionPreset and top100_period are ignored; because industryIn is filled, top100_industry is ignored.
{"result_type": "top100_with_analytics","country": "US","regions": ["US", "GB", "DE", "BR", "JP"],"regionPreset": "none","allRegions": false,"windowDays": ["7", "30", "90"],"top100_period": "7","analytics_period": "30","industryIn": ["News & Entertainment", "Sports & Outdoor"],"top100_industry": "","top100_new_on_board": false,"hashtag_list": [],"total_hashtags": 50,"includeRegionRollup": true,"dedupeAcrossSlices": true,"sweepConcurrency": 8,"proxyConfiguration": { "useApifyProxy": true }}
⬆️ Output
Typed, normalized JSON with a consistent shape across runs — one row per unique hashtag by default, 61 top-level keys on every row, with the nested analytics, dailyTrend and appearances structures carried alongside flat mirrors of their most-used values. Export as JSON, CSV or Excel from the Console, or read the dataset through the Apify API. Two dataset views ship with the Actor: a flat Trending hashtags table of 37 columns, and a Coverage & truth-in-inputs view that puts the requested-versus-served pairs, the support flags and appearances side by side.
On charging and uncharged rows. Each hashtag row is pushed with one row_result charged event. Diagnostic rows are pushed with no charged event at all, and the marker that separates them is rowType — "hashtag" on charged rows, "accounting" on uncharged ones. Unlike some sibling Actors, errorReason is present on both shapes here; on a charged row it is simply null. Either check works, and both are shown below:
items = list(client.dataset(run["defaultDatasetId"]).iterate_items())hashtags = [r for r in items if r["rowType"] == "hashtag"]problems = [r for r in items if r.get("errorReason")]
An accounting row carries the same 61 keys with every data field nulled or emptied, so it never breaks a consumer expecting a fixed schema. The errorReason values the Actor emits are engine_preflight_failed (the loader refused both transports before anything was fetched), board_empty (every requested slice came back without rows even though the preflight succeeded), filtered_empty (the sweep found hashtags but your industry or hashtag-list filters removed all of them), engine_dead and unexpected_error. The first three end the run immediately and are the only row in the dataset; the last two are caught from the wider run and can land after some hashtag rows were already written.
Example output
One charged row, from a sweep where this hashtag was on the board in nine countries. The dailyTrend, analytics.trend, analytics.creators and appearances arrays are shortened here for readability — a real row carries one entry per curve point and one per slice appearance, as the counts on the row say:
{"hashtag_id": "5673","hashtag_name": "worldcup","video_views": "412870934","publish_cnt": "86214","rank": 1,"analytics": {"video_views": "412870934","publish_cnt": "86214","rank": 1,"industry_ids": ["23000000000", "28000000000"],"trend": [{ "timestamp": 1752710400, "value": 0.34 },{ "timestamp": 1752796800, "value": 0.51 },{ "timestamp": 1752883200, "value": 0.88 }],"creators": [{ "handleName": "goalzone.br", "nickname": "Goal Zone", "followedCnt": "2418003" }]},"rowType": "hashtag","hashtagUrl": "https://www.tiktok.com/tag/worldcup","regionCode": "BR","regionName": "Brazil","periodDays": 7,"regionRequested": "BR","regionUsed": "BR","countrySupported": true,"countryFallbackApplied": false,"periodRequested": 7,"periodUsed": 7,"periodAliasApplied": false,"videoViewsInt": 412870934,"publishCountInt": 86214,"viewsPerPost": 4789.36,"industryIds": ["23000000000", "28000000000"],"industryNames": ["News & Entertainment", "Sports & Outdoor"],"industryIdsUnmapped": [],"primaryIndustryId": "23000000000","primaryIndustryName": "News & Entertainment","dailyTrend": [{ "date": "2026-07-17", "timestamp": 1752710400, "value": 0.34 },{ "date": "2026-07-18", "timestamp": 1752796800, "value": 0.51 },{ "date": "2026-07-19", "timestamp": 1752883200, "value": 0.88 }],"trendPointCount": 7,"trendExpectedPoints": 7,"trendComplete": true,"trendDirection": "rising","momentum": 63.41,"peakDayIndex": 6,"peakDate": "2026-07-23","daysSincePeak": 0,"curveVolatility": 0.3812,"topCreatorHandles": ["goalzone.br", "futeboldaily", "copa.moments"],"topCreatorNicknames": ["Goal Zone", "Futebol Daily", "Copa Moments"],"topCreatorFollowers": [2418003, 883410, 512977],"topCreatorCount": 3,"regionSpread": 9,"regionsTrendingIn": ["BR", "AR", "CO", "MX", "ES", "IT", "FR", "SA", "JP"],"regionNamesTrendingIn": ["Brazil", "Argentina", "Colombia", "Mexico", "Spain","Italy", "France", "Saudi Arabia", "Japan"],"windowsTrendingIn": [7, 30, 90],"isGlobalTrend": true,"isLocalTrend": false,"bestRank": 1,"bestRankRegion": "BR","bestRankPeriod": 7,"sliceCount": 17,"appearances": [{"regionCode": "BR","regionName": "Brazil","periodDays": 7,"rank": 1,"videoViews": "412870934","publishCount": "86214"},{"regionCode": "AR","regionName": "Argentina","periodDays": 7,"rank": 2,"videoViews": "88104227","publishCount": "19442"}],"analyticsPeriodSupported": false,"newOnBoardSupported": false,"industryFilterApplied": true,"industryFilterMode": "client_side","industryFilterUnsupported": false,"boardRowsPerSlice": 3,"sourceUrl": "https://ads.tiktok.com/creative/creativeCenter/trends/hashtag?__loader=creativeCenter%2Ftrends%2F%28tab%29%2Fpage&__ssrDirect=true®ion=BR&period=7","scrapedAt": "2026-07-25T09:41:07.482913+00:00","errorReason": null,"errorMessage": null}
And the uncharged accounting row written when filters removed everything the sweep found — abbreviated to the keys that carry a value, though all 61 are present:
{"hashtag_id": null,"hashtag_name": null,"rank": null,"analytics": {},"rowType": "accounting","industryIds": [],"appearances": [],"sliceCount": 0,"analyticsPeriodSupported": false,"newOnBoardSupported": false,"industryFilterApplied": true,"industryFilterMode": "client_side","industryFilterUnsupported": false,"boardRowsPerSlice": 3,"scrapedAt": "2026-07-25T09:41:12.006841+00:00","errorReason": "filtered_empty","errorMessage": "The sweep returned hashtags but every one of them was removed by your filters (industryIn=['Healthcare'], hashtag_list=None). Nothing was charged."}
How does it work?
For each country-and-window pair you select, the Actor builds one Creative Center loader URL, adds region and period as query parameters, and issues a single GET through a Chrome-impersonated TLS session. Those requests run in parallel up to your concurrency setting, with a preflight request proving the transport works before anything is pushed. There is no headless browser, no JavaScript execution, no login and no cookie jar.
Each response is parsed by walking the page's own dehydratedState for the query whose key identifies the popular-hashtag board, then reading its pages. That means the values come from TikTok's own JSON rather than from CSS selectors, so a Creative Center front-end redesign generally does not change your field names. The payload's echoed filter object is read back too, which is how the Actor knows the country and window TikTok actually served versus the ones you asked for.
Every board row from every slice is then grouped by hashtag, merged into one record with the best-ranked slice as its headline, enriched with the derived curve and rollup values, filtered, sorted and capped. Only publicly visible data is returned, and the output key names stay the same whatever TikTok does to its layout.
Integrations
TikTok Trending Hashtags Scraper is an Apify Actor, so it works with anything that can call the Apify API or read a dataset.
Calling TikTok Trending Hashtags Scraper from Python
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("<YOUR_USERNAME>/tiktok-trending-hashtags-by-country-industry").call(run_input={"regions": ["US", "GB", "BR", "JP"],"windowDays": ["7", "30"],"result_type": "top100_with_analytics","total_hashtags": 100,})for row in client.dataset(run["defaultDatasetId"]).iterate_items():if row["rowType"] != "hashtag":print("diagnostic:", row["errorReason"], row["errorMessage"])continueprint(row["hashtag_name"], row["bestRank"], row["regionSpread"], row["trendDirection"])
Works in Go, Ruby, Node.js, cURL — any language that can make an HTTP request. Check rowType first so a diagnostic row never reaches your reporting code.
No-code tools (n8n, Make, LangChain)
In n8n, use the Apify node — or an HTTP Request node pointed at the Actor's run endpoint with your token — and pass the same JSON input shown above; an IF node on rowType keeps accounting rows out of the branch that writes to your sheet, and a Filter node on isGlobalTrend reduces a full sweep to the cross-border trends in one step. In Make, the Apify module supports run-and-wait, so a daily trend pull can feed a Google Sheets, Airtable or Slack step directly. In LangChain, wrap the run endpoint as a tool and hand the rows to the model unchanged, since they are already typed JSON. Apify schedules and webhooks cover recurrence, which is how momentum and daysSincePeak become comparable day over day.
Is it legal to scrape TikTok trending hashtags?
Scraping publicly accessible trend listings is broadly treated as permissible where no authentication is bypassed, and this Actor reads only a logged-out public marketing surface — no account, no cookie, no session.
Most of this output is non-personal. A hashtag name, a board rank, view and post totals, an industry id, a country code, a window length and a popularity curve are aggregate platform signals about content categories, not records about identifiable people. GDPR and equivalent personal-data regimes do not attach to those fields the way they would to a profile scraper, and no lawful-basis analysis is needed to store a table of hashtag ranks.
Four fields are the exception, and they are easy to isolate. In top100_with_analytics and analytics modes the row carries topCreatorHandles, topCreatorNicknames, topCreatorFollowers and the raw creator objects inside analytics.creators. Those name identifiable individuals and describe their audience size, so if you retain them in bulk you are processing personal data and should treat them under GDPR, UK GDPR or CCPA like any other creator dataset — lawful basis, minimisation, retention period. If you do not need them, run result_type: "top100": in that mode all three arrays come back empty, topCreatorCount is null and analytics is {}, so the output contains no personal data at all. One smaller nuance: hashtag_name is free text and can incidentally contain a person's name because that is what was trending.
What applies primarily to the rest is contractual and database-related: TikTok's terms of service, its rights in the underlying trend data, and database or unfair-competition rules covering systematic reuse of a compiled dataset.
Consult legal counsel if your use case involves bulk storage of the creator fields, or republishing this data as a competing trends product.
❓ Frequently asked questions
What TikTok trending-hashtag fields does TikTok Trending Hashtags Scraper return?
The five most used are hashtag_name, rank, video_views, regionSpread and trendDirection. Every charged row carries 61 top-level keys in total, including the nested analytics, dailyTrend and appearances structures — see the data fields table above for all of them.
Where does the trending-hashtag data actually come from?
From TikTok's own Creative Center trends surface — the page loader behind https://ads.tiktok.com/creative/creativeCenter/trends/hashtag, called with a region and a period query parameter. Not a third-party trends aggregator, and not TikTok's Research API. That matters for two reasons: the available countries and windows are whatever that surface publishes, not whatever TikTok exposes elsewhere; and sourceUrl on every row records the exact slice request behind it, so any figure is traceable back to something you can check yourself.
Does TikTok Trending Hashtags Scraper require a TikTok account, ads account or API key?
No. There is no login, password, cookie or token anywhere in the input schema or the code, and none can be supplied. The Creative Center trends board is served to anonymous requests, which is why the Actor tries a direct connection before it touches a proxy. The only credential you need is your Apify token. The trade-off is that anything TikTok serves only to a signed-in Creative Center session is out of reach by design.
How many hashtags can I get in one run?
That is set by how wide you sweep, not by the row cap. TikTok serves exactly three hashtags per country and window on this route — the number is stamped on every row as boardRowsPerSlice — and nothing pages past it. The widest possible run is 27 supported countries by 3 real windows, which is 81 slices; the shipped input schema records a full sweep of that size yielding roughly 150 unique hashtags after de-duplication. total_hashtags accepts up to 500, so setting it far above what the sweep can find simply returns everything found. To get more hashtags, add countries and windows — not a bigger cap.
What happens when a country and window combination has no trends available?
You are never left guessing, and one empty slice does not end the run. A slice that fails or returns no rows is logged by name with its reason and skipped, while the remaining slices continue and the run charges normally for whatever it found. Only when every requested slice comes back without rows does the Actor write a single uncharged accounting row with errorReason: "board_empty", whose message states the preflight byte count so you can see the transport was working and the emptiness was real. Two neighbouring cases have their own reasons: if the loader refuses both the direct and proxy transports before anything is fetched you get engine_preflight_failed, whose message says explicitly that the failure is a transport refusal and not a consequence of your country or window; and if the sweep found hashtags but your industry or hashtag-list filters removed all of them you get filtered_empty, naming the filters that emptied it. All three are uncharged. Filter them out with row["rowType"] == "hashtag".
Can I scrape multiple countries and time windows at once?
Yes, and that is the intended mode. regions takes any subset of the 27 supported country codes, allRegions takes all of them in one click, regionPreset offers ready-made bundles, and windowDays takes any combination of the 7, 30 and 90-day boards. Countries and windows multiply out into a slice grid, so five countries by three windows is 15 requests and the full sweep is 81. With dedupeAcrossSlices on, all of those collapse into one row per unique hashtag with regionSpread, windowsTrendingIn and appearances recording where it showed up; turn it off if you want the raw per-country boards instead.
Am I charged for runs that return nothing?
No. Only hashtag rows are pushed with the row_result charged event. Every diagnostic row — preflight failure, empty board, everything filtered out — is pushed with no charged event at all and marked rowType: "accounting". Because that key is present on both shapes, row["rowType"] == "hashtag" is the clean filter; row.get("errorReason") is None works too, since errorReason is null on every charged row.
Does TikTok Trending Hashtags Scraper work with Claude, ChatGPT and other AI agent tools?
Yes. It is callable as a standard HTTP-triggered Apify run, so LangChain, CrewAI, n8n or a hand-written tool definition can start it and read back typed JSON with no parsing step. Give the agent one rule: skip rows where rowType is not "hashtag", because those are uncharged diagnostics rather than trends.
How does TikTok Trending Hashtags Scraper compare to other TikTok trending-hashtag scrapers?
All observations below were checked on the Apify Store on 25 July 2026, and describe what each listing documents — not measurements of our own.
codebyte/tiktok-trending-hashtags-analytics is the most used in this category and the origin of the shared input contract: its README documents the same result_type values (top100, top100_with_analytics, analytics), plus country, top100_period, analytics_period and a hashtags list, and describes retrieving "the top 100 trending hashtags for a given country and time frame". Its published output sample carries fields this Actor does not return — country_info, industry_info, is_promoted, rank_diff_type, rank_change_readable, period — and its analytics blob documents publish_cnt_all, video_views_all, longevity, audience_interests, audience_countries, related_hashtags, related_items and audience_ages_readable. Those audience-demographic and related-content fields are a genuine trade-off: this Actor returns none of them. Its documented input is one country per run.
api-empire/tiktok-trending-hashtags-analytics-scraper and scrapeflow/tiktok-trending-hashtags-analytics-scraper publish byte-identical READMEs. Both document "up to 500 hashtags per run", "40+ countries", periods of 7, 30 and 120 days, "19+ industries" with fuzzy matching, a Direct → Datacenter → Residential proxy chain, and a "Performance Snapshot" table giving times per hashtag volume. Those figures are their listings' claims and are reproduced here as claims, not endorsed. Their documented output sample is the same country_info / industry_info / is_promoted / rank_diff_type / rank_change_readable shape as codebyte's.
Where this Actor differs, observably: countries and time windows sweep together in one run rather than one country per run; the 90-day board, which none of the three lists among its periods; a documented cross-slice rollup — regionSpread, regionsTrendingIn, windowsTrendingIn, bestRank, appearances — that no listing above documents; per-input coverage flags (countrySupported, periodAliasApplied, analyticsPeriodSupported, newOnBoardSupported, industryFilterUnsupported) that state plainly when an input was not honoured upstream; derived trend metrics trendDirection, momentum, daysSincePeak, curveVolatility and viewsPerPost; and an uncharged-row contract with a single marker key. It also states its own upstream ceiling up front — three rows per country and window — where the three listings above describe a top-100 ranking. These are field-list and documentation differences you can verify yourself, not performance claims.
Does TikTok Trending Hashtags Scraper return data in a format LLMs can use directly?
Yes. Typed, normalized JSON with consistent field names across runs — no HTML parsing, no selectors. Pass a row straight into an LLM context window, index it into a vector store, or hand it to an agent tool. Two things to map deliberately: the base contract keys are snake_case (hashtag_name, video_views, publish_cnt) while everything added on top is camelCase, and several values are mirrored between the flat row and the analytics blob, so drop one side of each pair before indexing.
What happens when TikTok changes its Creative Center layout or anti-bot system?
The scraper is maintained, and because it reads TikTok's own dehydratedState JSON rather than rendered markup, a Creative Center redesign generally does not affect your field names or types. Fingerprint pressure is the live surface, and it is handled explicitly rather than hopefully: a 200 response under 200 bytes is treated as a transport refusal and retried, never reported as an empty board, and a preflight request proves the transport before any row is pushed so a gate surfaces at the start of a run instead of as a silently empty dataset. Industry ids the Actor has not verified are emitted raw in industryIdsUnmapped rather than guessed at, so a new TikTok category degrades one field instead of shipping a wrong label.
Can I use TikTok Trending Hashtags Scraper without managing proxies or browser infrastructure?
Yes, and there is nothing to configure. There is no browser at all — the Actor uses an impersonated HTTP session, which is why runs are lighter than a headless-Chromium scraper. Proxying is cheapest-first and automatic: every run starts direct and only falls back to your Apify proxy configuration if the direct preflight fails, and if no proxy configuration can be built the run warns and continues direct. You never install a browser, create a proxy account or rotate an IP. The Actor does not solve CAPTCHAs and makes no claim to.
Which TikTok trending-hashtag fields work best for AI training data and RAG indexing?
For RAG, hashtag_name is the high-information text field and it chunks best paired with primaryIndustryName, regionNamesTrendingIn and periodDays, so a retrieved chunk carries its own category, markets and window instead of floating free. For training or modelling, the structurally consistent fields across every record are rank, bestRank, videoViewsInt, publishCountInt, viewsPerPost, regionSpread, momentum, daysSincePeak and trendComplete — all typed primitives. Presence-check before feeding any of them to a model: the whole curve block is null when TikTok returns fewer than three points, viewsPerPost is null when either input is zero, and every creator and curve field is empty in top100 mode.
🔗 Related scrapers
| Scraper Name | What it extracts |
|---|---|
| TikTok Trending Hashtags Scraper By Multiple Countries | Trending hashtags swept across a much longer country list in a single time window, with cross-country spread thresholds |
| TikTok Profile Scraper | A creator's full public post feed — views, likes, captions, sounds and video URLs |
| TikTok Comments Scraper By Real Comment Filter | Comment threads under TikTok posts, filtered to real comments |
| Tiktok Video Scraper (Creator Profile & Contact) | Single-video data with the creator's profile and public contact details attached |
| Twitter Trends Scraper | Twitter/X trending topics by country, with hourly rewind snapshots |
| Reddit Trends Scraper By Sort, Time & Score Filter | Trending Reddit posts with sort, time-window and score filters |
Which of the two TikTok hashtag sweepers you want. They read the same Creative Center board and are built for different questions. Use this Actor when the axis you care about is depth per market: it sweeps countries and the 7, 30 and 90-day windows together in one grid, filters by the industries TikTok labels, and returns the daily popularity curve, momentum verdict and top creators, plus a coverage flag on every row telling you whether the country you asked for is one of the 27 TikTok genuinely serves. Use TikTok Trending Hashtags Scraper By Multiple Countries when the axis is breadth of countries: it accepts a much longer country list, runs a single window per run, adds threshold controls for minimum country spread and what counts as global, and can optionally resolve each hashtag's lifetime video and view totals from TikTok's own challenge endpoint — a figure this Actor does not return.
💬 Your feedback
Found a bug, or need a field that is in the Creative Center payload but not in this output — related hashtags, audience breakdowns, a rank-change marker? Open an issue on the Actor's Issues tab. Reports that include the exact input JSON and the sourceUrl from the row that looked wrong are the fastest to reproduce and fix — and if a country or window turns out to be one TikTok no longer serves, the issue is the quickest way to get that confirmed in writing.