{
  "data": {
    "id": "Zi3c96rdMUizrbfUg",
    "actId": "k05bfa7NlOQLER8tY",
    "userId": "zq8lKbpLzTjGDayPC",
    "startedAt": "2026-11-01T03:25:51.671Z",
    "finishedAt": "2026-11-01T03:26:22.860Z",
    "status": "SUCCEEDED",
    "meta": {
      "origin": "CLI"
    },
    "stats": {
      "durationMillis": 31116,
      "runTimeSecs": 31.116,
      "computeUnits": 0.03457333333333333,
      "imageSizeBytes": 3104676864
    },
    "options": {
      "useCache": true,
      "betaPackages": false,
      "memoryMbytes": 4096,
      "diskMbytes": 8192
    },
    "createdByOrganizationMemberUserId": "GK9YjZRyQZVzOdoH5",
    "exitCode": 0,
    "inputSchema": "{\n  \"title\": \"TikTok Keyword & Brand Mention Monitor Input\",\n  \"description\": \"Monitor TikTok for posts that mention keywords, brand names, #hashtags or @handles. Choose strict mention evidence, date windows, multi-source coverage and optional only-new delta state.\",\n  \"type\": \"object\",\n  \"schemaVersion\": 1,\n  \"properties\": {\n    \"keywords\": {\n      \"title\": \"Keywords, brands, hashtags or handles\",\n      \"type\": \"array\",\n      \"description\": \"Terms to monitor. One per line. Accepts plain keywords and phrases ('nike', 'nike shoes'), hashtags ('#JustDoIt') and handles ('@gymshark'). Multi-word caption matches require a contiguous phrase; direct @handles require a clickable tag.\",\n      \"editor\": \"stringList\",\n      \"items\": {\n        \"type\": \"string\"\n      },\n      \"prefill\": [\n        \"coffee\"\n      ],\n      \"example\": [\n        \"nike\",\n        \"#JustDoIt\",\n        \"@gymshark\",\n        \"قهوة\"\n      ]\n    },\n    \"matchMode\": {\n      \"title\": \"What counts as a mention\",\n      \"type\": \"string\",\n      \"description\": \"'Real mentions' keeps posts with a contiguous caption phrase, a matching hashtag slug, a clickable @mention or an explicit sound match. For direct @handle inputs only the clickable tag (or sound) counts. 'Every search result' also keeps weak search hits, labelled searchResult.\",\n      \"editor\": \"select\",\n      \"enum\": [\n        \"mentionsOnly\",\n        \"allResults\"\n      ],\n      \"enumTitles\": [\n        \"Real mentions only (caption / hashtag / tag)\",\n        \"Every search result (labelled)\"\n      ],\n      \"default\": \"mentionsOnly\",\n      \"prefill\": \"mentionsOnly\"\n    },\n    \"datePosted\": {\n      \"title\": \"Posted within\",\n      \"type\": \"string\",\n      \"description\": \"Only keep posts published in this window. The actor asks TikTok for the matching publish window, merges a bounded unfiltered pass for recall, and applies the exact cutoff.\",\n      \"editor\": \"select\",\n      \"enum\": [\n        \"anyTime\",\n        \"past24Hours\",\n        \"pastWeek\",\n        \"pastMonth\",\n        \"past3Months\",\n        \"past6Months\"\n      ],\n      \"enumTitles\": [\n        \"Any time\",\n        \"Past 24 hours\",\n        \"Past week\",\n        \"Past month\",\n        \"Past 3 months\",\n        \"Past 6 months\"\n      ],\n      \"default\": \"anyTime\",\n      \"prefill\": \"anyTime\"\n    },\n    \"postedAfter\": {\n      \"title\": \"Posted on or after\",\n      \"type\": \"string\",\n      \"description\": \"Optional exact cut-off, e.g. 2026-10-01 or '3 days'. The tighter of this and 'Posted within' applies.\",\n      \"editor\": \"datepicker\",\n      \"dateType\": \"absoluteOrRelative\"\n    },\n    \"sortBy\": {\n      \"title\": \"Order\",\n      \"type\": \"string\",\n      \"description\": \"Order of the final rows. 'Newest first' is the monitor-friendly order; TikTok's own newest ordering is used where available, with a client-side fallback.\",\n      \"editor\": \"select\",\n      \"enum\": [\n        \"relevance\",\n        \"newest\",\n        \"mostLiked\"\n      ],\n      \"enumTitles\": [\n        \"Relevance (TikTok order)\",\n        \"Newest first\",\n        \"Most liked first\"\n      ],\n      \"default\": \"relevance\",\n      \"prefill\": \"relevance\"\n    },\n    \"searchDepth\": {\n      \"title\": \"How wide to search\",\n      \"type\": \"string\",\n      \"description\": \"'Just this term' runs one search. 'Standard' adds the #hashtag and @handle for single-word terms. 'Deep' also reads photo/slideshow posts, related searches and case/spacing variants. A direct @handle stays tagged-only in mentionsOnly.\",\n      \"editor\": \"select\",\n      \"enum\": [\n        \"keywordOnly\",\n        \"standard\",\n        \"deep\"\n      ],\n      \"enumTitles\": [\n        \"Just this term (fastest)\",\n        \"Standard: keyword + #hashtag + @handle\",\n        \"Deep: + photo posts, related searches, variants\"\n      ],\n      \"default\": \"standard\",\n      \"prefill\": \"keywordOnly\"\n    },\n    \"maxResultsPerKeyword\": {\n      \"title\": \"Max posts per keyword\",\n      \"type\": \"integer\",\n      \"description\": \"Maximum rows kept for each keyword after all sources are merged (1-500). TikTok returns roughly 130-240 posts per source query; deep mode can find more across sources.\",\n      \"default\": 50,\n      \"minimum\": 1,\n      \"maximum\": 500,\n      \"prefill\": 3\n    },\n    \"maxItems\": {\n      \"title\": \"Max posts in total\",\n      \"type\": \"integer\",\n      \"description\": \"Optional global cap across all keywords and sources.\",\n      \"minimum\": 1,\n      \"maximum\": 100000\n    },\n    \"minPlayCount\": {\n      \"title\": \"Minimum views\",\n      \"type\": \"integer\",\n      \"description\": \"Drop posts with fewer plays/views. Leave empty for no minimum.\",\n      \"minimum\": 0\n    },\n    \"searchCountry\": {\n      \"title\": \"Search from country\",\n      \"type\": \"string\",\n      \"description\": \"'Any country' uses the fastest free connection. A specific country pins the search to that market's results, which is slower but shows what people there see.\",\n      \"editor\": \"select\",\n      \"enum\": [\n        \"any\",\n        \"US\",\n        \"CA\",\n        \"MX\",\n        \"BR\",\n        \"AR\",\n        \"CO\",\n        \"CL\",\n        \"PE\",\n        \"GB\",\n        \"IE\",\n        \"FR\",\n        \"DE\",\n        \"AT\",\n        \"CH\",\n        \"NL\",\n        \"BE\",\n        \"ES\",\n        \"PT\",\n        \"IT\",\n        \"SE\",\n        \"NO\",\n        \"DK\",\n        \"FI\",\n        \"PL\",\n        \"CZ\",\n        \"RO\",\n        \"GR\",\n        \"TR\",\n        \"UA\",\n        \"IL\",\n        \"SA\",\n        \"AE\",\n        \"EG\",\n        \"MA\",\n        \"NG\",\n        \"KE\",\n        \"ZA\",\n        \"PK\",\n        \"BD\",\n        \"ID\",\n        \"MY\",\n        \"SG\",\n        \"PH\",\n        \"TH\",\n        \"VN\",\n        \"JP\",\n        \"KR\",\n        \"TW\",\n        \"AU\",\n        \"NZ\"\n      ],\n      \"enumTitles\": [\n        \"Any country (fastest)\",\n        \"United States\",\n        \"Canada\",\n        \"Mexico\",\n        \"Brazil\",\n        \"Argentina\",\n        \"Colombia\",\n        \"Chile\",\n        \"Peru\",\n        \"United Kingdom\",\n        \"Ireland\",\n        \"France\",\n        \"Germany\",\n        \"Austria\",\n        \"Switzerland\",\n        \"Netherlands\",\n        \"Belgium\",\n        \"Spain\",\n        \"Portugal\",\n        \"Italy\",\n        \"Sweden\",\n        \"Norway\",\n        \"Denmark\",\n        \"Finland\",\n        \"Poland\",\n        \"Czechia\",\n        \"Romania\",\n        \"Greece\",\n        \"Turkey\",\n        \"Ukraine\",\n        \"Israel\",\n        \"Saudi Arabia\",\n        \"United Arab Emirates\",\n        \"Egypt\",\n        \"Morocco\",\n        \"Nigeria\",\n        \"Kenya\",\n        \"South Africa\",\n        \"Pakistan\",\n        \"Bangladesh\",\n        \"Indonesia\",\n        \"Malaysia\",\n        \"Singapore\",\n        \"Philippines\",\n        \"Thailand\",\n        \"Vietnam\",\n        \"Japan\",\n        \"South Korea\",\n        \"Taiwan\",\n        \"Australia\",\n        \"New Zealand\"\n      ],\n      \"default\": \"any\",\n      \"prefill\": \"any\"\n    },\n    \"onlyNewMentions\": {\n      \"title\": \"Only new mentions\",\n      \"type\": \"boolean\",\n      \"description\": \"Emit only posts not seen by this monitor in previous runs. The first run backfills; 'new' means new to this monitor, not new on TikTok.\",\n      \"default\": false,\n      \"prefill\": false\n    },\n    \"monitorName\": {\n      \"title\": \"Monitor name\",\n      \"type\": \"string\",\n      \"description\": \"Name for this watchlist's saved state. Use a different name for a separate set of keywords.\",\n      \"default\": \"default\",\n      \"prefill\": \"default\",\n      \"editor\": \"textfield\"\n    },\n    \"soundUrls\": {\n      \"title\": \"Brand or campaign sounds\",\n      \"type\": \"array\",\n      \"description\": \"Optional TikTok sound URLs or IDs to read as explicit sources, e.g. https://www.tiktok.com/music/original-sound-123... . Sound evidence sets soundMatch; a sound-only row uses source=sound and keyword=sound:<musicId>.\",\n      \"editor\": \"stringList\",\n      \"items\": {\n        \"type\": \"string\"\n      },\n      \"example\": [\n        \"https://www.tiktok.com/music/original-sound-7627060748200577810\"\n      ]\n    }\n  },\n  \"required\": [\n    \"keywords\"\n  ]\n}",
    "readme": "# TikTok Keyword & Brand Mention Monitor\n\nFind the TikTok posts that actually mention your brand, product, campaign, hashtag or handle — and see why each one matched. This actor reads TikTok's public search from several angles (keyword, hashtag, handle, photo posts, related searches and optional brand sounds), merges duplicates into one row per post, and labels the evidence so a real caption mention is never confused with a weak search hit.\n\n## What this actor does\n\n- **Six source types:** keyword search, `#hashtag` search, `@handle` search, photo/slideshow search, token-filtered related searches, and explicit brand/campaign sounds.\n- **Evidence, not guesses:** every row says whether the term appears in the caption, in a hashtag, as TikTok's clickable `@mention`, from a sound page, or only as a search result.\n- **Strict by default:** `mentionsOnly` keeps rows with real evidence; switch to `allResults` to keep every search hit, labelled.\n- **One row per post:** multi-keyword and multi-source duplicates are merged, with `matchedKeywords[]`, `sources[]` and the best `rank` across merged sources.\n- **Built for monitoring:** server-backed date windows, newest-first ordering, an exact cut-off, and optional only-new delta state.\n- **Full post detail:** caption, engagement, creator snapshot, sound, video or slideshow images, hashtags, mentions and location tags.\n- **Honest empty fields:** values TikTok does not provide are omitted, never filled with placeholders.\n\n## Output per post\n\nEvery row is one public TikTok post.\n\n- `postId`, `postType` (`video` or `slideshow`), `postUrl` — identity and canonical link\n- `caption`, `textLanguage`, `createTime`, `createdAt` — text and publication time\n- `keyword` — the monitored term that primarily matched this post; an explicit sound-only row uses `sound:<musicId>`\n- `source` — the primary source: `keyword`, `hashtag`, `handle`, `photo`, `relatedSearch` or `sound`; merged rows list every source in `sources[]`\n- `matchReason` — `caption`, `hashtag`, `taggedMention`, `sound` or `searchResult`\n- `matchReasons[]` — every reason this post matched across sources\n- `matchedTerm`, `matchedKeywords[]`, `sources[]` — full provenance after dedupe; explicit sounds contribute a `sound:<musicId>` label\n- `mentionInCaption`, `captionMatch`, `hashtagMatch`, `taggedMatch`, `soundMatch` — evidence flags\n- `matchedHashtags[]` — the hashtag entries that matched the monitored term\n- `matchedMention` — `{username, displayText, userId, secUid, start, end}` for a clickable tag\n- `rank` — best (lowest) position this post had across all merged sources\n- `searchUrl`, `searchCountry`, `monitorName`, `scrapedAt` — provenance and run context\n- `isNew`, `firstSeenAt` — monitor state (first run: everything is new)\n- `playCount`, `likeCount`, `commentCount`, `shareCount`, `collectCount`, `repostCount`\n- `author` — `{id, secUid, username, displayName, bio, verified, privateAccount, avatarUrl, followerCount, followingCount, heartCount, videoCount}`\n- `music` — `{id, title, authorName, original, duration, album, playUrl, coverUrl, ...}` when a sound is attached\n- `video` — dimensions, duration, definition, `playUrl`, `downloadAddr`, cover images and quality details\n- `images[]` — ordered slideshow images (`{ordinal, width, height, urlList}`)\n- `hashtags[]` — all structured hashtags in the post\n- `mentions[]` — all clickable `@mentions`, with real handles and source-text offsets\n- `poi` — location tag, when the creator added one\n- `isAd`, `adLabelVersion`, `privateItem`, `forFriend`, `originalItem`, `stitchEnabled`, `shareEnabled`, `duetEnabled`, `commentStatus`\n- `isAigc`, `aiContentLabel`, `isPaidPartnership` — only when TikTok exposes a verified label\n- `mediaUrlExpiresAt` — when TikTok's signed media links expire\n\nMedia URLs (`video.playUrl`, `video.downloadAddr`, `music.playUrl`) are reference-only. Most video play/download links return 403 outside TikTok; music and cover links are signed too and can expire or fail. Covers and slideshow images also expire.\n\n## How matching works\n\nA search result is not automatically a mention.\n\n- `caption` — the monitored term appears contiguously in the caption. Single words use whole-word matching, so `nike` does **not** match `nikeshoes`. Multi-word terms must appear as a true phrase: `#nike ... #shoes` is **not** a `nike shoes` caption match; `my nike shoes` is. For non-Latin scripts, matching is a contiguous substring, so related forms can count (for example `القهوة` for `قهوة`).\n- `hashtag` — the post carries a hashtag whose slug equals the monitored term with whitespace removed. `nike shoes` matches `#nikeshoes`; separate `#nike` and `#shoes` do not count as a phrase match.\n- `taggedMention` — the caption contains TikTok's clickable tag for the monitored `@handle`. A direct `@handle` keyword is strict: in `mentionsOnly`, only clickable tags (or an explicit sound source) are kept; caption and hashtag hits for the bare name are labelled `searchResult` and appear only in `allResults`.\n- `sound` — the post came from a sound URL you supplied.\n- `searchResult` — TikTok returned the post for the search, but none of the above is true. These rows are kept only in `allResults` mode.\n\nFor non-Latin scripts (for example Arabic, Chinese or Devanagari), TikTok often returns relevant posts whose captions do not contain the literal query as a separate word. Matching is a contiguous substring for those scripts, so `mentionsOnly` can keep related word forms; use `allResults` when you want every search hit and label the weak ones yourself.\n\n## Input\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `keywords` | array | – | **Required.** Keywords, phrases, `#hashtags` or `@handles` to monitor. |\n| `matchMode` | string | `mentionsOnly` | `mentionsOnly` (real evidence) or `allResults` (every search hit, labelled). |\n| `datePosted` | string | `anyTime` | `anyTime`, `past24Hours`, `pastWeek`, `pastMonth`, `past3Months` or `past6Months`. |\n| `postedAfter` | string | – | Exact cut-off: `2026-10-01` or a relative age such as `3 days`. |\n| `sortBy` | string | `relevance` | `relevance`, `newest` or `mostLiked`. |\n| `searchDepth` | string | `standard` | `keywordOnly`; `standard` adds #hashtag and @handle for single-word terms; `deep` adds photos, related searches and case/spacing variants. |\n| `maxResultsPerKeyword` | integer | 50 | Rows kept per keyword after merging sources (1-500). |\n| `maxItems` | integer | – | Optional global cap across all keywords. |\n| `minPlayCount` | integer | – | Drop posts with fewer views. |\n| `searchCountry` | string | `any` | `any` or a market code such as `US`, `GB`, `PK`, `JP`. |\n| `onlyNewMentions` | boolean | false | Emit only posts not seen by this monitor before. |\n| `monitorName` | string | `default` | State name for this watchlist. |\n| `soundUrls` | array | – | Optional TikTok sound URLs or IDs to read as explicit sources. |\n\n### Example: brand monitor, last week, newest first\n\n```json\n{\n  \"keywords\": [\"nike\", \"adidas\"],\n  \"matchMode\": \"mentionsOnly\",\n  \"searchDepth\": \"standard\",\n  \"datePosted\": \"pastWeek\",\n  \"sortBy\": \"newest\",\n  \"maxResultsPerKeyword\": 100\n}\n```\n\n### Example: campaign hashtag with photo posts\n\n```json\n{\n  \"keywords\": [\"#JustDoIt\"],\n  \"matchMode\": \"allResults\",\n  \"searchDepth\": \"deep\",\n  \"maxResultsPerKeyword\": 200,\n  \"minPlayCount\": 1000\n}\n```\n\n### Example: unicode discovery\n\n```json\n{\n  \"keywords\": [\"قهوة\", \"咖啡\"],\n  \"matchMode\": \"allResults\",\n  \"searchDepth\": \"standard\",\n  \"maxResultsPerKeyword\": 50\n}\n```\n\n### Example: daily only-new watchlist\n\n```json\n{\n  \"keywords\": [\"khaadi\", \"@gymshark\"],\n  \"matchMode\": \"mentionsOnly\",\n  \"datePosted\": \"pastWeek\",\n  \"sortBy\": \"newest\",\n  \"onlyNewMentions\": true,\n  \"monitorName\": \"brand-watch\"\n}\n```\n\n### Example: brand-owned sound\n\n```json\n{\n  \"keywords\": [\"khaadi\"],\n  \"soundUrls\": [\"https://www.tiktok.com/music/original-sound-7627060748200577810\"],\n  \"searchDepth\": \"standard\",\n  \"maxResultsPerKeyword\": 100\n}\n```\n\n## Use cases\n\n- **Brand monitoring** — catch posts that name your brand, not only posts that tag your account.\n- **Competitor tracking** — compare how often competing brands are mentioned and how those posts perform.\n- **Campaign measurement** — follow a campaign hashtag, slogan or brand-owned sound across creators.\n- **PR and crisis watch** — run a daily only-new monitor for a product or brand term.\n- **Market research** — compare the conversation in different countries and languages.\n- **Creator and partnership discovery** — find the creators and posts driving a brand conversation.\n\n## FAQ\n\n### Do I need a TikTok account, cookies or a proxy?\n\nNo. The actor reads public TikTok search and needs no login, cookies or proxy setup. It chooses a working connection automatically.\n\n### What is the difference between `caption`, `hashtag`, `taggedMention` and `searchResult`?\n\n`caption` means the term appears in the caption text. `hashtag` means a matching hashtag is attached. `taggedMention` means TikTok's clickable tag for the monitored handle is present. `searchResult` means TikTok returned the post for the search but none of the stronger evidence exists; those rows appear only in `allResults` mode.\n\n### Why do I get fewer rows than `maxResultsPerKeyword`?\n\nTikTok's public search pool is finite and rotates. Runs returned roughly 130-240 posts per source query in current tests; a repeat of the same query can return a different set. Niche terms, narrow date windows and hashtags can run out earlier. Date windows also merge a bounded unfiltered pass, so recent posts are not lost just because TikTok's date-ranked pool is incomplete. `deep` mode searches several sources and merges them, which usually finds more.\n\n### How does `onlyNewMentions` work?\n\nThe actor saves the posts it has seen for each `monitorName` in a persistent store. On the next run, only posts not seen before are emitted and marked `isNew`. The first run backfills everything it finds within your filters and caps; if the state is lost, the next run backfills again. \"New\" means new to this monitor, not new on TikTok.\n\n### Does it monitor hashtags and handles?\n\nYes. A plain single-word keyword can automatically include its `#hashtag` and `@handle` in `standard` or `deep` mode. You can also enter a hashtag or handle directly. A direct `@handle` is strict in `mentionsOnly`: only TikTok's clickable tag for that handle is kept. A direct `#hashtag` keeps matching hashtag, caption or tag evidence for the bare term. Use a plain keyword when you want caption mentions of a brand name.\n\n### Can I search a specific country?\n\nYes. Set `searchCountry` to a market code. Country-specific results are slower because the actor uses a country-pinned connection. Leave it on `any` for the fastest run.\n\n### Why are some fields missing from a row?\n\nEmpty values are omitted. TikTok does not expose every field for every post. A per-post country is not available from search; `poi` appears when the creator added a location tag, and `textLanguage` gives the caption language.\n\n### Can I download the video or sound from the output?\n\nTreat media URLs as reference-only. Most video play/download links return 403 outside TikTok; music and cover links are signed too and can expire or fail. Covers and slideshow images also expire.\n\n### Does this actor include comments?\n\nNo. It monitors public posts and captions. TikTok's public web search does not offer a comment search, so comment-level mentions are out of scope.\n\n### Is this affiliated with TikTok?\n\nNo. This is a third-party actor that reads publicly visible TikTok search results and is not affiliated with TikTok or ByteDance.\n",
    "actorDefinition": {
      "actorSpecification": 1,
      "name": "tiktok-keyword-and-brand-mention-monitor",
      "title": "TikTok Keyword & Brand Mention Monitor",
      "description": "Monitors TikTok for posts mentioning keywords, brands, #hashtags or @handles. Labels caption, hashtag and clickable-tag evidence, merges multiple sources, filters by date and supports only-new runs.",
      "version": "1.0",
      "readme": "# TikTok Keyword & Brand Mention Monitor\n\nFind the TikTok posts that actually mention your brand, product, campaign, hashtag or handle — and see why each one matched. This actor reads TikTok's public search from several angles (keyword, hashtag, handle, photo posts, related searches and optional brand sounds), merges duplicates into one row per post, and labels the evidence so a real caption mention is never confused with a weak search hit.\n\n## What this actor does\n\n- **Six source types:** keyword search, `#hashtag` search, `@handle` search, photo/slideshow search, token-filtered related searches, and explicit brand/campaign sounds.\n- **Evidence, not guesses:** every row says whether the term appears in the caption, in a hashtag, as TikTok's clickable `@mention`, from a sound page, or only as a search result.\n- **Strict by default:** `mentionsOnly` keeps rows with real evidence; switch to `allResults` to keep every search hit, labelled.\n- **One row per post:** multi-keyword and multi-source duplicates are merged, with `matchedKeywords[]`, `sources[]` and the best `rank` across merged sources.\n- **Built for monitoring:** server-backed date windows, newest-first ordering, an exact cut-off, and optional only-new delta state.\n- **Full post detail:** caption, engagement, creator snapshot, sound, video or slideshow images, hashtags, mentions and location tags.\n- **Honest empty fields:** values TikTok does not provide are omitted, never filled with placeholders.\n\n## Output per post\n\nEvery row is one public TikTok post.\n\n- `postId`, `postType` (`video` or `slideshow`), `postUrl` — identity and canonical link\n- `caption`, `textLanguage`, `createTime`, `createdAt` — text and publication time\n- `keyword` — the monitored term that primarily matched this post; an explicit sound-only row uses `sound:<musicId>`\n- `source` — the primary source: `keyword`, `hashtag`, `handle`, `photo`, `relatedSearch` or `sound`; merged rows list every source in `sources[]`\n- `matchReason` — `caption`, `hashtag`, `taggedMention`, `sound` or `searchResult`\n- `matchReasons[]` — every reason this post matched across sources\n- `matchedTerm`, `matchedKeywords[]`, `sources[]` — full provenance after dedupe; explicit sounds contribute a `sound:<musicId>` label\n- `mentionInCaption`, `captionMatch`, `hashtagMatch`, `taggedMatch`, `soundMatch` — evidence flags\n- `matchedHashtags[]` — the hashtag entries that matched the monitored term\n- `matchedMention` — `{username, displayText, userId, secUid, start, end}` for a clickable tag\n- `rank` — best (lowest) position this post had across all merged sources\n- `searchUrl`, `searchCountry`, `monitorName`, `scrapedAt` — provenance and run context\n- `isNew`, `firstSeenAt` — monitor state (first run: everything is new)\n- `playCount`, `likeCount`, `commentCount`, `shareCount`, `collectCount`, `repostCount`\n- `author` — `{id, secUid, username, displayName, bio, verified, privateAccount, avatarUrl, followerCount, followingCount, heartCount, videoCount}`\n- `music` — `{id, title, authorName, original, duration, album, playUrl, coverUrl, ...}` when a sound is attached\n- `video` — dimensions, duration, definition, `playUrl`, `downloadAddr`, cover images and quality details\n- `images[]` — ordered slideshow images (`{ordinal, width, height, urlList}`)\n- `hashtags[]` — all structured hashtags in the post\n- `mentions[]` — all clickable `@mentions`, with real handles and source-text offsets\n- `poi` — location tag, when the creator added one\n- `isAd`, `adLabelVersion`, `privateItem`, `forFriend`, `originalItem`, `stitchEnabled`, `shareEnabled`, `duetEnabled`, `commentStatus`\n- `isAigc`, `aiContentLabel`, `isPaidPartnership` — only when TikTok exposes a verified label\n- `mediaUrlExpiresAt` — when TikTok's signed media links expire\n\nMedia URLs (`video.playUrl`, `video.downloadAddr`, `music.playUrl`) are reference-only. Most video play/download links return 403 outside TikTok; music and cover links are signed too and can expire or fail. Covers and slideshow images also expire.\n\n## How matching works\n\nA search result is not automatically a mention.\n\n- `caption` — the monitored term appears contiguously in the caption. Single words use whole-word matching, so `nike` does **not** match `nikeshoes`. Multi-word terms must appear as a true phrase: `#nike ... #shoes` is **not** a `nike shoes` caption match; `my nike shoes` is. For non-Latin scripts, matching is a contiguous substring, so related forms can count (for example `القهوة` for `قهوة`).\n- `hashtag` — the post carries a hashtag whose slug equals the monitored term with whitespace removed. `nike shoes` matches `#nikeshoes`; separate `#nike` and `#shoes` do not count as a phrase match.\n- `taggedMention` — the caption contains TikTok's clickable tag for the monitored `@handle`. A direct `@handle` keyword is strict: in `mentionsOnly`, only clickable tags (or an explicit sound source) are kept; caption and hashtag hits for the bare name are labelled `searchResult` and appear only in `allResults`.\n- `sound` — the post came from a sound URL you supplied.\n- `searchResult` — TikTok returned the post for the search, but none of the above is true. These rows are kept only in `allResults` mode.\n\nFor non-Latin scripts (for example Arabic, Chinese or Devanagari), TikTok often returns relevant posts whose captions do not contain the literal query as a separate word. Matching is a contiguous substring for those scripts, so `mentionsOnly` can keep related word forms; use `allResults` when you want every search hit and label the weak ones yourself.\n\n## Input\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `keywords` | array | – | **Required.** Keywords, phrases, `#hashtags` or `@handles` to monitor. |\n| `matchMode` | string | `mentionsOnly` | `mentionsOnly` (real evidence) or `allResults` (every search hit, labelled). |\n| `datePosted` | string | `anyTime` | `anyTime`, `past24Hours`, `pastWeek`, `pastMonth`, `past3Months` or `past6Months`. |\n| `postedAfter` | string | – | Exact cut-off: `2026-10-01` or a relative age such as `3 days`. |\n| `sortBy` | string | `relevance` | `relevance`, `newest` or `mostLiked`. |\n| `searchDepth` | string | `standard` | `keywordOnly`; `standard` adds #hashtag and @handle for single-word terms; `deep` adds photos, related searches and case/spacing variants. |\n| `maxResultsPerKeyword` | integer | 50 | Rows kept per keyword after merging sources (1-500). |\n| `maxItems` | integer | – | Optional global cap across all keywords. |\n| `minPlayCount` | integer | – | Drop posts with fewer views. |\n| `searchCountry` | string | `any` | `any` or a market code such as `US`, `GB`, `PK`, `JP`. |\n| `onlyNewMentions` | boolean | false | Emit only posts not seen by this monitor before. |\n| `monitorName` | string | `default` | State name for this watchlist. |\n| `soundUrls` | array | – | Optional TikTok sound URLs or IDs to read as explicit sources. |\n\n### Example: brand monitor, last week, newest first\n\n```json\n{\n  \"keywords\": [\"nike\", \"adidas\"],\n  \"matchMode\": \"mentionsOnly\",\n  \"searchDepth\": \"standard\",\n  \"datePosted\": \"pastWeek\",\n  \"sortBy\": \"newest\",\n  \"maxResultsPerKeyword\": 100\n}\n```\n\n### Example: campaign hashtag with photo posts\n\n```json\n{\n  \"keywords\": [\"#JustDoIt\"],\n  \"matchMode\": \"allResults\",\n  \"searchDepth\": \"deep\",\n  \"maxResultsPerKeyword\": 200,\n  \"minPlayCount\": 1000\n}\n```\n\n### Example: unicode discovery\n\n```json\n{\n  \"keywords\": [\"قهوة\", \"咖啡\"],\n  \"matchMode\": \"allResults\",\n  \"searchDepth\": \"standard\",\n  \"maxResultsPerKeyword\": 50\n}\n```\n\n### Example: daily only-new watchlist\n\n```json\n{\n  \"keywords\": [\"khaadi\", \"@gymshark\"],\n  \"matchMode\": \"mentionsOnly\",\n  \"datePosted\": \"pastWeek\",\n  \"sortBy\": \"newest\",\n  \"onlyNewMentions\": true,\n  \"monitorName\": \"brand-watch\"\n}\n```\n\n### Example: brand-owned sound\n\n```json\n{\n  \"keywords\": [\"khaadi\"],\n  \"soundUrls\": [\"https://www.tiktok.com/music/original-sound-7627060748200577810\"],\n  \"searchDepth\": \"standard\",\n  \"maxResultsPerKeyword\": 100\n}\n```\n\n## Use cases\n\n- **Brand monitoring** — catch posts that name your brand, not only posts that tag your account.\n- **Competitor tracking** — compare how often competing brands are mentioned and how those posts perform.\n- **Campaign measurement** — follow a campaign hashtag, slogan or brand-owned sound across creators.\n- **PR and crisis watch** — run a daily only-new monitor for a product or brand term.\n- **Market research** — compare the conversation in different countries and languages.\n- **Creator and partnership discovery** — find the creators and posts driving a brand conversation.\n\n## FAQ\n\n### Do I need a TikTok account, cookies or a proxy?\n\nNo. The actor reads public TikTok search and needs no login, cookies or proxy setup. It chooses a working connection automatically.\n\n### What is the difference between `caption`, `hashtag`, `taggedMention` and `searchResult`?\n\n`caption` means the term appears in the caption text. `hashtag` means a matching hashtag is attached. `taggedMention` means TikTok's clickable tag for the monitored handle is present. `searchResult` means TikTok returned the post for the search but none of the stronger evidence exists; those rows appear only in `allResults` mode.\n\n### Why do I get fewer rows than `maxResultsPerKeyword`?\n\nTikTok's public search pool is finite and rotates. Runs returned roughly 130-240 posts per source query in current tests; a repeat of the same query can return a different set. Niche terms, narrow date windows and hashtags can run out earlier. Date windows also merge a bounded unfiltered pass, so recent posts are not lost just because TikTok's date-ranked pool is incomplete. `deep` mode searches several sources and merges them, which usually finds more.\n\n### How does `onlyNewMentions` work?\n\nThe actor saves the posts it has seen for each `monitorName` in a persistent store. On the next run, only posts not seen before are emitted and marked `isNew`. The first run backfills everything it finds within your filters and caps; if the state is lost, the next run backfills again. \"New\" means new to this monitor, not new on TikTok.\n\n### Does it monitor hashtags and handles?\n\nYes. A plain single-word keyword can automatically include its `#hashtag` and `@handle` in `standard` or `deep` mode. You can also enter a hashtag or handle directly. A direct `@handle` is strict in `mentionsOnly`: only TikTok's clickable tag for that handle is kept. A direct `#hashtag` keeps matching hashtag, caption or tag evidence for the bare term. Use a plain keyword when you want caption mentions of a brand name.\n\n### Can I search a specific country?\n\nYes. Set `searchCountry` to a market code. Country-specific results are slower because the actor uses a country-pinned connection. Leave it on `any` for the fastest run.\n\n### Why are some fields missing from a row?\n\nEmpty values are omitted. TikTok does not expose every field for every post. A per-post country is not available from search; `poi` appears when the creator added a location tag, and `textLanguage` gives the caption language.\n\n### Can I download the video or sound from the output?\n\nTreat media URLs as reference-only. Most video play/download links return 403 outside TikTok; music and cover links are signed too and can expire or fail. Covers and slideshow images also expire.\n\n### Does this actor include comments?\n\nNo. It monitors public posts and captions. TikTok's public web search does not offer a comment search, so comment-level mentions are out of scope.\n\n### Is this affiliated with TikTok?\n\nNo. This is a third-party actor that reads publicly visible TikTok search results and is not affiliated with TikTok or ByteDance.\n",
      "meta": {
        "templateId": "python-playwright"
      },
      "input": {
        "title": "TikTok Keyword & Brand Mention Monitor Input",
        "description": "Monitor TikTok for posts that mention keywords, brand names, #hashtags or @handles. Choose strict mention evidence, date windows, multi-source coverage and optional only-new delta state.",
        "type": "object",
        "schemaVersion": 1,
        "properties": {
          "keywords": {
            "title": "Keywords, brands, hashtags or handles",
            "type": "array",
            "description": "Terms to monitor. One per line. Accepts plain keywords and phrases ('nike', 'nike shoes'), hashtags ('#JustDoIt') and handles ('@gymshark'). Multi-word caption matches require a contiguous phrase; direct @handles require a clickable tag.",
            "editor": "stringList",
            "items": {
              "type": "string"
            },
            "prefill": [
              "coffee"
            ],
            "example": [
              "nike",
              "#JustDoIt",
              "@gymshark",
              "قهوة"
            ]
          },
          "matchMode": {
            "title": "What counts as a mention",
            "type": "string",
            "description": "'Real mentions' keeps posts with a contiguous caption phrase, a matching hashtag slug, a clickable @mention or an explicit sound match. For direct @handle inputs only the clickable tag (or sound) counts. 'Every search result' also keeps weak search hits, labelled searchResult.",
            "editor": "select",
            "enum": [
              "mentionsOnly",
              "allResults"
            ],
            "enumTitles": [
              "Real mentions only (caption / hashtag / tag)",
              "Every search result (labelled)"
            ],
            "default": "mentionsOnly",
            "prefill": "mentionsOnly"
          },
          "datePosted": {
            "title": "Posted within",
            "type": "string",
            "description": "Only keep posts published in this window. The actor asks TikTok for the matching publish window, merges a bounded unfiltered pass for recall, and applies the exact cutoff.",
            "editor": "select",
            "enum": [
              "anyTime",
              "past24Hours",
              "pastWeek",
              "pastMonth",
              "past3Months",
              "past6Months"
            ],
            "enumTitles": [
              "Any time",
              "Past 24 hours",
              "Past week",
              "Past month",
              "Past 3 months",
              "Past 6 months"
            ],
            "default": "anyTime",
            "prefill": "anyTime"
          },
          "postedAfter": {
            "title": "Posted on or after",
            "type": "string",
            "description": "Optional exact cut-off, e.g. 2026-10-01 or '3 days'. The tighter of this and 'Posted within' applies.",
            "editor": "datepicker",
            "dateType": "absoluteOrRelative"
          },
          "sortBy": {
            "title": "Order",
            "type": "string",
            "description": "Order of the final rows. 'Newest first' is the monitor-friendly order; TikTok's own newest ordering is used where available, with a client-side fallback.",
            "editor": "select",
            "enum": [
              "relevance",
              "newest",
              "mostLiked"
            ],
            "enumTitles": [
              "Relevance (TikTok order)",
              "Newest first",
              "Most liked first"
            ],
            "default": "relevance",
            "prefill": "relevance"
          },
          "searchDepth": {
            "title": "How wide to search",
            "type": "string",
            "description": "'Just this term' runs one search. 'Standard' adds the #hashtag and @handle for single-word terms. 'Deep' also reads photo/slideshow posts, related searches and case/spacing variants. A direct @handle stays tagged-only in mentionsOnly.",
            "editor": "select",
            "enum": [
              "keywordOnly",
              "standard",
              "deep"
            ],
            "enumTitles": [
              "Just this term (fastest)",
              "Standard: keyword + #hashtag + @handle",
              "Deep: + photo posts, related searches, variants"
            ],
            "default": "standard",
            "prefill": "keywordOnly"
          },
          "maxResultsPerKeyword": {
            "title": "Max posts per keyword",
            "type": "integer",
            "description": "Maximum rows kept for each keyword after all sources are merged (1-500). TikTok returns roughly 130-240 posts per source query; deep mode can find more across sources.",
            "default": 50,
            "minimum": 1,
            "maximum": 500,
            "prefill": 3
          },
          "maxItems": {
            "title": "Max posts in total",
            "type": "integer",
            "description": "Optional global cap across all keywords and sources.",
            "minimum": 1,
            "maximum": 100000
          },
          "minPlayCount": {
            "title": "Minimum views",
            "type": "integer",
            "description": "Drop posts with fewer plays/views. Leave empty for no minimum.",
            "minimum": 0
          },
          "searchCountry": {
            "title": "Search from country",
            "type": "string",
            "description": "'Any country' uses the fastest free connection. A specific country pins the search to that market's results, which is slower but shows what people there see.",
            "editor": "select",
            "enum": [
              "any",
              "US",
              "CA",
              "MX",
              "BR",
              "AR",
              "CO",
              "CL",
              "PE",
              "GB",
              "IE",
              "FR",
              "DE",
              "AT",
              "CH",
              "NL",
              "BE",
              "ES",
              "PT",
              "IT",
              "SE",
              "NO",
              "DK",
              "FI",
              "PL",
              "CZ",
              "RO",
              "GR",
              "TR",
              "UA",
              "IL",
              "SA",
              "AE",
              "EG",
              "MA",
              "NG",
              "KE",
              "ZA",
              "PK",
              "BD",
              "ID",
              "MY",
              "SG",
              "PH",
              "TH",
              "VN",
              "JP",
              "KR",
              "TW",
              "AU",
              "NZ"
            ],
            "enumTitles": [
              "Any country (fastest)",
              "United States",
              "Canada",
              "Mexico",
              "Brazil",
              "Argentina",
              "Colombia",
              "Chile",
              "Peru",
              "United Kingdom",
              "Ireland",
              "France",
              "Germany",
              "Austria",
              "Switzerland",
              "Netherlands",
              "Belgium",
              "Spain",
              "Portugal",
              "Italy",
              "Sweden",
              "Norway",
              "Denmark",
              "Finland",
              "Poland",
              "Czechia",
              "Romania",
              "Greece",
              "Turkey",
              "Ukraine",
              "Israel",
              "Saudi Arabia",
              "United Arab Emirates",
              "Egypt",
              "Morocco",
              "Nigeria",
              "Kenya",
              "South Africa",
              "Pakistan",
              "Bangladesh",
              "Indonesia",
              "Malaysia",
              "Singapore",
              "Philippines",
              "Thailand",
              "Vietnam",
              "Japan",
              "South Korea",
              "Taiwan",
              "Australia",
              "New Zealand"
            ],
            "default": "any",
            "prefill": "any"
          },
          "onlyNewMentions": {
            "title": "Only new mentions",
            "type": "boolean",
            "description": "Emit only posts not seen by this monitor in previous runs. The first run backfills; 'new' means new to this monitor, not new on TikTok.",
            "default": false,
            "prefill": false
          },
          "monitorName": {
            "title": "Monitor name",
            "type": "string",
            "description": "Name for this watchlist's saved state. Use a different name for a separate set of keywords.",
            "default": "default",
            "prefill": "default",
            "editor": "textfield"
          },
          "soundUrls": {
            "title": "Brand or campaign sounds",
            "type": "array",
            "description": "Optional TikTok sound URLs or IDs to read as explicit sources, e.g. https://www.tiktok.com/music/original-sound-123... . Sound evidence sets soundMatch; a sound-only row uses source=sound and keyword=sound:<musicId>.",
            "editor": "stringList",
            "items": {
              "type": "string"
            },
            "example": [
              "https://www.tiktok.com/music/original-sound-7627060748200577810"
            ]
          }
        },
        "required": [
          "keywords"
        ]
      },
      "output": {
        "actorOutputSchemaVersion": 1,
        "title": "TikTok Keyword & Brand Mention Monitor Output",
        "description": "One row per public TikTok post matched to a keyword, hashtag, handle, photo search, related search or explicit sound, with match evidence, provenance and engagement.",
        "properties": {
          "data": {
            "type": "string",
            "title": "Keyword Mentions",
            "description": "Dataset containing one row per matched TikTok post. Each row includes keyword, source, matchReason, matchedKeywords/sources, caption/hashtag/tag evidence, engagement, author snapshot, music/video metadata, slideshow images, hashtags and mentions.",
            "template": "{{links.apiDefaultDatasetUrl}}/items"
          }
        }
      },
      "dockerfile": "./Dockerfile",
      "storages": {
        "dataset": {
          "actorSpecification": 1,
          "title": "TikTok Keyword Mentions",
          "views": {
            "overview": {
              "title": "Mentions Overview",
              "description": "TikTok posts matching a monitored keyword, hashtag or handle, with match evidence and engagement.",
              "transformation": {
                "fields": [
                  "postId",
                  "keyword",
                  "rank",
                  "mentionInCaption",
                  "caption",
                  "createdAt",
                  "author",
                  "playCount",
                  "likeCount",
                  "commentCount",
                  "shareCount",
                  "postUrl",
                  "source",
                  "matchReason",
                  "isNew",
                  "captionMatch",
                  "hashtagMatch",
                  "taggedMatch"
                ]
              },
              "display": {
                "component": "table",
                "properties": {
                  "postId": {
                    "label": "Post ID",
                    "format": "text"
                  },
                  "keyword": {
                    "label": "Keyword",
                    "format": "text"
                  },
                  "rank": {
                    "label": "Rank",
                    "format": "number"
                  },
                  "mentionInCaption": {
                    "label": "In Caption",
                    "format": "boolean"
                  },
                  "caption": {
                    "label": "Caption",
                    "format": "text"
                  },
                  "createdAt": {
                    "label": "Created At",
                    "format": "text"
                  },
                  "playCount": {
                    "label": "Views",
                    "format": "number"
                  },
                  "likeCount": {
                    "label": "Likes",
                    "format": "number"
                  },
                  "commentCount": {
                    "label": "Comments",
                    "format": "number"
                  },
                  "shareCount": {
                    "label": "Shares",
                    "format": "number"
                  },
                  "postUrl": {
                    "label": "Post URL",
                    "format": "link"
                  },
                  "source": {
                    "label": "source",
                    "format": "text"
                  },
                  "matchReason": {
                    "label": "matchReason",
                    "format": "text"
                  },
                  "isNew": {
                    "label": "New",
                    "format": "boolean"
                  }
                }
              }
            },
            "matching": {
              "title": "Matching Evidence",
              "description": "One row per post with the evidence that caused it to be kept.",
              "transformation": {
                "fields": [
                  "postId",
                  "keyword",
                  "source",
                  "matchReason",
                  "matchedTerm",
                  "captionMatch",
                  "hashtagMatch",
                  "taggedMatch",
                  "soundMatch",
                  "matchedHashtags",
                  "matchedMention",
                  "matchedKeywords",
                  "sources",
                  "isNew"
                ]
              },
              "display": {
                "component": "table",
                "properties": {
                  "postId": {
                    "label": "Post ID",
                    "format": "text"
                  },
                  "keyword": {
                    "label": "Keyword",
                    "format": "text"
                  },
                  "source": {
                    "label": "Source",
                    "format": "text"
                  },
                  "matchReason": {
                    "label": "Match Reason",
                    "format": "text"
                  },
                  "matchedTerm": {
                    "label": "Matched Term",
                    "format": "text"
                  },
                  "captionMatch": {
                    "label": "Caption",
                    "format": "boolean"
                  },
                  "hashtagMatch": {
                    "label": "Hashtag",
                    "format": "boolean"
                  },
                  "taggedMatch": {
                    "label": "Tagged",
                    "format": "boolean"
                  },
                  "soundMatch": {
                    "label": "Sound",
                    "format": "boolean"
                  },
                  "isNew": {
                    "label": "New",
                    "format": "boolean"
                  }
                }
              }
            }
          },
          "fields": {
            "keyword": {
              "type": "string",
              "title": "Keyword",
              "description": "The monitored keyword/brand/hashtag/handle this post primarily matched; a sound-only row uses sound:<musicId>"
            },
            "rank": {
              "type": "integer",
              "title": "Rank",
              "description": "Best (lowest) 0-based position this post had across all merged source queries"
            },
            "mentionInCaption": {
              "type": "boolean",
              "title": "Mention In Caption",
              "description": "Whether the monitored term appears contiguously in the caption (same as captionMatch; kept for backward compatibility)"
            },
            "postId": {
              "type": "string",
              "title": "Post ID",
              "description": "TikTok's unique post/video ID (string to preserve 64-bit precision)"
            },
            "postUrl": {
              "type": "string",
              "title": "Post URL",
              "description": "Direct URL to the TikTok post"
            },
            "postType": {
              "type": "string",
              "title": "Post Type",
              "description": "Either 'video' or 'slideshow'"
            },
            "caption": {
              "type": "string",
              "title": "Caption",
              "description": "Post caption/description text including hashtags and mentions"
            },
            "textLanguage": {
              "type": "string",
              "title": "Text Language",
              "description": "Detected language code of the caption text"
            },
            "createTime": {
              "type": "integer",
              "title": "Create Time (Unix)",
              "description": "Unix timestamp (seconds) when the post was created"
            },
            "createdAt": {
              "type": "string",
              "title": "Created At",
              "description": "ISO 8601 UTC datetime when the post was created"
            },
            "playCount": {
              "type": "integer",
              "title": "Play Count",
              "description": "Number of video plays"
            },
            "likeCount": {
              "type": "integer",
              "title": "Like Count",
              "description": "Number of likes/diggs"
            },
            "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 bookmarked/collected"
            },
            "repostCount": {
              "type": "integer",
              "title": "Repost Count",
              "description": "Number of reposts"
            },
            "stitchEnabled": {
              "type": "boolean",
              "title": "Stitch Enabled",
              "description": "Whether the creator allows stitching this video"
            },
            "shareEnabled": {
              "type": "boolean",
              "title": "Share Enabled",
              "description": "Whether sharing is enabled for this post"
            },
            "duetEnabled": {
              "type": "boolean",
              "title": "Duet Enabled",
              "description": "Whether the creator allows duets with this video"
            },
            "commentStatus": {
              "type": "integer",
              "title": "Comment Status",
              "description": "Comment permission setting (0=all, 1=friends, 2=none)"
            },
            "isAd": {
              "type": "boolean",
              "title": "Is Ad",
              "description": "Whether this post is a paid advertisement"
            },
            "privateItem": {
              "type": "boolean",
              "title": "Private Item",
              "description": "Whether the post is set to private"
            },
            "forFriend": {
              "type": "boolean",
              "title": "For Friend",
              "description": "Whether the post is restricted to friends only"
            },
            "originalItem": {
              "type": "boolean",
              "title": "Original Item",
              "description": "Whether this post is an original (non-duet, non-stitch) creation"
            },
            "isAigc": {
              "type": "boolean",
              "title": "AI Generated Content",
              "description": "True only when TikTok exposes an AI label; omitted when unknown"
            },
            "AIGCDescription": {
              "type": "string",
              "title": "AIGC Description",
              "description": "Description for AI-generated content label"
            },
            "adLabelVersion": {
              "type": "integer",
              "title": "Ad Label Version",
              "description": "Ad label version; non-zero when the post is a sponsored ad"
            },
            "mediaUrlExpiresAt": {
              "type": "string",
              "title": "Media URL Expires At",
              "description": "ISO 8601 UTC datetime when signed media URLs (video/cover) expire"
            },
            "author": {
              "type": "object",
              "title": "Author",
              "description": "Author/creator information at time of scraping",
              "properties": {
                "id": {
                  "type": "string",
                  "title": "User ID"
                },
                "secUid": {
                  "type": "string",
                  "title": "Secure UID"
                },
                "username": {
                  "type": "string",
                  "title": "Username"
                },
                "displayName": {
                  "type": "string",
                  "title": "Display Name"
                },
                "bio": {
                  "type": "string",
                  "title": "Bio"
                },
                "verified": {
                  "type": "boolean",
                  "title": "Verified"
                },
                "privateAccount": {
                  "type": "boolean",
                  "title": "Private Account"
                },
                "roomId": {
                  "type": "string",
                  "title": "Live Room ID (non-empty when live)"
                },
                "avatarUrl": {
                  "type": "string",
                  "title": "Avatar URL"
                },
                "followerCount": {
                  "type": "integer",
                  "title": "Followers"
                },
                "followingCount": {
                  "type": "integer",
                  "title": "Following"
                },
                "heartCount": {
                  "type": "integer",
                  "title": "Hearts/Likes"
                },
                "videoCount": {
                  "type": "integer",
                  "title": "Videos"
                }
              }
            },
            "music": {
              "type": "object",
              "title": "Music",
              "description": "Sound/music used in the post",
              "properties": {
                "id": {
                  "type": "string",
                  "title": "Music ID"
                },
                "title": {
                  "type": "string",
                  "title": "Title"
                },
                "authorName": {
                  "type": "string",
                  "title": "Author"
                },
                "original": {
                  "type": "boolean",
                  "title": "Original Sound"
                },
                "duration": {
                  "type": "integer",
                  "title": "Duration (s)"
                },
                "isCopyrighted": {
                  "type": "boolean",
                  "title": "Is Copyrighted"
                },
                "isCommerceMusic": {
                  "type": "boolean",
                  "title": "Is Commerce Music"
                },
                "isUnlimitedMusic": {
                  "type": "boolean",
                  "title": "Is Unlimited Music"
                },
                "isPrivate": {
                  "type": "boolean",
                  "title": "Is Private Sound"
                },
                "shootDuration": {
                  "type": "number",
                  "title": "Shoot Duration (s)"
                },
                "album": {
                  "type": "string",
                  "title": "Album"
                },
                "dspLinks": {
                  "type": "array",
                  "title": "DSP Links (Spotify, Apple Music, etc.)"
                },
                "playUrl": {
                  "type": "string",
                  "title": "Play URL"
                },
                "coverUrl": {
                  "type": "string",
                  "title": "Cover URL"
                }
              }
            },
            "video": {
              "type": "object",
              "title": "Video",
              "description": "Video technical metadata and signed play/cover URLs",
              "properties": {
                "width": {
                  "type": "integer",
                  "title": "Width (px)"
                },
                "height": {
                  "type": "integer",
                  "title": "Height (px)"
                },
                "duration": {
                  "type": "integer",
                  "title": "Duration (s)"
                },
                "ratio": {
                  "type": "string",
                  "title": "Aspect Ratio"
                },
                "definition": {
                  "type": "string",
                  "title": "Definition"
                },
                "bitrate": {
                  "type": "integer",
                  "title": "Bitrate"
                },
                "codecType": {
                  "type": "string",
                  "title": "Codec"
                },
                "format": {
                  "type": "string",
                  "title": "Format"
                },
                "size": {
                  "type": "string",
                  "title": "File Size (bytes)"
                },
                "playUrl": {
                  "type": "string",
                  "title": "Play URL"
                },
                "downloadAddr": {
                  "type": "string",
                  "title": "Download URL"
                },
                "cover": {
                  "type": "string",
                  "title": "Cover Image URL"
                },
                "originCover": {
                  "type": "string",
                  "title": "Original Cover URL"
                },
                "dynamicCover": {
                  "type": "string",
                  "title": "Dynamic Cover URL"
                },
                "videoQuality": {
                  "type": "string",
                  "title": "Video Quality Label"
                },
                "bitrateInfo": {
                  "type": "array",
                  "title": "Bitrate Info",
                  "description": "All available quality/bitrate variants with play URLs"
                },
                "volumeInfo": {
                  "type": "object",
                  "title": "Volume Info",
                  "description": "Audio loudness and peak level metadata"
                }
              }
            },
            "images": {
              "type": "array",
              "title": "Slideshow Images",
              "description": "Images for slideshow posts (postType='slideshow')",
              "items": {
                "type": "object",
                "properties": {
                  "ordinal": {
                    "type": "integer",
                    "title": "Index"
                  },
                  "width": {
                    "type": "integer",
                    "title": "Width"
                  },
                  "height": {
                    "type": "integer",
                    "title": "Height"
                  },
                  "urlList": {
                    "type": "array",
                    "title": "Image URLs"
                  }
                }
              }
            },
            "hashtags": {
              "type": "array",
              "title": "Hashtags",
              "description": "Structured hashtags from TikTok metadata; a caption-only tag that matched is in matchedHashtags",
              "items": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "title": "Hashtag ID"
                  },
                  "name": {
                    "type": "string",
                    "title": "Hashtag Name"
                  },
                  "isCommerce": {
                    "type": "boolean",
                    "title": "Is Commerce"
                  }
                }
              }
            },
            "mentions": {
              "type": "array",
              "title": "Mentions",
              "description": "@mentions in the post caption with UTF-16 text offsets",
              "items": {
                "type": "object",
                "properties": {
                  "username": {
                    "type": "string",
                    "title": "Username"
                  },
                  "start": {
                    "type": "integer",
                    "title": "Start Offset"
                  },
                  "end": {
                    "type": "integer",
                    "title": "End Offset"
                  },
                  "awemeId": {
                    "type": "string",
                    "title": "Mentioned Post ID (if any)"
                  }
                }
              }
            },
            "source": {
              "type": "string",
              "title": "Source",
              "description": "Primary source for this row: keyword, hashtag, handle, photo, relatedSearch or sound; sources[] lists every merged source"
            },
            "matchReason": {
              "type": "string",
              "title": "Match Reason",
              "description": "Primary evidence: caption (contiguous phrase), hashtag (slug match), taggedMention (clickable @tag), sound (explicit sound source) or searchResult (weak search hit, allResults only)"
            },
            "matchReasons": {
              "type": "array",
              "title": "All Match Reasons",
              "description": "Every evidence type found across sources",
              "items": {
                "type": "string"
              }
            },
            "matchedTerm": {
              "type": "string",
              "title": "Matched Term",
              "description": "The exact search term, related suggestion, source variant or sound ID that surfaced this post"
            },
            "matchedKeywords": {
              "type": "array",
              "title": "Matched Keywords",
              "description": "Every monitored keyword that surfaced this post, plus sound:<musicId> for explicit sound sources",
              "items": {
                "type": "string"
              }
            },
            "sources": {
              "type": "array",
              "title": "Sources",
              "description": "Every source that surfaced this post",
              "items": {
                "type": "string"
              }
            },
            "captionMatch": {
              "type": "boolean",
              "title": "Caption Match",
              "description": "The monitored term appears contiguously in the caption (whole word for single tokens; true phrase for multi-word Latin; substring for non-Latin scripts)"
            },
            "hashtagMatch": {
              "type": "boolean",
              "title": "Hashtag Match",
              "description": "A hashtag slug equals the monitored term with whitespace removed (e.g. #nikeshoes for \"nike shoes\"); separate #nike and #shoes do not count"
            },
            "taggedMatch": {
              "type": "boolean",
              "title": "Tagged Mention",
              "description": "TikTok's clickable @mention for the monitored handle is present; for direct @handle inputs this is the only caption-side match kept in mentionsOnly"
            },
            "soundMatch": {
              "type": "boolean",
              "title": "Sound Match",
              "description": "The post came from an explicit sound URL"
            },
            "matchedHashtags": {
              "type": "array",
              "title": "Matched Hashtags",
              "description": "Hashtag entries that matched the monitored term",
              "items": {
                "type": "object"
              }
            },
            "matchedMention": {
              "type": "object",
              "title": "Matched Mention",
              "description": "The clickable @mention that matched, with real handle and UTF-16 offsets"
            },
            "searchUrl": {
              "type": "string",
              "title": "Search URL",
              "description": "The TikTok search or sound page this post came from"
            },
            "searchCountry": {
              "type": "string",
              "title": "Search Country",
              "description": "Market code when the run was country-pinned"
            },
            "monitorName": {
              "type": "string",
              "title": "Monitor Name",
              "description": "Watchlist state name used for onlyNewMentions"
            },
            "isNew": {
              "type": "boolean",
              "title": "Is New",
              "description": "New to this monitor (not necessarily new on TikTok)"
            },
            "firstSeenAt": {
              "type": "string",
              "title": "First Seen At",
              "description": "ISO 8601 time this monitor first saw the post"
            },
            "scrapedAt": {
              "type": "string",
              "title": "Scraped At",
              "description": "ISO 8601 run timestamp"
            },
            "aiContentLabel": {
              "type": "string",
              "title": "AI Content Label",
              "description": "Human-readable AI label when TikTok exposes one"
            },
            "isPaidPartnership": {
              "type": "boolean",
              "title": "Paid Partnership",
              "description": "TikTok's paid-partnership label when exposed"
            }
          }
        }
      },
      "defaultMemoryMbytes": 2048,
      "minMemoryMbytes": 1024
    },
    "buildNumber": "1.0.3",
    "imageDigest": "f393fc75d8cedee72256b4667c878d55d09af529ef32f8a3e22f1ccc521384d2",
    "actVersion": {
      "sourceType": "SOURCE_FILES",
      "buildTag": "latest",
      "versionNumber": "1.0"
    },
    "usage": {
      "ACTOR_COMPUTE_UNITS": 0.03457333333333333
    }
  }
}