# Changelog of TikTok Scraper - Trends, Creators, Hashtags \[NO COOKIES] ✅ (`unseenuser/tiktok-search-scraper`) Actor

- **URL**: https://apify.com/unseenuser/tiktok-search-scraper/changelog.md
- **Full Actor documentation**: https://apify.com/unseenuser/tiktok-search-scraper.md

## Changelog

All notable changes to **TikTok Scraper - Trends, Creators, Hashtags, Songs \[NO COOKIES]** are documented here. The Actor follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) formatting; new entries are added at the top.

### \[1.9.0] - 2026-10-03

#### Removed

- `verified`, `bio`, and `bioLink` fields from `UserRow`. Testing against the data source confirmed these fields are not included in any current response shape (trending feed, popular creators, search users), so every row was shipping `verified: false`, `bio: null`, `bioLink: null`. Removing them from the schema aligns the Actor's output with what it can actually deliver and shortens the dataset table. Buyers who filter their automations on these keys should drop that filter.
- The `ACTOR_DEBUG_USER_SHAPE` env-var-gated one-time response probe, no longer needed.

#### Changed

- `UserRow.creatorCountry` continues to fall back to the Actor's `region` input (unchanged from 1.8.0), and the output row's `tcmLink` continues to be constructed from the handle when the data source doesn't provide one.
- Dataset "Creators only" view no longer shows the Verified / Bio / Bio Link columns.

### \[1.8.0] - 2026-10-02

#### Removed

- `followerCountBucket` input. Testing confirmed the data provider silently ignored the parameter, so the Actor was over-fetching and filtering client-side - which cost upstream credits without producing additional billable rows. Narrow by `followerCount` on the output dataset after the run completes.
- `creatorCountry` input. Only ever consumed by the direct popular_creators path. The Actor now auto-derives it from the main `region` field, so a `popular_creators` run with `region=US` still narrows to US creators.
- `audienceCountry` input. Same reason as `creatorCountry`: only read by the direct path, no effect on the fallback path, and no reliable way to confirm the data provider honored it. For audience-geography segmentation, filter the dataset in post-processing.
- `sortBy` input. Never forwarded to the data provider in any mode. Buyers who selected a sort hint got the mode's natural ordering regardless. Dropping the input stops advertising a feature that wasn't live.

#### Changed

- `popular_creators` output row's `creatorCountry` now falls back to the Actor's `region` input when the data provider doesn't return a value, so the field is populated on every row instead of being `null` on the derived fallback path.
- Pay-per-event schema collapsed from 13 events to 5: nine mode-specific slugs (`trending_feed_video`, `popular_hashtag_listing`, `popular_creator_listing`, `hashtag_search_video`, `keyword_search_video`, `user_search_result`, `top_search_result`, `song_detail_fetched`, `song_video_result`) are merged into a single `result_row` event at $0.0035 per row. The four 1.7.0 enrichment events are unchanged.
- Dataset `rowType` enum now accepts `photo` and `region_summary` (previously these rows were silently dropped by the dataset validator).

#### Fixed

- `enableRegionSummary` now actually produces the summary row at the end of a region-based run. Before, the row was being emitted but Apify's dataset validator rejected it because `region_summary` wasn't in the `rowType` enum.
- Key-value store writes for trend intelligence (rank history, co-occurrence) no longer fail silently. The key format used `:` as a separator, which isn't in Apify's allowed character set for KVS keys; separators are now `-`.
- Run logs no longer leak the data provider's error-response body, internal API endpoint paths, or the word "upstream". Buyer-facing warnings now use neutral category labels like "trending feed" and "popular creators".

#### Backward compatibility

- Any saved Task that set `followerCountBucket`, `creatorCountry`, `audienceCountry`, or `sortBy` continues to run - Apify ignores unknown input fields. Behavior is now whatever that mode produces without the (previously ineffective) filter.
- Per-row cost is identical ($0.0035). Invoices now show one `Result row × N` line instead of mode-specific lines.

### [1.7.0] - 2026-10-02

#### Added — four opt-in enrichment events

All default to OFF; existing saved Tasks run identically to 1.6.0 with no new charges.

- **`trend_intelligence`** ($0.005/row) — adds rank velocity (points per day), saturation score (0-100), early-trend flag with confidence, related hashtags from cross-run co-occurrence, and rank history for every hashtag and creator row from region-based modes. Requires ≥2 prior runs of the same mode+region to produce a non-null `rankVelocity`. Uses a stateful key-value store keyed by `rankHistory:<mode>:<region>:<identifier>` and `cooccur:<mode>:<region>`. Triggered by `enableTrendIntelligence=true`.

- **`industry_classification`** ($0.005/row) — classifies each hashtag or song into one of 20 industry categories (Beauty, Fashion, Food, Fitness, Gaming, Entertainment, Finance, Technology, Travel, Automotive, Education, Home, Pets, Parenting, Sports, Music, Art, Business, Healthcare, Lifestyle) via a short LLM call. Uses the **buyer's own Anthropic or OpenAI API key** passed via `aiEnrichmentApiKey`. If no key is set, the event is silently skipped and nothing is charged. Max 30 output tokens per call. Triggered by `enableIndustryClassification=true`.

- **`ai_brand_brief`** ($0.015/row) — generates a 2-3 sentence actionable brief per hashtag or song: what the trend is, which brand category fits, one example campaign angle. Uses the buyer's LLM key. Max 300 output tokens per call. Skipped silently (no charge) when the key is missing. Triggered by `enableAiBrief=true`.

- **`region_summary_report`** ($0.05/report) — emits one aggregated row (`rowType: 'region_summary'`) at the end of each region-based mode run (trending_feed, popular_hashtags, popular_creators). Includes totals, top-10 hashtags/sounds/creators, dominant industries (when classification is also on), and run-level averages for rank velocity, new-entry count, and saturation index. Triggered by `enableRegionSummary=true`.

#### Added — supporting input fields

- `aiEnrichmentApiKey` (secret textfield, empty default) — the buyer's own Anthropic or OpenAI key. Never logged, never persisted beyond the run.
- `aiEnrichmentProvider` (enum anthropic/openai, default anthropic).
- `aiEnrichmentModel` (enum cheap-default/premium, default cheap-default). cheap-default = Claude Haiku 4.5 or GPT-4o mini. premium = Claude Sonnet 5.5 or GPT-4o. Override to any model string via `AI_ENRICHMENT_MODEL_OVERRIDE` env var.

#### Added — new row type

- `RegionSummaryRow` (`rowType: 'region_summary'`). Fields: mode, region, scrapedAt, reportDate, runId, totalTrends, totalVideosScanned, totalCreatorsSeen, topHashtags\[], topSounds\[], topCreators\[], industriesBreakdown\[], avgRankVelocity, newEntryCount, saturationIndex.

#### Added — nested enrichment fields

Appear on existing row types only when the corresponding event fires. Always `null` otherwise (consistent row shape).

- `HashtagRow`: `rank`, `topVideos[]`, `relatedHashtags[]`, `topSounds[]`, `rankHistory[]`, `regionComparison[]`, `rankVelocity`, `saturationScore`, `isEarlyTrend`, `earlyTrendConfidence`, `industry`, `aiBrief`.
- `SongRow`: `topVideos[]`, `similarSongs[]`, `rankHistory[]`, `rankVelocity`, `saturationScore`, `isEarlyTrend`, `industry`, `aiBrief`.
- `UserRow`: `rankHistory[]`, `rankVelocity` (in addition to the 1.5.0 fields).

#### Security

- `src/sanitize.ts` redactor extended to catch Anthropic (`sk-ant-*`) and OpenAI (`sk-*`, `sk-proj-*`) key formats, plus `authorization: Bearer ...` headers. The buyer's LLM key is never persisted, never logged, and discarded when the run ends.

#### Terms of Service

- New section 17 added to the Terms of Service in README.md covering optional third-party AI service usage. Existing sections 0-16 unchanged.

#### Backward compatibility

- All 11 saved Tasks that worked in 1.6.0 return byte-identical output in 1.7.0 as long as the 4 new `enable*` toggles stay false (the default). No existing field key changed, no existing field type changed, no existing default changed.

### [1.6.0] - 2026-10-02

#### Added

- New `PhotoCarouselRow` output row type (`rowType: 'photo'`). The `top_search` mode now returns photo-carousel posts (TikTok's multi-image "photo mode") as their own rows alongside video rows, instead of silently dropping them. Each row carries `imageUrls[]`, `imageCount`, `coverUrl`, and the same statistics / author / music / commerce fields as video rows.
- New optional input `videoPostedWithin` (enum of 7 values: empty default, yesterday, this-week, this-month, last-3-months, last-6-months, all-time). Passed natively to the data provider for `search_keyword` (as `date_posted`) and `top_search` (as `publish_time`). Native upstream filter means setting a tighter window reduces cost, not increases it. Ignored by every other mode.

#### Backward compatibility

- `PhotoCarouselRow` is additive; no existing row type is changed. Buyers who filter on `rowType in ('video', 'user', 'hashtag', 'song')` will not see the new rows unless they add `'photo'` to their filter.
- `videoPostedWithin` defaults to empty string = today's behavior. Existing saved Tasks that don't set it return the exact same data as before.

### [1.5.0] - 2026-10-02

#### Added

- `popular_creators` mode now calls a direct provider endpoint when available, returning real rank data, TikTok Creator Marketplace IDs and links, and per-creator top-video previews.
- Three new optional input fields on `popular_creators`: `followerCountBucket`, `creatorCountry`, `audienceCountry`. **Note:** all three were later removed in 1.8.0 after testing confirmed they were not reliably honored by the data provider; see the 1.8.0 Removed section.
- Five new nullable fields on every creator row: `rank`, `tcmId`, `tcmLink`, `creatorCountry`, `topVideos[]`. Populated by the direct path; `null` elsewhere.

#### Changed

- Clarified error messages for malformed Song URLs in `song_intel`: the Actor now logs the expected URL shapes before an upstream rejection, so buyers see what to fix before the run finishes.
- Removed all surface mentions of the upstream provider from the input form and dataset schema. Form descriptions now use neutral language.

#### Safety

- The direct `popular_creators` path auto-falls back to the previous derive-from-trending logic on any upstream error or empty response. The fallback output is identical to the pre-1.5.0 output.
- Env var `ACTOR_USE_DIRECT_POPULAR_CREATORS=0` forces the fallback path for the entire run.

### [1.4.0] - 2026-10-01

#### Added

- 26 new countries in the `region` enum: HK, TW, RU, AT, CH, IE, NZ, HU, UA, MA, KE, QA, KW, LB, DO, VE, EC, CR, PR, BG, SK, HR, SI, LT, LV, EE. Total region coverage is now 69 countries.
- URL and handle normalization on `queries` (used by `search_users`, `search_keyword`, `top_search`): accepts `@user`, full profile URLs (`https://www.tiktok.com/@user`), and bare handles equivalently.
- URL normalization on `songUrls` (used by `song_intel`): accepts bare numeric song IDs and short share URLs (`vm.tiktok.com`, `vt.tiktok.com`) in addition to the canonical music URL.

#### Changed

- Input schema wording pass: every field title and description now explains itself with a concrete example. The 3-step form flow is explicit, with the mode label pointing to the one field each run needs.
- Hard upper cap on `maxResults` removed. Any positive number is now accepted; internal per-mode safety caps still prevent runaway bills.

#### Removed

- `minFollowers` and `minVideoCount` input filters. Both were applied client-side after fetching results from the data provider, which meant filtered rows cost the Actor money with no revenue. Filtering is now the buyer's responsibility on the output dataset.

### [1.3.0] - 2026-09-24

#### Added

- Paid-plan gate on the Apify platform. Free-plan runs exit immediately with a clear notice explaining how to upgrade, before any upstream call is made and before any charging event fires. Local development runs are unaffected.
- Paid-plan notice is surfaced in the Overview dataset view so buyers see it without digging into logs.

#### Changed

- Hardened the paying-user path end-to-end: all 8 modes verified to produce charged output rows under the paid plan with no cross-mode regressions.

### [1.2.0] - 2026-09-23

#### Changed

- Free-plan result cap reduced from 50 to 5 dataset items. Free-plan runs are a marketing surface, not a product tier.
- `maxResults` is intersected with the billing-plan cap inside each mode so the fetch loop sizes correctly and no upstream call is wasted on data that would be dropped.

### [1.1.5] - 2026-07-29

#### Changed

- `maxResults = 0` now explicitly means "fetch everything the provider returns" (subject to per-mode safety caps).
- Clarified in-form that filter inputs set to 0 or left blank disable the filter and keep every row.

### [1.1.0] - 2026-07-09

#### Added

- `popular_creators` enrichment: each creator surfaced from trending is enriched via a dedicated profile lookup, populating `bio`, `bioLink`, `verified`, `followerCount`, `videoCount`, and more.
- Full pagination for `trending_feed`, `popular_hashtags`, and `popular_creators` via a dedup-and-stall loop.
- Graceful upstream error handling on `song_intel` video pagination and on `popular_*` modes: a transient upstream error returns what has been collected so far instead of failing the run.

#### Changed

- Flat pricing: every pay-per-event normalized to $0.0035 per row, so every mode bills $3.50 per 1,000 results with no mode-specific markup and no run-start fee.
- `search_*` modes updated to match a new provider response shape (`search_item_list`, `aweme_list`, `user_list`, and `items[]` with content-type discriminator in `top_search`).
- `popular_hashtags` and `popular_creators` fallback to deriving from `trending_feed` while TikTok Creative Center is offline, so these modes still return rows instead of failing.

#### Fixed

- `bioLink` now populates reliably by walking TikTok's `bio_url`, `share_info.bio_url`, and five other fallback keys.
- Eliminated `[object Object]` in `popular_creators` username and avatar fields.
- `search_users` stat parsing: follower, video, and heart counts are read from both nested `stats` and top-level `user_info` to match endpoint variations.

### [1.0.5] - 2026-07-07

#### Added

- Minimal output schema wired into Apify so the dataset renders in the Output tab.
- SEO README additions grounded in real keyword research.
- SEO measurement baseline template to track position changes across releases.

### [1.0.4] - 2026-06-22

#### Added

- Example Tasks section in README with 11 ready-to-run configurations covering trending, hashtag, creator, keyword, song, and cross-regional use cases.
- Task bootstrapper script for programmatically seeding the example Tasks.

### [1.0.3] - 2026-06-18

#### Changed

- Apify compute cost reduced via batched dataset pushes and tightened concurrency on multi-input modes. No output fields affected.

### [1.0.2] - 2026-06-10

#### Security

- API key and provider credit balance redacted from every observable surface: run logs, status messages, error paths, and the key-value store.

#### Changed

- Removed the Technical Details block from the README. User-facing docs no longer surface internal implementation details.

### [1.0.1] - 2026-05-25

#### Added

- Free-plan cap at 50 dataset items (later reduced to 5 in 1.2.0), following Apify's recommended pattern for free-tier limiting.

#### Fixed

- Output tab now renders the dataset correctly; `output_schema` reference adjusted to match Apify's expectations.

### [1.0.0] - 2026-05-12

#### Added

- Initial release. 8 discovery modes in one Actor:
  - `trending_feed` - viral TikTok videos by region
  - `popular_hashtags` - top hashtags in a region
  - `popular_creators` - top creators in a region
  - `search_keyword` - find videos matching keywords
  - `search_hashtag` - every video under a hashtag
  - `search_users` - find creators by name or keyword
  - `top_search` - mixed best results for a query
  - `song_intel` - song details and every video using the song
- Mixed-type dataset: each row carries `rowType` (video, user, hashtag, or song) and `mode` so one dataset can hold outputs from different modes.
- Pay-per-event pricing at a flat $3.50 per 1,000 rows across all modes. No run-start fee.
- No TikTok cookies and no login required.
- Full input schema, output schema, dataset schema, and key-value store schema.
- Live status web server (opt-in) exposing `/` and `/status` endpoints during a run.
- Pagination, concurrency control, API-key redaction, and per-run cost monitoring.

[1.7.0]: https://github.com/UnseenUser/TikTok-Search-Scraper/releases/tag/v1.7.0

[1.6.0]: https://github.com/UnseenUser/TikTok-Search-Scraper/releases/tag/v1.6.0

[1.5.0]: https://github.com/UnseenUser/TikTok-Search-Scraper/releases/tag/v1.5.0

[1.4.0]: https://github.com/UnseenUser/TikTok-Search-Scraper/releases/tag/v1.4.0

[1.3.0]: https://github.com/UnseenUser/TikTok-Search-Scraper/releases/tag/v1.3.0

[1.2.0]: https://github.com/UnseenUser/TikTok-Search-Scraper/releases/tag/v1.2.0

[1.1.5]: https://github.com/UnseenUser/TikTok-Search-Scraper/releases/tag/v1.1.5

[1.1.0]: https://github.com/UnseenUser/TikTok-Search-Scraper/releases/tag/v1.1.0

[1.0.5]: https://github.com/UnseenUser/TikTok-Search-Scraper/releases/tag/v1.0.5

[1.0.4]: https://github.com/UnseenUser/TikTok-Search-Scraper/releases/tag/v1.0.4

[1.0.3]: https://github.com/UnseenUser/TikTok-Search-Scraper/releases/tag/v1.0.3

[1.0.2]: https://github.com/UnseenUser/TikTok-Search-Scraper/releases/tag/v1.0.2

[1.0.1]: https://github.com/UnseenUser/TikTok-Search-Scraper/releases/tag/v1.0.1

[1.0.0]: https://github.com/UnseenUser/TikTok-Search-Scraper/releases/tag/v1.0.0
