{
  "data": {
    "id": "Jaudae6KZydhY2aLp",
    "actId": "FSS2ebxYm0XcCsrgp",
    "userId": "IYgBDiXtHOijAaUel",
    "startedAt": "2026-10-25T07:47:51.555Z",
    "finishedAt": "2026-10-25T07:48:19.305Z",
    "status": "SUCCEEDED",
    "meta": {
      "origin": "CLI"
    },
    "stats": {
      "durationMillis": 27685,
      "runTimeSecs": 27.685,
      "computeUnits": 0.03076111111111111,
      "imageSizeBytes": 3103066112
    },
    "options": {
      "useCache": true,
      "betaPackages": false,
      "memoryMbytes": 4096,
      "diskMbytes": 8192
    },
    "createdByOrganizationMemberUserId": "QYo53EZkJk0prePWY",
    "exitCode": 0,
    "inputSchema": "{\n  \"title\": \"TikTok Hashtag Stats and Video Feed Input\",\n  \"description\": \"Collect all-time stats for one or more TikTok hashtags and/or the videos underneath them. Stats mode is the cheapest; feed mode returns the public US hashtag feed; search mode returns one page of keyword search; auto combines stats and feed and adds search when the feed is short.\",\n  \"type\": \"object\",\n  \"schemaVersion\": 1,\n  \"properties\": {\n    \"hashtags\": {\n      \"title\": \"Hashtags\",\n      \"type\": \"array\",\n      \"description\": \"Hashtags to collect. Accepts a bare tag, #tag, or a TikTok tag URL. Case-insensitive, duplicates removed. Up to 10.\",\n      \"editor\": \"stringList\",\n      \"items\": {\n        \"type\": \"string\"\n      },\n      \"minItems\": 1,\n      \"maxItems\": 10,\n      \"prefill\": [\n        \"fitness\"\n      ]\n    },\n    \"mode\": {\n      \"title\": \"Mode\",\n      \"type\": \"string\",\n      \"description\": \"What to collect. 'stats' returns one aggregate row per hashtag (cheapest). 'feed' returns the public hashtag video feed. 'search' returns one page of TikTok keyword/hashtag search results. 'auto' returns stats plus the feed and adds search results only when the feed is shorter than one page.\",\n      \"editor\": \"select\",\n      \"enum\": [\n        \"stats\",\n        \"feed\",\n        \"search\",\n        \"auto\"\n      ],\n      \"enumTitles\": [\n        \"Stats only — video and view totals\",\n        \"Hashtag feed — videos under the tag\",\n        \"Search — one page of TikTok results\",\n        \"Auto — stats + feed, search when short\"\n      ],\n      \"default\": \"stats\",\n      \"prefill\": \"stats\"\n    },\n    \"maxVideosPerHashtag\": {\n      \"title\": \"Max Videos Per Hashtag\",\n      \"type\": \"integer\",\n      \"description\": \"Upper bound on videos per hashtag for feed, search and auto modes (0–300). Use 0 for stats only. TikTok usually returns fewer than requested: the feed is served in pages of about 30 and many tags stop after roughly 100–150 rows.\",\n      \"default\": 30,\n      \"minimum\": 0,\n      \"maximum\": 300,\n      \"prefill\": 0\n    },\n    \"maxHashtags\": {\n      \"title\": \"Max Hashtags\",\n      \"type\": \"integer\",\n      \"description\": \"Process no more than this many hashtags from the list (1–10). Extra hashtags are skipped.\",\n      \"default\": 10,\n      \"minimum\": 1,\n      \"maximum\": 10\n    },\n    \"includeStatsRow\": {\n      \"title\": \"Include Stats Row\",\n      \"type\": \"boolean\",\n      \"description\": \"Emit the aggregate hashtag stats row before video rows in feed, search and auto modes. Stats mode always emits it.\",\n      \"default\": true\n    },\n    \"region\": {\n      \"title\": \"Region\",\n      \"type\": \"string\",\n      \"description\": \"Market used for the hashtag feed. Only the United States is verified to return a public feed; other markets are not exposed yet.\",\n      \"editor\": \"select\",\n      \"enum\": [\n        \"US\"\n      ],\n      \"enumTitles\": [\n        \"United States (verified)\"\n      ],\n      \"default\": \"US\"\n    },\n    \"proxyConfiguration\": {\n      \"title\": \"Proxy Configuration\",\n      \"type\": \"object\",\n      \"description\": \"Optional. Leave empty to use the built-in US residential connection with automatic fallback. Set your own Apify proxy configuration here to override it. Feed mode uses the most residential bandwidth, which is billed by Apify proxy usage.\",\n      \"editor\": \"proxy\"\n    }\n  },\n  \"required\": [\n    \"hashtags\"\n  ]\n}",
    "readme": "# TikTok Hashtag Stats and Video Feed Collector\r\n\r\nCollect the numbers behind a TikTok hashtag and the videos underneath it. For each tag you get one all-time stats row — total videos, total views, cover images, commerce flag — plus, when you ask for them, video rows with captions, authors, music, engagement counts, and the exact source each video came from. No TikTok login, cookies, or API key is needed.\r\n\r\n## What this actor does\r\n\r\n- **Four modes:** `stats` (cheapest), `feed` (the public hashtag video feed), `search` (one page of TikTok keyword/hashtag results), and `auto` (stats plus feed, with search added when the feed is short)\r\n- **One aggregate row per hashtag:** all-time `videoCount` and `viewCount`, canonical hashtag name and ID, description, commerce flag, cover images, and campaign announcement text when TikTok provides it\r\n- **Video rows with provenance:** `source` (`feed` or `search`), 1-based `position`, `region`, `scrapedAt`, and `carriesSearchedHashtag` so you can tell a true tag video from a search match\r\n- **Rich video payload:** post ID/URL/caption/language/creation time, play/like/comment/share/collect/repost counts, author profile and stats, music metadata, video dimensions and cover, slideshow images, hashtags, and mentions\r\n- **Deduplicated and capped:** repeated videos are removed by post ID, and every mode honours `maxVideosPerHashtag`\r\n- **Empty fields are omitted** from every record instead of being written as `null`\r\n\r\n## Output per hashtag stats row\r\n\r\nStats rows have `recordType: \"hashtag_metadata\"`.\r\n\r\n- `recordType` — always `\"hashtag_metadata\"`\r\n- `searchedHashtag` — the requested hashtag, lowercased and without `#`\r\n- `hashtag` — the same requested hashtag, for convenient filtering\r\n- `hashtagId` — TikTok's internal challenge ID\r\n- `hashtagName` — hashtag name as TikTok stores it\r\n- `desc` — hashtag description, when present\r\n- `isCommerce` — whether TikTok marks the hashtag as commercial\r\n- `videoCount` — all-time video count, as a string because values can be very large\r\n- `viewCount` — all-time view count, as a string\r\n- `profileLarger` / `profileMedium` / `profileThumb` — hashtag image URLs\r\n- `coverLarger` / `coverMedium` / `coverThumb` — cover images, when present\r\n- `announcementTitle` / `announcementBody` — campaign announcement text, when present\r\n- `shareMeta` — `{title, desc}` share preview, when present\r\n- `source` — always `\"challenge_detail\"` on stats rows\r\n- `region` — market used for the run\r\n- `scrapedAt` — UTC ISO 8601 timestamp\r\n\r\n## Output per video\r\n\r\nVideo rows have `recordType: \"post\"`.\r\n\r\n- `recordType` — always `\"post\"`\r\n- `searchedHashtag` — the hashtag whose run produced this video\r\n- `source` — `\"feed\"` for the hashtag feed or `\"search\"` for keyword-search results\r\n- `position` — 1-based order in the collected list for that hashtag\r\n- `carriesSearchedHashtag` — `true` when the video's own hashtag entities include the searched tag\r\n- `carriesAnyHashtag` — `true` when the caption carries at least one hashtag\r\n- `region` — market used for the run\r\n- `scrapedAt` — UTC ISO 8601 timestamp\r\n- `postId`, `postType` (`video` or `slideshow`), `postUrl`\r\n- `caption`, `textLanguage`, `locationCreated`\r\n- `createTime` (Unix epoch), `createdAt` (ISO 8601)\r\n- `stitchEnabled`, `shareEnabled`, `duetEnabled`, `isAd`\r\n- `playCount`, `likeCount`, `commentCount`, `shareCount`, `collectCount`, `repostCount`\r\n- `author` — `{id, secUid, username, displayName, bio, verified, privateAccount, avatarUrl, followerCount, followingCount, heartCount, videoCount}`\r\n- `authorStats` — the author's follower, following, heart, video, and digg counts as integers\r\n- `music` — `{id, title, authorName, original, duration, playUrl, coverUrl}`\r\n- `video` — `{width, height, duration, ratio, playUrl, cover}`\r\n- `hashtags[]` — `{name, id, isCommerce}` entries from the caption\r\n- `mentions[]` — `{username, start, end, awemeId}` entries from the caption\r\n- `textExtra` — raw caption entities when present\r\n- `images[]` — `{ordinal, width, height, urlList}` for slideshow posts\r\n- `mediaUrlExpiresAt` — UTC ISO 8601 expiry of the signed media URLs, when detectable\r\n\r\n## Input\r\n\r\n| Field | Type | Default | Description |\r\n|---|---|---|---|\r\n| `hashtags` | array | – | Hashtags to collect. Accepts `fitness`, `#fitness`, or a TikTok tag URL. Required, 1–10 values |\r\n| `mode` | string | `stats` | `stats`, `feed`, `search`, or `auto` |\r\n| `maxVideosPerHashtag` | integer | `30` | Upper bound per hashtag (0–300). Use `0` for stats only. This is a cap, not a promise — TikTok often returns fewer |\r\n| `maxHashtags` | integer | `10` | Process only the first N hashtags (1–10) |\r\n| `includeStatsRow` | boolean | `true` | Emit the stats row before video rows in `feed`, `search`, and `auto`. `stats` mode always emits it |\r\n| `region` | string | `US` | Market used for the feed. Only `US` is verified |\r\n| `proxyConfiguration` | object | – | Optional. Leave empty to use the built-in US residential connection with automatic fallback |\r\n\r\n### Example: cheapest stats check\r\n\r\n```json\r\n{\r\n  \"hashtags\": [\"fitness\"],\r\n  \"mode\": \"stats\",\r\n  \"maxVideosPerHashtag\": 0\r\n}\r\n```\r\n\r\n### Example: collect 60 videos from a campaign hashtag\r\n\r\n```json\r\n{\r\n  \"hashtags\": [\"summerfashion\"],\r\n  \"mode\": \"feed\",\r\n  \"maxVideosPerHashtag\": 60,\r\n  \"includeStatsRow\": true\r\n}\r\n```\r\n\r\n### Example: keyword search fallback\r\n\r\n```json\r\n{\r\n  \"hashtags\": [\"booktok\"],\r\n  \"mode\": \"search\",\r\n  \"maxVideosPerHashtag\": 40,\r\n  \"includeStatsRow\": false\r\n}\r\n```\r\n\r\n### Example: several tags with auto mode\r\n\r\n```json\r\n{\r\n  \"hashtags\": [\"#fitness\", \"gymtok\", \"https://www.tiktok.com/tag/workout\"],\r\n  \"mode\": \"auto\",\r\n  \"maxVideosPerHashtag\": 90,\r\n  \"maxHashtags\": 3\r\n}\r\n```\r\n\r\n## Use cases\r\n\r\n- **Campaign sizing** — compare all-time video and view counts across several brand or campaign hashtags in one run\r\n- **Content research** — sample current top videos under a niche tag and study hooks, captions, and creators\r\n- **Creator discovery** — rank authors by follower and engagement counts inside a hashtag feed\r\n- **Social listening** — track how a hashtag's totals change across repeated snapshots\r\n- **Agency reporting** — export one clean dataset of hashtag totals plus video-level evidence for a client deck\r\n- **Search-gap analysis** — compare true feed rows against keyword-search rows using `source` and `carriesSearchedHashtag`\r\n\r\n## Limits and data freshness\r\n\r\n- **Region:** the public video feed is verified for the United States only. Other markets are not exposed until each one is verified.\r\n- **Feed size:** TikTok serves roughly 30 videos per page and stops early for many tags. A typical run gets about 100–150 videos before the tag's own feed stops; very large tags can stop sooner. The platform's practical ceiling is far below “all videos”.\r\n- **Search size:** search returns one page of roughly 50–90 results. It is keyword search, not guaranteed tag membership — use `carriesSearchedHashtag` to filter.\r\n- **No date or sort filter:** TikTok's public hashtag feed does not expose a reliable date range or sort order, so this actor does not offer one.\r\n- **Stats are all-time:** `videoCount` and `viewCount` describe every video ever posted with the tag, not only the rows in your dataset.\r\n- **Freshness:** every record carries `scrapedAt`. Stats and video counts move continuously on TikTok.\r\n- **Media URLs expire:** video, audio, cover, and avatar URLs are signed and time-limited. `mediaUrlExpiresAt` tells you when they were expected to expire. Download or copy media promptly if you need it; this actor does not rehost files.\r\n- **Empty fields are omitted:** if TikTok does not return a field for a record, the field is absent rather than `null`.\r\n\r\n## FAQ\r\n\r\n**Do I need a TikTok login or cookies?**\r\nNo. The actor reads public hashtag pages and public search results without any account.\r\n\r\n**Do I need to configure a proxy?**\r\nNo. Leave `proxyConfiguration` empty and the actor picks a working US connection, falling back automatically if the preferred one is unavailable. Feed mode depends on a US residential connection, which is billed by Apify proxy usage. As a guide, feed runs often add roughly $0.05–0.06 per hashtag in residential transfer; stats-only runs are cheaper. Actual cost varies with run time, row count, and current Apify pricing.\r\n\r\n**Why did I get fewer videos than `maxVideosPerHashtag`?**\r\nTikTok stops serving the public feed after its own internal limit. The actor also stops when TikTok reports no more pages, when no new videos arrive after repeated scrolls, or when the tag's feed is empty. `maxVideosPerHashtag` is an upper bound, never a guarantee.\r\n\r\n**Why is the stats row's view count larger than the views in my videos?**\r\nStats are all-time aggregates for the whole hashtag. Your dataset holds only the videos this run could collect.\r\n\r\n**Why do some search videos not carry my hashtag?**\r\nTikTok search matches keywords and related content, not exact tag membership. Check `carriesSearchedHashtag`; `false` means the video appeared in search but does not list the searched tag as a hashtag entity.\r\n\r\n**What is the difference between `feed` and `search`?**\r\n`feed` is TikTok's hashtag video feed and is the closest thing to \"videos posted under this tag\". `search` is a single page of keyword results and may include only loosely related videos. `auto` uses the feed and only adds search when the feed returns less than one page.\r\n\r\n**Does `auto` always add search results?**\r\nNo. Search is a supplement, not a replacement. It is used only when the feed delivers no rows or fewer than a full page, up to your per-hashtag cap.\r\n\r\n**Are duplicate videos removed?**\r\nYes. Videos are deduplicated by post ID within each hashtag. The same video can still appear under two different hashtags because each row records its `searchedHashtag`.\r\n\r\n**Do old saved tasks still work?**\r\nYes. Inputs using `metadata`, `posts`, `topPosts`, `maxPostsPerHashtag`, or `includeHashtagMetadata` are mapped to the new fields automatically.\r\n\r\n**Will media links still work tomorrow?**\r\nTikTok media links are signed and expire. Use `mediaUrlExpiresAt` as a guide and refresh the dataset when you need current URLs.\r\n\r\n**Is this affiliated with TikTok?**\r\nNo. This is an independent third-party actor that reads publicly available TikTok pages and is not operated or endorsed by TikTok.\r\n",
    "actorDefinition": {
      "actorSpecification": 1,
      "name": "tiktok-hashtag-video-feed-and-stats-extractor",
      "title": "TikTok Hashtag Video Feed & Stats Extractor",
      "description": "Collect a TikTok hashtag's all-time video and view totals plus the public US video feed underneath it — captions, authors, music, engagement, and provenance — in stats, feed, search, or auto mode. No login, cookies, or API key.",
      "version": "1.2",
      "readme": "# TikTok Hashtag Stats and Video Feed Collector\r\n\r\nCollect the numbers behind a TikTok hashtag and the videos underneath it. For each tag you get one all-time stats row — total videos, total views, cover images, commerce flag — plus, when you ask for them, video rows with captions, authors, music, engagement counts, and the exact source each video came from. No TikTok login, cookies, or API key is needed.\r\n\r\n## What this actor does\r\n\r\n- **Four modes:** `stats` (cheapest), `feed` (the public hashtag video feed), `search` (one page of TikTok keyword/hashtag results), and `auto` (stats plus feed, with search added when the feed is short)\r\n- **One aggregate row per hashtag:** all-time `videoCount` and `viewCount`, canonical hashtag name and ID, description, commerce flag, cover images, and campaign announcement text when TikTok provides it\r\n- **Video rows with provenance:** `source` (`feed` or `search`), 1-based `position`, `region`, `scrapedAt`, and `carriesSearchedHashtag` so you can tell a true tag video from a search match\r\n- **Rich video payload:** post ID/URL/caption/language/creation time, play/like/comment/share/collect/repost counts, author profile and stats, music metadata, video dimensions and cover, slideshow images, hashtags, and mentions\r\n- **Deduplicated and capped:** repeated videos are removed by post ID, and every mode honours `maxVideosPerHashtag`\r\n- **Empty fields are omitted** from every record instead of being written as `null`\r\n\r\n## Output per hashtag stats row\r\n\r\nStats rows have `recordType: \"hashtag_metadata\"`.\r\n\r\n- `recordType` — always `\"hashtag_metadata\"`\r\n- `searchedHashtag` — the requested hashtag, lowercased and without `#`\r\n- `hashtag` — the same requested hashtag, for convenient filtering\r\n- `hashtagId` — TikTok's internal challenge ID\r\n- `hashtagName` — hashtag name as TikTok stores it\r\n- `desc` — hashtag description, when present\r\n- `isCommerce` — whether TikTok marks the hashtag as commercial\r\n- `videoCount` — all-time video count, as a string because values can be very large\r\n- `viewCount` — all-time view count, as a string\r\n- `profileLarger` / `profileMedium` / `profileThumb` — hashtag image URLs\r\n- `coverLarger` / `coverMedium` / `coverThumb` — cover images, when present\r\n- `announcementTitle` / `announcementBody` — campaign announcement text, when present\r\n- `shareMeta` — `{title, desc}` share preview, when present\r\n- `source` — always `\"challenge_detail\"` on stats rows\r\n- `region` — market used for the run\r\n- `scrapedAt` — UTC ISO 8601 timestamp\r\n\r\n## Output per video\r\n\r\nVideo rows have `recordType: \"post\"`.\r\n\r\n- `recordType` — always `\"post\"`\r\n- `searchedHashtag` — the hashtag whose run produced this video\r\n- `source` — `\"feed\"` for the hashtag feed or `\"search\"` for keyword-search results\r\n- `position` — 1-based order in the collected list for that hashtag\r\n- `carriesSearchedHashtag` — `true` when the video's own hashtag entities include the searched tag\r\n- `carriesAnyHashtag` — `true` when the caption carries at least one hashtag\r\n- `region` — market used for the run\r\n- `scrapedAt` — UTC ISO 8601 timestamp\r\n- `postId`, `postType` (`video` or `slideshow`), `postUrl`\r\n- `caption`, `textLanguage`, `locationCreated`\r\n- `createTime` (Unix epoch), `createdAt` (ISO 8601)\r\n- `stitchEnabled`, `shareEnabled`, `duetEnabled`, `isAd`\r\n- `playCount`, `likeCount`, `commentCount`, `shareCount`, `collectCount`, `repostCount`\r\n- `author` — `{id, secUid, username, displayName, bio, verified, privateAccount, avatarUrl, followerCount, followingCount, heartCount, videoCount}`\r\n- `authorStats` — the author's follower, following, heart, video, and digg counts as integers\r\n- `music` — `{id, title, authorName, original, duration, playUrl, coverUrl}`\r\n- `video` — `{width, height, duration, ratio, playUrl, cover}`\r\n- `hashtags[]` — `{name, id, isCommerce}` entries from the caption\r\n- `mentions[]` — `{username, start, end, awemeId}` entries from the caption\r\n- `textExtra` — raw caption entities when present\r\n- `images[]` — `{ordinal, width, height, urlList}` for slideshow posts\r\n- `mediaUrlExpiresAt` — UTC ISO 8601 expiry of the signed media URLs, when detectable\r\n\r\n## Input\r\n\r\n| Field | Type | Default | Description |\r\n|---|---|---|---|\r\n| `hashtags` | array | – | Hashtags to collect. Accepts `fitness`, `#fitness`, or a TikTok tag URL. Required, 1–10 values |\r\n| `mode` | string | `stats` | `stats`, `feed`, `search`, or `auto` |\r\n| `maxVideosPerHashtag` | integer | `30` | Upper bound per hashtag (0–300). Use `0` for stats only. This is a cap, not a promise — TikTok often returns fewer |\r\n| `maxHashtags` | integer | `10` | Process only the first N hashtags (1–10) |\r\n| `includeStatsRow` | boolean | `true` | Emit the stats row before video rows in `feed`, `search`, and `auto`. `stats` mode always emits it |\r\n| `region` | string | `US` | Market used for the feed. Only `US` is verified |\r\n| `proxyConfiguration` | object | – | Optional. Leave empty to use the built-in US residential connection with automatic fallback |\r\n\r\n### Example: cheapest stats check\r\n\r\n```json\r\n{\r\n  \"hashtags\": [\"fitness\"],\r\n  \"mode\": \"stats\",\r\n  \"maxVideosPerHashtag\": 0\r\n}\r\n```\r\n\r\n### Example: collect 60 videos from a campaign hashtag\r\n\r\n```json\r\n{\r\n  \"hashtags\": [\"summerfashion\"],\r\n  \"mode\": \"feed\",\r\n  \"maxVideosPerHashtag\": 60,\r\n  \"includeStatsRow\": true\r\n}\r\n```\r\n\r\n### Example: keyword search fallback\r\n\r\n```json\r\n{\r\n  \"hashtags\": [\"booktok\"],\r\n  \"mode\": \"search\",\r\n  \"maxVideosPerHashtag\": 40,\r\n  \"includeStatsRow\": false\r\n}\r\n```\r\n\r\n### Example: several tags with auto mode\r\n\r\n```json\r\n{\r\n  \"hashtags\": [\"#fitness\", \"gymtok\", \"https://www.tiktok.com/tag/workout\"],\r\n  \"mode\": \"auto\",\r\n  \"maxVideosPerHashtag\": 90,\r\n  \"maxHashtags\": 3\r\n}\r\n```\r\n\r\n## Use cases\r\n\r\n- **Campaign sizing** — compare all-time video and view counts across several brand or campaign hashtags in one run\r\n- **Content research** — sample current top videos under a niche tag and study hooks, captions, and creators\r\n- **Creator discovery** — rank authors by follower and engagement counts inside a hashtag feed\r\n- **Social listening** — track how a hashtag's totals change across repeated snapshots\r\n- **Agency reporting** — export one clean dataset of hashtag totals plus video-level evidence for a client deck\r\n- **Search-gap analysis** — compare true feed rows against keyword-search rows using `source` and `carriesSearchedHashtag`\r\n\r\n## Limits and data freshness\r\n\r\n- **Region:** the public video feed is verified for the United States only. Other markets are not exposed until each one is verified.\r\n- **Feed size:** TikTok serves roughly 30 videos per page and stops early for many tags. A typical run gets about 100–150 videos before the tag's own feed stops; very large tags can stop sooner. The platform's practical ceiling is far below “all videos”.\r\n- **Search size:** search returns one page of roughly 50–90 results. It is keyword search, not guaranteed tag membership — use `carriesSearchedHashtag` to filter.\r\n- **No date or sort filter:** TikTok's public hashtag feed does not expose a reliable date range or sort order, so this actor does not offer one.\r\n- **Stats are all-time:** `videoCount` and `viewCount` describe every video ever posted with the tag, not only the rows in your dataset.\r\n- **Freshness:** every record carries `scrapedAt`. Stats and video counts move continuously on TikTok.\r\n- **Media URLs expire:** video, audio, cover, and avatar URLs are signed and time-limited. `mediaUrlExpiresAt` tells you when they were expected to expire. Download or copy media promptly if you need it; this actor does not rehost files.\r\n- **Empty fields are omitted:** if TikTok does not return a field for a record, the field is absent rather than `null`.\r\n\r\n## FAQ\r\n\r\n**Do I need a TikTok login or cookies?**\r\nNo. The actor reads public hashtag pages and public search results without any account.\r\n\r\n**Do I need to configure a proxy?**\r\nNo. Leave `proxyConfiguration` empty and the actor picks a working US connection, falling back automatically if the preferred one is unavailable. Feed mode depends on a US residential connection, which is billed by Apify proxy usage. As a guide, feed runs often add roughly $0.05–0.06 per hashtag in residential transfer; stats-only runs are cheaper. Actual cost varies with run time, row count, and current Apify pricing.\r\n\r\n**Why did I get fewer videos than `maxVideosPerHashtag`?**\r\nTikTok stops serving the public feed after its own internal limit. The actor also stops when TikTok reports no more pages, when no new videos arrive after repeated scrolls, or when the tag's feed is empty. `maxVideosPerHashtag` is an upper bound, never a guarantee.\r\n\r\n**Why is the stats row's view count larger than the views in my videos?**\r\nStats are all-time aggregates for the whole hashtag. Your dataset holds only the videos this run could collect.\r\n\r\n**Why do some search videos not carry my hashtag?**\r\nTikTok search matches keywords and related content, not exact tag membership. Check `carriesSearchedHashtag`; `false` means the video appeared in search but does not list the searched tag as a hashtag entity.\r\n\r\n**What is the difference between `feed` and `search`?**\r\n`feed` is TikTok's hashtag video feed and is the closest thing to \"videos posted under this tag\". `search` is a single page of keyword results and may include only loosely related videos. `auto` uses the feed and only adds search when the feed returns less than one page.\r\n\r\n**Does `auto` always add search results?**\r\nNo. Search is a supplement, not a replacement. It is used only when the feed delivers no rows or fewer than a full page, up to your per-hashtag cap.\r\n\r\n**Are duplicate videos removed?**\r\nYes. Videos are deduplicated by post ID within each hashtag. The same video can still appear under two different hashtags because each row records its `searchedHashtag`.\r\n\r\n**Do old saved tasks still work?**\r\nYes. Inputs using `metadata`, `posts`, `topPosts`, `maxPostsPerHashtag`, or `includeHashtagMetadata` are mapped to the new fields automatically.\r\n\r\n**Will media links still work tomorrow?**\r\nTikTok media links are signed and expire. Use `mediaUrlExpiresAt` as a guide and refresh the dataset when you need current URLs.\r\n\r\n**Is this affiliated with TikTok?**\r\nNo. This is an independent third-party actor that reads publicly available TikTok pages and is not operated or endorsed by TikTok.\r\n",
      "meta": {
        "templateId": "python-playwright"
      },
      "input": {
        "title": "TikTok Hashtag Stats and Video Feed Input",
        "description": "Collect all-time stats for one or more TikTok hashtags and/or the videos underneath them. Stats mode is the cheapest; feed mode returns the public US hashtag feed; search mode returns one page of keyword search; auto combines stats and feed and adds search when the feed is short.",
        "type": "object",
        "schemaVersion": 1,
        "properties": {
          "hashtags": {
            "title": "Hashtags",
            "type": "array",
            "description": "Hashtags to collect. Accepts a bare tag, #tag, or a TikTok tag URL. Case-insensitive, duplicates removed. Up to 10.",
            "editor": "stringList",
            "items": {
              "type": "string"
            },
            "minItems": 1,
            "maxItems": 10,
            "prefill": [
              "fitness"
            ]
          },
          "mode": {
            "title": "Mode",
            "type": "string",
            "description": "What to collect. 'stats' returns one aggregate row per hashtag (cheapest). 'feed' returns the public hashtag video feed. 'search' returns one page of TikTok keyword/hashtag search results. 'auto' returns stats plus the feed and adds search results only when the feed is shorter than one page.",
            "editor": "select",
            "enum": [
              "stats",
              "feed",
              "search",
              "auto"
            ],
            "enumTitles": [
              "Stats only — video and view totals",
              "Hashtag feed — videos under the tag",
              "Search — one page of TikTok results",
              "Auto — stats + feed, search when short"
            ],
            "default": "stats",
            "prefill": "stats"
          },
          "maxVideosPerHashtag": {
            "title": "Max Videos Per Hashtag",
            "type": "integer",
            "description": "Upper bound on videos per hashtag for feed, search and auto modes (0–300). Use 0 for stats only. TikTok usually returns fewer than requested: the feed is served in pages of about 30 and many tags stop after roughly 100–150 rows.",
            "default": 30,
            "minimum": 0,
            "maximum": 300,
            "prefill": 0
          },
          "maxHashtags": {
            "title": "Max Hashtags",
            "type": "integer",
            "description": "Process no more than this many hashtags from the list (1–10). Extra hashtags are skipped.",
            "default": 10,
            "minimum": 1,
            "maximum": 10
          },
          "includeStatsRow": {
            "title": "Include Stats Row",
            "type": "boolean",
            "description": "Emit the aggregate hashtag stats row before video rows in feed, search and auto modes. Stats mode always emits it.",
            "default": true
          },
          "region": {
            "title": "Region",
            "type": "string",
            "description": "Market used for the hashtag feed. Only the United States is verified to return a public feed; other markets are not exposed yet.",
            "editor": "select",
            "enum": [
              "US"
            ],
            "enumTitles": [
              "United States (verified)"
            ],
            "default": "US"
          },
          "proxyConfiguration": {
            "title": "Proxy Configuration",
            "type": "object",
            "description": "Optional. Leave empty to use the built-in US residential connection with automatic fallback. Set your own Apify proxy configuration here to override it. Feed mode uses the most residential bandwidth, which is billed by Apify proxy usage.",
            "editor": "proxy"
          }
        },
        "required": [
          "hashtags"
        ]
      },
      "output": {
        "actorOutputSchemaVersion": 1,
        "title": "TikTok Hashtag Stats and Video Feed Output",
        "description": "Aggregate hashtag stats rows (video/view totals, source, region) and video rows (caption, author, music, engagement, provenance). Use the recordType field to distinguish rows.",
        "properties": {
          "data": {
            "type": "string",
            "title": "Hashtag Data",
            "description": "Dataset containing hashtag_metadata stats rows and/or post video rows. Stats rows carry all-time totals; post rows carry the video payload plus source, position, region, scrapedAt, and carriesSearchedHashtag.",
            "template": "{{links.apiDefaultDatasetUrl}}/items"
          }
        }
      },
      "dockerfile": "./Dockerfile",
      "storages": {
        "dataset": {
          "actorSpecification": 1,
          "title": "TikTok Hashtag Stats and Video Feed Dataset",
          "views": {
            "overview": {
              "title": "Overview",
              "transformation": {
                "fields": [
                  "recordType",
                  "searchedHashtag",
                  "hashtagName",
                  "videoCount",
                  "viewCount",
                  "source",
                  "postId",
                  "caption",
                  "carriesSearchedHashtag",
                  "position",
                  "region",
                  "scrapedAt",
                  "playCount",
                  "likeCount",
                  "commentCount",
                  "postUrl"
                ]
              },
              "display": {
                "component": "table",
                "properties": {
                  "recordType": {
                    "label": "Type",
                    "format": "text"
                  },
                  "searchedHashtag": {
                    "label": "Hashtag",
                    "format": "text"
                  },
                  "hashtagName": {
                    "label": "Hashtag Name",
                    "format": "text"
                  },
                  "videoCount": {
                    "label": "Video Count",
                    "format": "text"
                  },
                  "viewCount": {
                    "label": "View Count",
                    "format": "text"
                  },
                  "source": {
                    "label": "Source",
                    "format": "text"
                  },
                  "postId": {
                    "label": "Post ID",
                    "format": "text"
                  },
                  "caption": {
                    "label": "Caption",
                    "format": "text"
                  },
                  "carriesSearchedHashtag": {
                    "label": "Has Tag",
                    "format": "text"
                  },
                  "position": {
                    "label": "Position",
                    "format": "number"
                  },
                  "region": {
                    "label": "Region",
                    "format": "text"
                  },
                  "scrapedAt": {
                    "label": "Scraped At",
                    "format": "text"
                  },
                  "playCount": {
                    "label": "Plays",
                    "format": "number"
                  },
                  "likeCount": {
                    "label": "Likes",
                    "format": "number"
                  },
                  "commentCount": {
                    "label": "Comments",
                    "format": "number"
                  },
                  "postUrl": {
                    "label": "Post URL",
                    "format": "link"
                  }
                }
              }
            }
          },
          "fields": {
            "recordType": {
              "type": "string",
              "title": "Record Type",
              "description": "Discriminator: 'hashtag_metadata' for aggregate stats rows, 'post' for video rows"
            },
            "searchedHashtag": {
              "type": "string",
              "title": "Searched Hashtag",
              "description": "The normalized hashtag input that produced this record (without #)"
            },
            "hashtag": {
              "type": "string",
              "title": "Hashtag",
              "description": "The requested hashtag (without #) on stats rows"
            },
            "hashtagId": {
              "type": "string",
              "title": "Hashtag ID",
              "description": "TikTok internal challenge ID (string)"
            },
            "hashtagName": {
              "type": "string",
              "title": "Hashtag Name",
              "description": "Canonical hashtag name as TikTok stores it"
            },
            "desc": {
              "type": "string",
              "title": "Description",
              "description": "Optional hashtag description"
            },
            "isCommerce": {
              "type": "boolean",
              "title": "Is Commerce",
              "description": "Whether this is a commercial/brand hashtag"
            },
            "videoCount": {
              "type": "string",
              "title": "Video Count",
              "description": "All-time number of videos using this hashtag, from statsV2 (string to avoid overflow)"
            },
            "viewCount": {
              "type": "string",
              "title": "View Count",
              "description": "All-time views across all videos using this hashtag, from statsV2 (string)"
            },
            "source": {
              "type": "string",
              "title": "Source",
              "description": "'challenge_detail' on stats rows; 'feed' or 'search' on video rows"
            },
            "position": {
              "type": "integer",
              "title": "Position",
              "description": "1-based position in the collected feed/search list for the hashtag"
            },
            "region": {
              "type": "string",
              "title": "Region",
              "description": "Market used for the run (US is the verified feed market)"
            },
            "scrapedAt": {
              "type": "string",
              "title": "Scraped At",
              "description": "UTC ISO 8601 timestamp when the record was built"
            },
            "carriesSearchedHashtag": {
              "type": "boolean",
              "title": "Carries Searched Hashtag",
              "description": "True when the video's own hashtag entities include the searched tag"
            },
            "carriesAnyHashtag": {
              "type": "boolean",
              "title": "Carries Any Hashtag",
              "description": "True when the video's caption carries at least one hashtag entity"
            },
            "profileLarger": {
              "type": "string",
              "title": "Profile Image (Large)",
              "description": "Large profile/cover image URL for the hashtag"
            },
            "profileMedium": {
              "type": "string",
              "title": "Profile Image (Medium)",
              "description": "Medium profile image URL for the hashtag"
            },
            "profileThumb": {
              "type": "string",
              "title": "Profile Image (Thumb)",
              "description": "Thumbnail profile image URL for the hashtag"
            },
            "coverLarger": {
              "type": "string",
              "title": "Cover Image (Large)",
              "description": "Large cover banner image URL"
            },
            "coverMedium": {
              "type": "string",
              "title": "Cover Image (Medium)",
              "description": "Medium cover banner image URL"
            },
            "coverThumb": {
              "type": "string",
              "title": "Cover Image (Thumb)",
              "description": "Thumbnail cover image URL"
            },
            "announcementTitle": {
              "type": "string",
              "title": "Announcement Title",
              "description": "Optional challenge announcement title"
            },
            "announcementBody": {
              "type": "string",
              "title": "Announcement Body",
              "description": "Optional challenge announcement body text"
            },
            "shareMeta": {
              "type": "object",
              "title": "Share Meta",
              "description": "Share metadata object with title and desc fields"
            },
            "postId": {
              "type": "string",
              "title": "Post ID",
              "description": "Unique TikTok post/video ID (string)"
            },
            "postType": {
              "type": "string",
              "title": "Post Type",
              "description": "Either 'video' or 'slideshow'"
            },
            "postUrl": {
              "type": "string",
              "title": "Post URL",
              "description": "Direct URL to the TikTok post"
            },
            "caption": {
              "type": "string",
              "title": "Caption",
              "description": "Post caption/description text"
            },
            "textLanguage": {
              "type": "string",
              "title": "Text Language",
              "description": "Language code of the caption text"
            },
            "createTime": {
              "type": "integer",
              "title": "Create Time",
              "description": "Unix timestamp when the post was created"
            },
            "createdAt": {
              "type": "string",
              "title": "Created At",
              "description": "ISO 8601 timestamp of post creation (UTC)"
            },
            "playCount": {
              "type": "integer",
              "title": "Play Count",
              "description": "Number of video plays"
            },
            "likeCount": {
              "type": "integer",
              "title": "Like Count",
              "description": "Number of likes"
            },
            "commentCount": {
              "type": "integer",
              "title": "Comment Count",
              "description": "Number of comments"
            },
            "shareCount": {
              "type": "integer",
              "title": "Share Count",
              "description": "Number of shares"
            },
            "collectCount": {
              "type": "integer",
              "title": "Collect Count",
              "description": "Number of times post was saved/collected"
            },
            "repostCount": {
              "type": "integer",
              "title": "Repost Count",
              "description": "Number of reposts (from statsV2)"
            },
            "stitchEnabled": {
              "type": "boolean",
              "title": "Stitch Enabled",
              "description": "Whether stitching is allowed on this post"
            },
            "duetEnabled": {
              "type": "boolean",
              "title": "Duet Enabled",
              "description": "Whether duets are allowed on this post"
            },
            "shareEnabled": {
              "type": "boolean",
              "title": "Share Enabled",
              "description": "Whether sharing is enabled on this post"
            },
            "isAd": {
              "type": "boolean",
              "title": "Is Ad",
              "description": "Whether the post is a paid advertisement"
            },
            "author": {
              "type": "object",
              "title": "Author",
              "description": "Author snapshot at time of scraping (id, username, displayName, followerCount, etc.)"
            },
            "authorStats": {
              "type": "object",
              "title": "Author Stats",
              "description": "Follower, following, heart, video and digg counts for the author"
            },
            "music": {
              "type": "object",
              "title": "Music",
              "description": "Music/sound used in the post (id, title, authorName, duration, playUrl, coverUrl)"
            },
            "video": {
              "type": "object",
              "title": "Video",
              "description": "Video metadata (width, height, duration, ratio, playUrl, cover)"
            },
            "textExtra": {
              "type": "array",
              "title": "Text Extra",
              "description": "Raw TikTok caption entities (hashtags and mentions) when present"
            },
            "hashtags": {
              "type": "array",
              "title": "Hashtags",
              "description": "Hashtags used in the post, each with id and name"
            },
            "mentions": {
              "type": "array",
              "title": "Mentions",
              "description": "@mentions in the caption with username and offsets"
            },
            "images": {
              "type": "array",
              "title": "Images",
              "description": "Slideshow images for photo posts (postType='slideshow'). Each item has ordinal, width, height, urlList."
            },
            "locationCreated": {
              "type": "string",
              "title": "Location Created",
              "description": "Country code where the post was created"
            },
            "mediaUrlExpiresAt": {
              "type": "string",
              "title": "Media URL Expires At",
              "description": "ISO 8601 UTC timestamp when signed media URLs expire"
            }
          }
        }
      },
      "defaultRunOptions": {
        "memoryMbytes": 2048
      }
    },
    "buildNumber": "1.2.1",
    "imageDigest": "eecb1138c55bc07301f28c02ab0783d5d8e4daf23b87b7f92e82333b991c6a14",
    "actVersion": {
      "sourceType": "SOURCE_FILES",
      "buildTag": "latest",
      "versionNumber": "1.2"
    },
    "usage": {
      "ACTOR_COMPUTE_UNITS": 0.03076111111111111
    }
  }
}