# Changelog of TikTok Shadow Ban Checker — Bulk Video CSV API (`apivault_labs/tiktok-shadow-ban-checker`) Actor

- **URL**: https://apify.com/apivault_labs/tiktok-shadow-ban-checker/changelog.md
- **Full Actor documentation**: https://apify.com/apivault_labs/tiktok-shadow-ban-checker.md

### 1.4 — 5 October 2026

- Rotate proxy selection for every request and allowed redirect; never fall back to direct access.
- Resolve official short links before building ID-based fallback requests; verify returned video identity and HTTP status.
- Preserve explicit false eligibility signals, keep unobserved eligibility unknown, and separate observation coverage from health score.
- Interaction/privacy settings no longer establish a shadow-ban verdict by themselves.
- Metadata-only/failures remain outside paid results; summary excludes unknown verdicts from percentages.

## Changelog

All notable changes to this Actor will be documented here.

### \[1.3] — 2026-09-12 — Honest billing and release recovery

#### Fixed

- Restored the newer local implementation to production; the public `latest`
  tag had still been pointing at the legacy `0.0.1` build.
- Only complete shadow-ban analyses are written to the paid Dataset. Invalid
  URLs, WAF/fetch failures, oEmbed-only partials, and summaries are free.
- Added strict TikTok video/short-link validation before using a proxy.
- Moved aggregate output and failure/partial diagnostics to the free `SUMMARY`
  Key-Value Store record.

### \[1.2] — 2026-05-22

#### Removed

- **`account` mode** — TikTok stopped shipping user video lists in public HTML
  in 2026 and the lazy-loaded endpoint now requires client-side tokens that
  cannot be reproduced server-side. Keeping a broken mode in the UI was hurting
  the actor more than it helped. Existing users who still pass `mode: "account"`
  get a clear deprecation warning and the actor falls back to URL mode if any
  URLs are also provided.
- **`username`** and **`accountLimit`** input fields removed from the schema.

#### Added

- **`recommendations[]`** — every video result now includes a list of
  actionable, plain-English advice based on the detected signals. Examples:
  - `🚨 TikTok officially removed this video. You will need to re-upload...`
  - `💬 Comments are disabled. Re-enable them in the … menu...`
  - `📉 Engagement rate 0.4% is well below average for 50,000 views — possible soft suppression...`
  - `⏱️ Sub-7-second video. Average watch time is currently the strongest FYP signal...`
    Recommendations are tier-prefixed by health (✅ healthy / 🟡 slightly /
    🟠 restricted / 🔴 heavy / ⛔ shadowbanned), so the first item is always the
    bottom-line verdict.
    Toggle via new boolean input `includeRecommendations` (default `true`).
- **`urlText`** — paste-friendly textarea input that accepts URLs separated by
  newlines, commas or any whitespace. Useful when copying a list out of a
  spreadsheet or doc. Merged with the existing `urls` array; duplicates are
  removed automatically.
- Empty-input safety net upgraded — the actor now logs a clear error and
  exits `FAILED` instead of running through retries on no work.

#### Migration

- If you only used URL mode, no changes needed. The new `recommendations[]`
  field appears on every record.
- If you used account mode, switch to passing direct video URLs. The Python
  SDK's `check_one()` and `check_urls()` already do this and return all 30+
  shadow-ban signals.

### \[1.1.5] — 2026-05-22

#### Known broken — `account` mode

Confirmed today via live tests on multiple usernames (`@tiktok`, `@catstory073`):
TikTok stopped shipping user-page video lists in public HTML some time in 2026.
Pages now serve `userInfo` only, with videos lazy-loaded via a signed endpoint
that requires client-side tokens. Cannot be reached without a JavaScript
runtime. Headless browser would change unit cost from $0.01 → $0.05+ which
kills the actor's economics.

**What changed**:

- Account mode now fails with an explicit, actionable error instead of a vague
  "All user-page strategies failed" loop. The error log includes the workaround
  (switch to `urls` mode + direct video links).
- Recursive parser + bare-id fallback added in 1.1.4 are kept — they catch any
  future TikTok layout that resumes shipping the list, no code change needed.
- URL mode (`mode: urls`) and the entire signal-detection pipeline are unaffected.

### \[1.1.4] — 2026-05-22

#### Fixed — account mode now survives TikTok layout changes

Account mode was returning `All user-page strategies failed` even when TikTok served full 350 KB pages because the parser only knew two specific JSON keys for the video list. TikTok routinely moves these between releases.

- **Recursive parser**: walks the entire embedded JSON tree (UNIVERSAL_DATA, SIGI_STATE, NEXT_DATA, plus any `<script type="application/json">` blob) and detects video lists structurally — by content shape, not by key name. Survives future schema renames.
- **Bare-ID fallback**: when JSON contains zero video lists (TikTok now sometimes ships only `userInfo` and lazy-loads videos client-side), the actor extracts `/video/<id>` paths from raw HTML and fetches each video page individually. Slower (N requests instead of 1) but reliable.
- **Better error message**: `All user-page strategies failed` now also reports which strategy returned the largest HTML so we can diagnose layout issues from logs alone.

### \[1.1.3] — 2026-05-22

#### Fixed — better error messages on missing input

- Account mode without `username` previously exited with status code 1 and no log line, leaving users staring at a silent fail. Now logs an explicit error, pushes a record into the dataset and sets a clear status message: *"Account mode requires a 'TikTok username' (without @). Set the 'username' input field, or switch Mode to 'urls'..."*
- URLs mode without `urls` gets the same treatment (clear log + dataset record).
- Unknown `mode` value now logs the actual value that was passed plus the two valid options.

### \[1.1.2] — 2026-05-22

#### Fixed — shadow ban detection now actually works

- TikTok exposes `indexEnabled` (the primary shadow ban flag) ONLY on mobile-UA pages. Desktop pages omit it entirely. Strategy order rewritten to put mobile UA FIRST so the shadow ban signal is captured in a single request.
- Parser now keeps fetching past partial matches if a richer payload (with `indexEnabled`) is still possible — desktop hits become a fallback rather than a premature success.
- Embed endpoints moved to last position because they don't expose shadow ban data at all.

#### Added

- Multi-signal detection beyond `indexEnabled`: `takeDown`, `secret`, `privateItem`, `forFriend`, `isReviewing`, `warnInfo`, `shareEnabled`, `duetDisplay`, `stitchDisplay`, `itemCommentStatus`, `downloadSetting`, plus author-level `secret`/`privateAccount`. Composite verdict triggers `shadowbanned=true` on any hard signal or 3+ soft restrictions.

### \[1.1.1] — 2026-05-22

#### Fixed

- Replaced removed httpx `proxies=` argument with `AsyncClient(proxy=...)` — fixes runtime error on Apify SDK 3.4 / httpx 0.28.
- `compute_viral_potential` and `createDate` now coerce `createTime` to `int` before passing to `datetime.fromtimestamp` — fixes `'str' object cannot be interpreted as an integer` on TikTok responses where the field is stringified.

#### Added — Anti-WAF resilience

- Multi-strategy fetcher: `embed/v2` → `embed` → desktop UA → mobile UA → m.tiktok.com mirror → Googlebot UA before failing.
- Parser now also reads the new `webapp.reflow.video.detail` scope used on mobile pages.
- oEmbed metadata fallback: when full HTML is blocked, the actor now still returns title, author, thumbnail and `partial=true` instead of an empty error record.
- `_extract_video_id_from_url` helper for robust ID parsing across URL forms.
- `proxyCountry` input (default `US`) — pin residential proxy to a country to stop region-redirects (e.g. `/in/about`) that previously broke parsing.
- `customProxyUrls` input — supply your own proxies in `host:port:user:pass`, `user:pass@host:port`, or full URL formats. Round-robin rotation. Overrides Apify proxy when set.

### \[1.1] — 2026-05-22

#### Added — Major intelligence expansion

**New shadow ban signals (beyond just indexEnabled):**

- `forYouEligible` — whether video is eligible for For You Page
- `searchVisible` — whether video appears in search results
- `divertedToPrivate` — TikTok forced the video to private (strong ban signal)
- `duetDisabled` — duet feature disabled
- `stitchDisabled` — stitch feature disabled
- `commentDisabled` — comments disabled
- `downloadDisabled` — downloads disabled
- `privateAccount` — account is private
- `isAd` — video is an ad
- `banReasonHints[]` — human-readable list of detected restrictions
- `restrictionCount` — total number of active restrictions

**New computed metrics:**

- `videoHealthScore` (0-100) — composite signal from all ban indicators + engagement
- `videoHealthStatus` — healthy / slightly_restricted / restricted / heavily_restricted / shadowbanned
- `engagementRate` — (likes + comments + shares) / views × 100 (%)
- `viralPotentialScore` (0-100) — views velocity + engagement bonus

**New author fields:**

- `author.verified` — verified account
- `author.followerCount`, `followingCount`, `heartCount`, `videoCount`

**New video metadata:**

- `createDate` — human-readable date (YYYY-MM-DD)
- `duration` — video length in seconds
- `ratio` — video aspect ratio
- `coverUrl` — thumbnail URL
- `music` — `{title, authorName, original}`
- `hashtags[]` — extracted from challenges

**New mode: `account`**

- Check last N videos from a TikTok username
- Automatically builds video URLs from user page data
- Configurable via `accountLimit` (1-50 videos)

**New: `includeSummary` flag**

- Adds one aggregate record at the end of the dataset:
  - `totalChecked`, `shadowbannedCount`, `shadowbannedPct`
  - `healthyCount`, `restrictedCount`
  - `avgHealthScore`, `avgEngagementRate`
  - `mostCommonRestrictions[]`
  - `recommendation` — actionable advice

#### Compatibility

- All v1.0 fields preserved: `url`, `shadowbanned`, `indexEnabled`, `locationCreated`, `description`, `author.nickname`, `author.uniqueId`, `stats`, `videoId`, `createTime`
- Default mode is still `urls` — existing integrations work unchanged

### \[1.0] — 2026-05-16

#### Added

- Initial release
- Shadow ban detection via `indexEnabled` property
- Batch URL checking with residential proxy rotation
- Basic stats: views, likes, comments, shares, saves
- Author info: nickname, uniqueId
- Short link resolution (vm.tiktok.com, vt.tiktok.com)
