{
  "data": {
    "id": "Ua3QPyCof7v3HFAEk",
    "actId": "VSfaWsBiswkFBVtAo",
    "userId": "IYgBDiXtHOijAaUel",
    "startedAt": "2026-10-26T16:20:33.307Z",
    "finishedAt": "2026-10-26T16:21:06.077Z",
    "status": "SUCCEEDED",
    "meta": {
      "origin": "CLI"
    },
    "stats": {
      "durationMillis": 32629,
      "runTimeSecs": 32.629,
      "computeUnits": 0.03625444444444444,
      "imageSizeBytes": 3103337984
    },
    "options": {
      "useCache": true,
      "betaPackages": false,
      "memoryMbytes": 4096,
      "diskMbytes": 8192
    },
    "createdByOrganizationMemberUserId": "QYo53EZkJk0prePWY",
    "exitCode": 0,
    "inputSchema": "{\n  \"title\": \"TikTok Post Scraper Input\",\n  \"description\": \"Scrape TikTok posts, profiles, and comment threads without cookies. Paste post URLs or IDs, profile handles, or both.\",\n  \"type\": \"object\",\n  \"schemaVersion\": 1,\n  \"properties\": {\n    \"mode\": {\n      \"title\": \"Mode\",\n      \"type\": \"string\",\n      \"description\": \"What to scrape: individual posts, a profile's newest posts, or comment threads on posts.\",\n      \"editor\": \"select\",\n      \"enum\": [\n        \"posts\",\n        \"profiles\",\n        \"comments\"\n      ],\n      \"enumTitles\": [\n        \"Posts by URL or ID\",\n        \"Profiles (newest posts)\",\n        \"Comments and replies\"\n      ],\n      \"default\": \"posts\",\n      \"prefill\": \"posts\"\n    },\n    \"postUrls\": {\n      \"title\": \"Post URLs or IDs\",\n      \"type\": \"array\",\n      \"description\": \"TikTok video/photo URLs, /v/ share links, embed links, vm.tiktok.com or vt.tiktok.com short links, or bare numeric post IDs. Duplicates are scraped once.\",\n      \"editor\": \"stringList\",\n      \"items\": {\n        \"type\": \"string\"\n      },\n      \"prefill\": [\n        \"https://www.tiktok.com/@khaby.lame/video/7574844184952081686\"\n      ]\n    },\n    \"postIds\": {\n      \"title\": \"Post IDs\",\n      \"type\": \"array\",\n      \"description\": \"Numeric TikTok post IDs (15-20 digits). Use this instead of, or in addition to, postUrls.\",\n      \"editor\": \"stringList\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"profiles\": {\n      \"title\": \"Profiles\",\n      \"type\": \"array\",\n      \"description\": \"TikTok usernames (@handle), profile URLs, or bare handles. Profiles mode returns up to 500 newest posts per profile; comments mode can use them as comment targets.\",\n      \"editor\": \"stringList\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"maxItems\": {\n      \"title\": \"Max rows\",\n      \"type\": \"integer\",\n      \"description\": \"Global cap on output rows. Posts mode counts posts; profiles mode counts posts; comments mode counts comments and replies. Status rows do not count.\",\n      \"default\": 100,\n      \"minimum\": 1,\n      \"maximum\": 1000,\n      \"prefill\": 1\n    },\n    \"maxDetail\": {\n      \"title\": \"Max detail lookups\",\n      \"type\": \"integer\",\n      \"description\": \"How many posts get the slower page/detail enrichment (caption language, collect/repost counts, video tracks). Remaining posts use the fast embed record. Set to 1 for the smallest daily-test run.\",\n      \"default\": 50,\n      \"minimum\": 1,\n      \"maximum\": 300,\n      \"prefill\": 1\n    },\n    \"maxCommentsPerPost\": {\n      \"title\": \"Max comments per post\",\n      \"type\": \"integer\",\n      \"description\": \"Maximum comments plus replies to keep for each post in comments mode, or to attach to each post when Include comments is on.\",\n      \"default\": 100,\n      \"minimum\": 1,\n      \"maximum\": 1000,\n      \"prefill\": 100\n    },\n    \"includeReplies\": {\n      \"title\": \"Include replies\",\n      \"type\": \"boolean\",\n      \"description\": \"Fetch the reply thread under each root comment and include replies with their parent.\",\n      \"default\": false,\n      \"prefill\": false\n    },\n    \"includeComments\": {\n      \"title\": \"Include comments\",\n      \"type\": \"boolean\",\n      \"description\": \"In posts/profiles mode, attach a comments array to each post. In comments mode, emit root comment rows (comments mode enables this automatically when both comment toggles are off).\",\n      \"default\": false,\n      \"prefill\": false\n    },\n    \"proxyConfiguration\": {\n      \"title\": \"Proxy configuration\",\n      \"type\": \"object\",\n      \"description\": \"Optional. Leave empty to run directly; a proxy is not required. The actor falls back to an Apify datacenter proxy on a block signal.\",\n      \"editor\": \"proxy\"\n    },\n    \"includeMediaUrls\": {\n      \"title\": \"Include media URLs\",\n      \"type\": \"boolean\",\n      \"description\": \"Include signed video, cover, music, and slideshow image URLs plus mediaUrlExpiresAt. They typically expire in about 48 hours.\",\n      \"default\": true,\n      \"prefill\": true\n    },\n    \"validateMediaUrls\": {\n      \"title\": \"Validate media URLs\",\n      \"type\": \"boolean\",\n      \"description\": \"Check the main media URL with a GET Range request (TikTok's CDN rejects HEAD) and add mediaUrlAccessible. Adds latency.\",\n      \"default\": false,\n      \"prefill\": false\n    },\n    \"includeAuthorStats\": {\n      \"title\": \"Include author stats\",\n      \"type\": \"boolean\",\n      \"description\": \"Include follower, following, heart, and video counts in author and authorStats.\",\n      \"default\": true,\n      \"prefill\": true\n    },\n    \"includeHashtags\": {\n      \"title\": \"Include hashtags\",\n      \"type\": \"boolean\",\n      \"description\": \"Include the hashtags array extracted from the caption.\",\n      \"default\": true,\n      \"prefill\": true\n    },\n    \"includeMentions\": {\n      \"title\": \"Include mentions\",\n      \"type\": \"boolean\",\n      \"description\": \"Include the mentions array extracted from the caption with text offsets.\",\n      \"default\": true,\n      \"prefill\": true\n    },\n    \"includeSubtitles\": {\n      \"title\": \"Include subtitle and bitrate tracks\",\n      \"type\": \"boolean\",\n      \"description\": \"Include video.subtitleInfos, video.bitrateInfo, and video.volumeInfo (detail path only).\",\n      \"default\": false,\n      \"prefill\": false\n    },\n    \"includeRawFields\": {\n      \"title\": \"Include raw TikTok fields\",\n      \"type\": \"boolean\",\n      \"description\": \"Include item_control, contents, effectStickers, and stickersOnItem from the detail payload. Legacy includeEffectStickers/includeItemControl/includeContents inputs map here.\",\n      \"default\": false,\n      \"prefill\": false\n    }\n  },\n  \"required\": []\n}",
    "readme": "# TikTok Post Scraper\n\nPaste a TikTok link, a bare post ID, or a profile handle and get structured post data back: engagement counts, the creator's snapshot at scrape time, the sound, every hashtag and @mention, carousel images, and signed media URLs. The actor also reads comment threads, including replies, and can walk a profile's newest posts. No TikTok account, cookies, or login are required.\n\n## Three ways to use it\n\n| Mode | What you provide | What you get |\n|---|---|---|\n| **Posts** | Video/photo URLs, share links, or numeric IDs | One row per post |\n| **Profiles** | Handles, @names, or profile URLs | One row per newest post, up to 500 per profile |\n| **Comments** | Post URLs/IDs and/or profiles | One row per comment and reply, up to 1,000 per post |\n\nThe default run is the smallest useful one: one known slideshow post, embed-first, no comments.\n\n## What makes it useful\n\n- **Fast first, rich when asked.** Every post starts on TikTok's embed surface (~0.7 s). The slower page/detail path runs only for `maxDetail` posts, so bulk runs stay quick while the first rows still carry language, collect/repost counts, and video tracks.\n- **Slideshows stay slideshows.** Carousel posts are detected from both the embed shape (`imagePostInfo.displayImages`) and the detail shape (`imagePost.images`), so the images array is not lost when the fast path misses.\n- **Bare IDs work.** IDs are opened with a safe placeholder username instead of the `/video/<id>` path that redirects to a 404.\n- **No silent holes.** Deleted, private, region-locked, or blocked inputs become explicit `recordType: \"status\"` rows with a reason, not an empty dataset.\n- **Honest media checks.** TikTok's video CDN rejects `HEAD`; the optional URL check uses a `GET Range` request and reports what actually answered.\n- **Deduplicated.** The same post ID from a URL, a short link, and an ID list is scraped once. Comments are deduplicated by comment ID.\n\n## Input\n\n| Field | Type | Default | Notes |\n|---|---|---|---|\n| `mode` | select | `posts` | `posts`, `profiles`, or `comments` |\n| `postUrls` | array | one slideshow prefill | Video/photo URLs, `/v/` and `/share/` links, embed links, `vm.tiktok.com`/`vt.tiktok.com`/`tiktok.com/t/` short links, or bare IDs |\n| `postIds` | array | – | 15–20 digit numeric IDs |\n| `profiles` | array | – | `@handle`, profile URL, or bare handle; up to 500 newest posts per profile |\n| `maxItems` | integer 1–1000 | `100` | Global row cap. Posts/profiles count posts; comments counts comments + replies |\n| `maxDetail` | integer 1–300 | `50` | How many posts get the slower page/detail enrichment |\n| `maxCommentsPerPost` | integer 1–1000 | `100` | Comments + replies kept per post |\n| `includeReplies` | boolean | `false` | Fetch reply threads under root comments |\n| `includeComments` | boolean | `false` | Attach a `comments` array in posts/profiles mode; emit root comment rows in comments mode |\n| `proxyConfiguration` | object | – | Optional. Direct requests are the default; AUTO datacenter is used on a block signal |\n| `includeMediaUrls` | boolean | `true` | Signed video/cover/music/image URLs and `mediaUrlExpiresAt` |\n| `validateMediaUrls` | boolean | `false` | Range GET probe → `mediaUrlAccessible` |\n| `includeAuthorStats` | boolean | `true` | `authorStats` and legacy author counts |\n| `includeHashtags` | boolean | `true` | Hashtags array |\n| `includeMentions` | boolean | `true` | Mentions array with offsets |\n| `includeSubtitles` | boolean | `false` | `video.subtitleInfos`, `bitrateInfo`, `volumeInfo` |\n| `includeRawFields` | boolean | `false` | `item_control`, `contents`, `effectStickers`, `stickersOnItem` |\n\n### Examples\n\nScrape one video and one carousel, with detail enrichment:\n\n```json\n{\n  \"mode\": \"posts\",\n  \"postUrls\": [\n    \"https://www.tiktok.com/@natgeo/video/7683891628230315295\",\n    \"https://www.tiktok.com/@nba/photo/7690755132400618783\"\n  ],\n  \"maxItems\": 2,\n  \"maxDetail\": 2\n}\n```\n\nWalk a profile's newest posts:\n\n```json\n{\n  \"mode\": \"profiles\",\n  \"profiles\": [\"natgeo\"],\n  \"maxItems\": 50,\n  \"maxDetail\": 20\n}\n```\n\nRead comments and replies from a post:\n\n```json\n{\n  \"mode\": \"comments\",\n  \"postUrls\": [\"https://www.tiktok.com/@natgeo/video/7683891628230315295\"],\n  \"maxCommentsPerPost\": 200,\n  \"includeReplies\": true,\n  \"maxItems\": 200\n}\n```\n\nBulk short links and IDs:\n\n```json\n{\n  \"mode\": \"posts\",\n  \"postUrls\": [\"https://vm.tiktok.com/ZMabc123/\"],\n  \"postIds\": [\"7574844184952081686\"],\n  \"maxItems\": 100\n}\n```\n\n## Output\n\nEvery record is omit-empty: absent fields are left out rather than returned as `null`, `\"\"`, or an empty array.\n\n### Post rows (`recordType: \"post\"`)\n\n- `postId`, `postType` (`video`/`slideshow`), `postUrl`\n- `caption`, `textLanguage`, `createTime`, `createdAt`, `locationCreated`, `viewerRegion`\n- `author` — id, secUid, username, displayName, bio, verified, privateAccount, avatarUrl, roomId (legacy counts also live here)\n- `authorStats` — followerCount, followingCount, heartCount, videoCount\n- `stats` — playCount, likeCount, commentCount, shareCount, collectCount, repostCount (also kept as flat legacy fields)\n- `flags` — stitchEnabled, duetEnabled, shareEnabled, commentStatus, isAd, isAigc, isECVideo, privateItem, forFriend, originalItem, plus labels when present (also kept flat)\n- `music` — id, title, authorName, album, original, duration, shootDuration, licensing flags, dspLinks, playUrl, coverUrl\n- `video` — width, height, duration, ratio, definition, bitrate, codecType, format, size, videoQuality, playUrl, downloadAddr, cover, originCover, dynamicCover, optional subtitle/bitrate/volume tracks\n- `images` — ordered carousel objects: `ordinal`, `width`, `height`, `urlList`\n- `hashtags`, `mentions`\n- `mediaUrlExpiresAt`, optional `mediaUrlAccessible`\n- `source` (`embed`, `detail`, `ssr`, or `profile`), `scrapedAt`, `recordType`\n- Profiles mode adds `profileUsername`, `position`, and `isPinned` when the profile pinned the post\n- `comments` when `includeComments` is on: root comments, each optionally carrying a `replies` array\n\n### Comment rows (`recordType: \"comment\"` or `\"reply\"`)\n\n`commentId`, `postId`, `postUrl`, `text`, `createTime`, `createdAt`, `likeCount`, `replyCount`, `isPinnedByAuthor`, `isLikedByAuthor`, `isTranslatable`, `language`, `isReply`, `replyToCommentId`, `threadId`, `user` (id, secUid, username, displayName, avatarUrl, optional verified), `images`, `textExtra`, optional share link/stickers/labels, `source`, `scrapedAt`.\n\n### Status rows (`recordType: \"status\"`)\n\n`status` is one of `not_found`, `private`, `region`, or `blocked`, plus `input`, optional `postId`, `message`, `httpStatus`, `source`, and `scrapedAt`. Status rows are diagnostics and do not count toward `maxItems`.\n\n## Limits and behaviour\n\n- **Posts:** up to `maxItems` rows (1–1000). `maxDetail` caps the slower page/detail lookups (1–300).\n- **Profiles:** up to 500 newest posts per profile and `maxItems` rows overall. The profile feed normally comes from TikTok's itemList; if that XHR returns an empty body, the actor retries and then falls back to the creator itemList endpoint.\n- **Comments:** up to 1,000 comment + reply rows per post. TikTok's `total` counter is not used as a progress guarantee; the actor follows the cursor and stops after repeated empty windows.\n- **Pacing:** requests are spaced 1.2–2.0 s apart. HTTP 429 and 503 responses are retried with a 1.5–4.5 s backoff.\n- **Media URLs** are signed and typically expire in about 48 hours. `mediaUrlExpiresAt` tells you when.\n- **Private / deleted / region-locked posts** cannot be read without cookies; they produce a status row.\n- **Comments on deleted posts** may look identical to a post with no comments; the actor reports the post status it observed.\n\n## FAQ\n\n**Do I need a TikTok account or cookies?**\nNo. The actor uses public embed pages, the public video page, and unsigned comment endpoints.\n\n**Is a proxy required?**\nNo. Direct requests are the primary path. If TikTok blocks the request, the actor retries with an Apify datacenter proxy. A proxy is exposed only for users who want to supply their own.\n\n**Why do some rows have more fields than others?**\nThe embed path is fast but omits a few detail-only values (caption language, collect/repost counts, video tracks). Raise `maxDetail` to enrich more posts; the actor never fabricates a field it could not read.\n\n**How do I get only slideshows?**\nProvide slideshow URLs or IDs. Slideshow rows have `postType: \"slideshow\"` and an ordered `images` array. There is no separate slideshow-only mode because the same post-ID path covers videos and carousels.\n\n**Why is a row missing?**\nCheck the status view. Inputs that cannot be parsed, resolved, or read are reported there instead of disappearing.\n\n**Can I attach comments to post rows?**\nYes. Set `includeComments: true` in posts or profiles mode; set `includeReplies` too if you want reply threads nested under each root comment.\n\n**Is this affiliated with TikTok?**\nNo. This is an independent, third-party actor that reads publicly viewable TikTok content.\n",
    "actorDefinition": {
      "actorSpecification": 1,
      "name": "tiktok-post-and-slideshow-detail-scraper",
      "title": "TikTok Post & Slideshow Detail Scraper",
      "description": "Scrape TikTok posts, profiles, and comment threads by URL or ID: engagement stats, author snapshot, music, slideshow images, hashtags, mentions, signed media, and replies. No cookies or proxy setup required.",
      "version": "2.0",
      "buildTag": "latest",
      "dockerfile": "./Dockerfile",
      "readme": "# TikTok Post Scraper\n\nPaste a TikTok link, a bare post ID, or a profile handle and get structured post data back: engagement counts, the creator's snapshot at scrape time, the sound, every hashtag and @mention, carousel images, and signed media URLs. The actor also reads comment threads, including replies, and can walk a profile's newest posts. No TikTok account, cookies, or login are required.\n\n## Three ways to use it\n\n| Mode | What you provide | What you get |\n|---|---|---|\n| **Posts** | Video/photo URLs, share links, or numeric IDs | One row per post |\n| **Profiles** | Handles, @names, or profile URLs | One row per newest post, up to 500 per profile |\n| **Comments** | Post URLs/IDs and/or profiles | One row per comment and reply, up to 1,000 per post |\n\nThe default run is the smallest useful one: one known slideshow post, embed-first, no comments.\n\n## What makes it useful\n\n- **Fast first, rich when asked.** Every post starts on TikTok's embed surface (~0.7 s). The slower page/detail path runs only for `maxDetail` posts, so bulk runs stay quick while the first rows still carry language, collect/repost counts, and video tracks.\n- **Slideshows stay slideshows.** Carousel posts are detected from both the embed shape (`imagePostInfo.displayImages`) and the detail shape (`imagePost.images`), so the images array is not lost when the fast path misses.\n- **Bare IDs work.** IDs are opened with a safe placeholder username instead of the `/video/<id>` path that redirects to a 404.\n- **No silent holes.** Deleted, private, region-locked, or blocked inputs become explicit `recordType: \"status\"` rows with a reason, not an empty dataset.\n- **Honest media checks.** TikTok's video CDN rejects `HEAD`; the optional URL check uses a `GET Range` request and reports what actually answered.\n- **Deduplicated.** The same post ID from a URL, a short link, and an ID list is scraped once. Comments are deduplicated by comment ID.\n\n## Input\n\n| Field | Type | Default | Notes |\n|---|---|---|---|\n| `mode` | select | `posts` | `posts`, `profiles`, or `comments` |\n| `postUrls` | array | one slideshow prefill | Video/photo URLs, `/v/` and `/share/` links, embed links, `vm.tiktok.com`/`vt.tiktok.com`/`tiktok.com/t/` short links, or bare IDs |\n| `postIds` | array | – | 15–20 digit numeric IDs |\n| `profiles` | array | – | `@handle`, profile URL, or bare handle; up to 500 newest posts per profile |\n| `maxItems` | integer 1–1000 | `100` | Global row cap. Posts/profiles count posts; comments counts comments + replies |\n| `maxDetail` | integer 1–300 | `50` | How many posts get the slower page/detail enrichment |\n| `maxCommentsPerPost` | integer 1–1000 | `100` | Comments + replies kept per post |\n| `includeReplies` | boolean | `false` | Fetch reply threads under root comments |\n| `includeComments` | boolean | `false` | Attach a `comments` array in posts/profiles mode; emit root comment rows in comments mode |\n| `proxyConfiguration` | object | – | Optional. Direct requests are the default; AUTO datacenter is used on a block signal |\n| `includeMediaUrls` | boolean | `true` | Signed video/cover/music/image URLs and `mediaUrlExpiresAt` |\n| `validateMediaUrls` | boolean | `false` | Range GET probe → `mediaUrlAccessible` |\n| `includeAuthorStats` | boolean | `true` | `authorStats` and legacy author counts |\n| `includeHashtags` | boolean | `true` | Hashtags array |\n| `includeMentions` | boolean | `true` | Mentions array with offsets |\n| `includeSubtitles` | boolean | `false` | `video.subtitleInfos`, `bitrateInfo`, `volumeInfo` |\n| `includeRawFields` | boolean | `false` | `item_control`, `contents`, `effectStickers`, `stickersOnItem` |\n\n### Examples\n\nScrape one video and one carousel, with detail enrichment:\n\n```json\n{\n  \"mode\": \"posts\",\n  \"postUrls\": [\n    \"https://www.tiktok.com/@natgeo/video/7683891628230315295\",\n    \"https://www.tiktok.com/@nba/photo/7690755132400618783\"\n  ],\n  \"maxItems\": 2,\n  \"maxDetail\": 2\n}\n```\n\nWalk a profile's newest posts:\n\n```json\n{\n  \"mode\": \"profiles\",\n  \"profiles\": [\"natgeo\"],\n  \"maxItems\": 50,\n  \"maxDetail\": 20\n}\n```\n\nRead comments and replies from a post:\n\n```json\n{\n  \"mode\": \"comments\",\n  \"postUrls\": [\"https://www.tiktok.com/@natgeo/video/7683891628230315295\"],\n  \"maxCommentsPerPost\": 200,\n  \"includeReplies\": true,\n  \"maxItems\": 200\n}\n```\n\nBulk short links and IDs:\n\n```json\n{\n  \"mode\": \"posts\",\n  \"postUrls\": [\"https://vm.tiktok.com/ZMabc123/\"],\n  \"postIds\": [\"7574844184952081686\"],\n  \"maxItems\": 100\n}\n```\n\n## Output\n\nEvery record is omit-empty: absent fields are left out rather than returned as `null`, `\"\"`, or an empty array.\n\n### Post rows (`recordType: \"post\"`)\n\n- `postId`, `postType` (`video`/`slideshow`), `postUrl`\n- `caption`, `textLanguage`, `createTime`, `createdAt`, `locationCreated`, `viewerRegion`\n- `author` — id, secUid, username, displayName, bio, verified, privateAccount, avatarUrl, roomId (legacy counts also live here)\n- `authorStats` — followerCount, followingCount, heartCount, videoCount\n- `stats` — playCount, likeCount, commentCount, shareCount, collectCount, repostCount (also kept as flat legacy fields)\n- `flags` — stitchEnabled, duetEnabled, shareEnabled, commentStatus, isAd, isAigc, isECVideo, privateItem, forFriend, originalItem, plus labels when present (also kept flat)\n- `music` — id, title, authorName, album, original, duration, shootDuration, licensing flags, dspLinks, playUrl, coverUrl\n- `video` — width, height, duration, ratio, definition, bitrate, codecType, format, size, videoQuality, playUrl, downloadAddr, cover, originCover, dynamicCover, optional subtitle/bitrate/volume tracks\n- `images` — ordered carousel objects: `ordinal`, `width`, `height`, `urlList`\n- `hashtags`, `mentions`\n- `mediaUrlExpiresAt`, optional `mediaUrlAccessible`\n- `source` (`embed`, `detail`, `ssr`, or `profile`), `scrapedAt`, `recordType`\n- Profiles mode adds `profileUsername`, `position`, and `isPinned` when the profile pinned the post\n- `comments` when `includeComments` is on: root comments, each optionally carrying a `replies` array\n\n### Comment rows (`recordType: \"comment\"` or `\"reply\"`)\n\n`commentId`, `postId`, `postUrl`, `text`, `createTime`, `createdAt`, `likeCount`, `replyCount`, `isPinnedByAuthor`, `isLikedByAuthor`, `isTranslatable`, `language`, `isReply`, `replyToCommentId`, `threadId`, `user` (id, secUid, username, displayName, avatarUrl, optional verified), `images`, `textExtra`, optional share link/stickers/labels, `source`, `scrapedAt`.\n\n### Status rows (`recordType: \"status\"`)\n\n`status` is one of `not_found`, `private`, `region`, or `blocked`, plus `input`, optional `postId`, `message`, `httpStatus`, `source`, and `scrapedAt`. Status rows are diagnostics and do not count toward `maxItems`.\n\n## Limits and behaviour\n\n- **Posts:** up to `maxItems` rows (1–1000). `maxDetail` caps the slower page/detail lookups (1–300).\n- **Profiles:** up to 500 newest posts per profile and `maxItems` rows overall. The profile feed normally comes from TikTok's itemList; if that XHR returns an empty body, the actor retries and then falls back to the creator itemList endpoint.\n- **Comments:** up to 1,000 comment + reply rows per post. TikTok's `total` counter is not used as a progress guarantee; the actor follows the cursor and stops after repeated empty windows.\n- **Pacing:** requests are spaced 1.2–2.0 s apart. HTTP 429 and 503 responses are retried with a 1.5–4.5 s backoff.\n- **Media URLs** are signed and typically expire in about 48 hours. `mediaUrlExpiresAt` tells you when.\n- **Private / deleted / region-locked posts** cannot be read without cookies; they produce a status row.\n- **Comments on deleted posts** may look identical to a post with no comments; the actor reports the post status it observed.\n\n## FAQ\n\n**Do I need a TikTok account or cookies?**\nNo. The actor uses public embed pages, the public video page, and unsigned comment endpoints.\n\n**Is a proxy required?**\nNo. Direct requests are the primary path. If TikTok blocks the request, the actor retries with an Apify datacenter proxy. A proxy is exposed only for users who want to supply their own.\n\n**Why do some rows have more fields than others?**\nThe embed path is fast but omits a few detail-only values (caption language, collect/repost counts, video tracks). Raise `maxDetail` to enrich more posts; the actor never fabricates a field it could not read.\n\n**How do I get only slideshows?**\nProvide slideshow URLs or IDs. Slideshow rows have `postType: \"slideshow\"` and an ordered `images` array. There is no separate slideshow-only mode because the same post-ID path covers videos and carousels.\n\n**Why is a row missing?**\nCheck the status view. Inputs that cannot be parsed, resolved, or read are reported there instead of disappearing.\n\n**Can I attach comments to post rows?**\nYes. Set `includeComments: true` in posts or profiles mode; set `includeReplies` too if you want reply threads nested under each root comment.\n\n**Is this affiliated with TikTok?**\nNo. This is an independent, third-party actor that reads publicly viewable TikTok content.\n",
      "input": {
        "title": "TikTok Post Scraper Input",
        "description": "Scrape TikTok posts, profiles, and comment threads without cookies. Paste post URLs or IDs, profile handles, or both.",
        "type": "object",
        "schemaVersion": 1,
        "properties": {
          "mode": {
            "title": "Mode",
            "type": "string",
            "description": "What to scrape: individual posts, a profile's newest posts, or comment threads on posts.",
            "editor": "select",
            "enum": [
              "posts",
              "profiles",
              "comments"
            ],
            "enumTitles": [
              "Posts by URL or ID",
              "Profiles (newest posts)",
              "Comments and replies"
            ],
            "default": "posts",
            "prefill": "posts"
          },
          "postUrls": {
            "title": "Post URLs or IDs",
            "type": "array",
            "description": "TikTok video/photo URLs, /v/ share links, embed links, vm.tiktok.com or vt.tiktok.com short links, or bare numeric post IDs. Duplicates are scraped once.",
            "editor": "stringList",
            "items": {
              "type": "string"
            },
            "prefill": [
              "https://www.tiktok.com/@khaby.lame/video/7574844184952081686"
            ]
          },
          "postIds": {
            "title": "Post IDs",
            "type": "array",
            "description": "Numeric TikTok post IDs (15-20 digits). Use this instead of, or in addition to, postUrls.",
            "editor": "stringList",
            "items": {
              "type": "string"
            }
          },
          "profiles": {
            "title": "Profiles",
            "type": "array",
            "description": "TikTok usernames (@handle), profile URLs, or bare handles. Profiles mode returns up to 500 newest posts per profile; comments mode can use them as comment targets.",
            "editor": "stringList",
            "items": {
              "type": "string"
            }
          },
          "maxItems": {
            "title": "Max rows",
            "type": "integer",
            "description": "Global cap on output rows. Posts mode counts posts; profiles mode counts posts; comments mode counts comments and replies. Status rows do not count.",
            "default": 100,
            "minimum": 1,
            "maximum": 1000,
            "prefill": 1
          },
          "maxDetail": {
            "title": "Max detail lookups",
            "type": "integer",
            "description": "How many posts get the slower page/detail enrichment (caption language, collect/repost counts, video tracks). Remaining posts use the fast embed record. Set to 1 for the smallest daily-test run.",
            "default": 50,
            "minimum": 1,
            "maximum": 300,
            "prefill": 1
          },
          "maxCommentsPerPost": {
            "title": "Max comments per post",
            "type": "integer",
            "description": "Maximum comments plus replies to keep for each post in comments mode, or to attach to each post when Include comments is on.",
            "default": 100,
            "minimum": 1,
            "maximum": 1000,
            "prefill": 100
          },
          "includeReplies": {
            "title": "Include replies",
            "type": "boolean",
            "description": "Fetch the reply thread under each root comment and include replies with their parent.",
            "default": false,
            "prefill": false
          },
          "includeComments": {
            "title": "Include comments",
            "type": "boolean",
            "description": "In posts/profiles mode, attach a comments array to each post. In comments mode, emit root comment rows (comments mode enables this automatically when both comment toggles are off).",
            "default": false,
            "prefill": false
          },
          "proxyConfiguration": {
            "title": "Proxy configuration",
            "type": "object",
            "description": "Optional. Leave empty to run directly; a proxy is not required. The actor falls back to an Apify datacenter proxy on a block signal.",
            "editor": "proxy"
          },
          "includeMediaUrls": {
            "title": "Include media URLs",
            "type": "boolean",
            "description": "Include signed video, cover, music, and slideshow image URLs plus mediaUrlExpiresAt. They typically expire in about 48 hours.",
            "default": true,
            "prefill": true
          },
          "validateMediaUrls": {
            "title": "Validate media URLs",
            "type": "boolean",
            "description": "Check the main media URL with a GET Range request (TikTok's CDN rejects HEAD) and add mediaUrlAccessible. Adds latency.",
            "default": false,
            "prefill": false
          },
          "includeAuthorStats": {
            "title": "Include author stats",
            "type": "boolean",
            "description": "Include follower, following, heart, and video counts in author and authorStats.",
            "default": true,
            "prefill": true
          },
          "includeHashtags": {
            "title": "Include hashtags",
            "type": "boolean",
            "description": "Include the hashtags array extracted from the caption.",
            "default": true,
            "prefill": true
          },
          "includeMentions": {
            "title": "Include mentions",
            "type": "boolean",
            "description": "Include the mentions array extracted from the caption with text offsets.",
            "default": true,
            "prefill": true
          },
          "includeSubtitles": {
            "title": "Include subtitle and bitrate tracks",
            "type": "boolean",
            "description": "Include video.subtitleInfos, video.bitrateInfo, and video.volumeInfo (detail path only).",
            "default": false,
            "prefill": false
          },
          "includeRawFields": {
            "title": "Include raw TikTok fields",
            "type": "boolean",
            "description": "Include item_control, contents, effectStickers, and stickersOnItem from the detail payload. Legacy includeEffectStickers/includeItemControl/includeContents inputs map here.",
            "default": false,
            "prefill": false
          }
        },
        "required": []
      },
      "output": {
        "actorOutputSchemaVersion": 1,
        "title": "TikTok Post Scraper Output",
        "description": "Post rows with engagement stats, author snapshot, music, media, hashtags/mentions; comment rows with replies; and explicit status rows for unavailable inputs.",
        "properties": {
          "dataset": {
            "type": "string",
            "title": "Dataset",
            "description": "All records: posts, comments/replies, and status rows. Use the dataset views to switch between them.",
            "template": "{{links.apiDefaultDatasetUrl}}/items"
          }
        }
      },
      "categories": [
        "SOCIAL_MEDIA"
      ],
      "defaultMemoryMbytes": 2048,
      "minMemoryMbytes": 1024,
      "storages": {
        "dataset": {
          "actorSpecification": 1,
          "title": "TikTok Posts, Profiles & Comments",
          "views": {
            "overview": {
              "title": "Posts Overview",
              "description": "One row per post with engagement metrics, author snapshot, and the extraction source.",
              "transformation": {
                "fields": [
                  "postId",
                  "postUrl",
                  "postType",
                  "caption",
                  "createdAt",
                  "playCount",
                  "likeCount",
                  "commentCount",
                  "shareCount",
                  "author",
                  "recordType",
                  "source",
                  "scrapedAt"
                ]
              },
              "display": {
                "component": "table",
                "properties": {
                  "postId": {
                    "label": "Post ID",
                    "format": "text"
                  },
                  "postUrl": {
                    "label": "Post URL",
                    "format": "link"
                  },
                  "postType": {
                    "label": "Type",
                    "format": "text"
                  },
                  "caption": {
                    "label": "Caption",
                    "format": "text"
                  },
                  "createdAt": {
                    "label": "Created At",
                    "format": "text"
                  },
                  "playCount": {
                    "label": "Plays",
                    "format": "number"
                  },
                  "likeCount": {
                    "label": "Likes",
                    "format": "number"
                  },
                  "commentCount": {
                    "label": "Comments",
                    "format": "number"
                  },
                  "shareCount": {
                    "label": "Shares",
                    "format": "number"
                  },
                  "author": {
                    "label": "Author",
                    "format": "object"
                  },
                  "recordType": {
                    "label": "Record",
                    "format": "text"
                  },
                  "source": {
                    "label": "Source",
                    "format": "text"
                  },
                  "scrapedAt": {
                    "label": "Scraped At",
                    "format": "text"
                  }
                }
              }
            },
            "comments": {
              "title": "Comments & Replies",
              "description": "Comment rows emitted by comments mode. Reply rows carry isReply=true.",
              "transformation": {
                "fields": [
                  "recordType",
                  "postId",
                  "postUrl",
                  "commentId",
                  "text",
                  "user",
                  "likeCount",
                  "replyCount",
                  "isReply",
                  "createdAt",
                  "language",
                  "source",
                  "scrapedAt"
                ]
              },
              "display": {
                "component": "table",
                "properties": {
                  "recordType": {
                    "label": "Type",
                    "format": "text"
                  },
                  "postId": {
                    "label": "Post ID",
                    "format": "text"
                  },
                  "postUrl": {
                    "label": "Post URL",
                    "format": "link"
                  },
                  "commentId": {
                    "label": "Comment ID",
                    "format": "text"
                  },
                  "text": {
                    "label": "Text",
                    "format": "text"
                  },
                  "user": {
                    "label": "User",
                    "format": "object"
                  },
                  "likeCount": {
                    "label": "Likes",
                    "format": "number"
                  },
                  "replyCount": {
                    "label": "Replies",
                    "format": "number"
                  },
                  "isReply": {
                    "label": "Is Reply",
                    "format": "boolean"
                  },
                  "createdAt": {
                    "label": "Created At",
                    "format": "text"
                  },
                  "language": {
                    "label": "Language",
                    "format": "text"
                  },
                  "source": {
                    "label": "Source",
                    "format": "text"
                  },
                  "scrapedAt": {
                    "label": "Scraped At",
                    "format": "text"
                  }
                }
              }
            },
            "status": {
              "title": "Unavailable Inputs",
              "description": "Explicit not_found/private/region/blocked status rows. These do not count toward Max rows.",
              "transformation": {
                "fields": [
                  "recordType",
                  "status",
                  "input",
                  "postId",
                  "message",
                  "httpStatus",
                  "source",
                  "scrapedAt"
                ]
              },
              "display": {
                "component": "table",
                "properties": {
                  "recordType": {
                    "label": "Record",
                    "format": "text"
                  },
                  "status": {
                    "label": "Status",
                    "format": "text"
                  },
                  "input": {
                    "label": "Input",
                    "format": "text"
                  },
                  "postId": {
                    "label": "Post ID",
                    "format": "text"
                  },
                  "message": {
                    "label": "Message",
                    "format": "text"
                  },
                  "httpStatus": {
                    "label": "HTTP",
                    "format": "number"
                  },
                  "source": {
                    "label": "Source",
                    "format": "text"
                  },
                  "scrapedAt": {
                    "label": "Scraped At",
                    "format": "text"
                  }
                }
              }
            }
          },
          "fields": {
            "recordType": {
              "type": "string",
              "title": "Record Type",
              "description": "post, comment, reply, or status"
            },
            "source": {
              "type": "string",
              "title": "Source",
              "description": "Extraction path: embed, detail, ssr, profile, comment_api, or short"
            },
            "scrapedAt": {
              "type": "string",
              "title": "Scraped At",
              "description": "ISO 8601 UTC timestamp when this row was built"
            },
            "status": {
              "type": "string",
              "title": "Status",
              "description": "Diagnostic status for a failed input: not_found, private, region, or blocked"
            },
            "input": {
              "type": "string",
              "title": "Input",
              "description": "The original input that produced a status row"
            },
            "message": {
              "type": "string",
              "title": "Message",
              "description": "Human-readable reason for a status row"
            },
            "httpStatus": {
              "type": "integer",
              "title": "HTTP Status",
              "description": "Last HTTP status observed for a failed input"
            },
            "postId": {
              "type": "string",
              "title": "Post ID",
              "description": "TikTok's unique post/video ID (string to preserve 64-bit precision)"
            },
            "postType": {
              "type": "string",
              "title": "Post Type",
              "description": "video or slideshow"
            },
            "postUrl": {
              "type": "string",
              "title": "Post URL",
              "description": "Canonical TikTok post URL"
            },
            "profileUsername": {
              "type": "string",
              "title": "Profile Username",
              "description": "Profile that produced this row in profiles mode"
            },
            "position": {
              "type": "integer",
              "title": "Position",
              "description": "1-based position within the profile feed"
            },
            "isPinned": {
              "type": "boolean",
              "title": "Is Pinned",
              "description": "Whether the profile has pinned this post"
            },
            "caption": {
              "type": "string",
              "title": "Caption",
              "description": "Post caption including hashtags and mentions"
            },
            "textLanguage": {
              "type": "string",
              "title": "Text Language",
              "description": "Detected caption language code (detail path)"
            },
            "locationCreated": {
              "type": "string",
              "title": "Location Created",
              "description": "Creator-set ISO country code"
            },
            "viewerRegion": {
              "type": "string",
              "title": "Request Region",
              "description": "Egress region TikTok inferred for the request; not the post's location"
            },
            "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": "Legacy flat play count"
            },
            "likeCount": {
              "type": "integer",
              "title": "Like Count",
              "description": "Legacy flat like count"
            },
            "commentCount": {
              "type": "integer",
              "title": "Comment Count",
              "description": "Legacy flat comment count"
            },
            "shareCount": {
              "type": "integer",
              "title": "Share Count",
              "description": "Legacy flat share count"
            },
            "collectCount": {
              "type": "integer",
              "title": "Collect Count",
              "description": "Legacy flat collect count"
            },
            "repostCount": {
              "type": "integer",
              "title": "Repost Count",
              "description": "Legacy flat repost count"
            },
            "stitchEnabled": {
              "type": "boolean",
              "title": "Stitch Enabled",
              "description": "Legacy flat flag"
            },
            "shareEnabled": {
              "type": "boolean",
              "title": "Share Enabled",
              "description": "Legacy flat flag"
            },
            "duetEnabled": {
              "type": "boolean",
              "title": "Duet Enabled",
              "description": "Legacy flat flag"
            },
            "commentStatus": {
              "type": "integer",
              "title": "Comment Status",
              "description": "Legacy flat flag (0=all, 1=friends, 2=none)"
            },
            "isAd": {
              "type": "boolean",
              "title": "Is Ad",
              "description": "Legacy flat flag"
            },
            "isAigc": {
              "type": "boolean",
              "title": "Is AIGC",
              "description": "Legacy flat flag"
            },
            "isECVideo": {
              "type": "boolean",
              "title": "Is E-Commerce Video",
              "description": "Legacy flat flag"
            },
            "privateItem": {
              "type": "boolean",
              "title": "Private Item",
              "description": "Legacy flat flag"
            },
            "forFriend": {
              "type": "boolean",
              "title": "For Friend",
              "description": "Legacy flat flag"
            },
            "originalItem": {
              "type": "boolean",
              "title": "Original Item",
              "description": "Legacy flat flag"
            },
            "diversificationLabels": {
              "type": "array",
              "title": "Diversification Labels",
              "items": {
                "type": "string"
              }
            },
            "channelTags": {
              "type": "array",
              "title": "Channel Tags",
              "items": {
                "type": "string"
              }
            },
            "warnInfo": {
              "type": "array",
              "title": "Warn Info"
            },
            "poi": {
              "type": "object",
              "title": "Point of Interest"
            },
            "AIGCDescription": {
              "type": "string",
              "title": "AIGC Description"
            },
            "adLabelVersion": {
              "type": "integer",
              "title": "Ad Label Version"
            },
            "mediaUrlExpiresAt": {
              "type": "string",
              "title": "Media URL Expires At",
              "description": "ISO 8601 UTC expiry of the signed media URLs (~48 hours)"
            },
            "mediaUrlAccessible": {
              "type": "boolean",
              "title": "Media URL Accessible",
              "description": "Result of the Range GET probe when validateMediaUrls is on"
            },
            "author": {
              "type": "object",
              "title": "Author",
              "description": "Creator snapshot at scrape time (legacy flat stats included)"
            },
            "authorStats": {
              "type": "object",
              "title": "Author Stats",
              "description": "Grouped follower/following/heart/video counts",
              "properties": {
                "followerCount": {
                  "type": "integer"
                },
                "followingCount": {
                  "type": "integer"
                },
                "heartCount": {
                  "type": "integer"
                },
                "videoCount": {
                  "type": "integer"
                }
              }
            },
            "stats": {
              "type": "object",
              "title": "Post Stats",
              "description": "Grouped engagement counts",
              "properties": {
                "playCount": {
                  "type": "integer"
                },
                "likeCount": {
                  "type": "integer"
                },
                "commentCount": {
                  "type": "integer"
                },
                "shareCount": {
                  "type": "integer"
                },
                "collectCount": {
                  "type": "integer"
                },
                "repostCount": {
                  "type": "integer"
                }
              }
            },
            "flags": {
              "type": "object",
              "title": "Post Flags",
              "description": "Grouped visibility, interaction, ad, and AI flags"
            },
            "music": {
              "type": "object",
              "title": "Music"
            },
            "video": {
              "type": "object",
              "title": "Video"
            },
            "images": {
              "type": "array",
              "title": "Slideshow Images",
              "description": "Ordered carousel images with ordinal, dimensions, and signed urls",
              "items": {
                "type": "object"
              }
            },
            "hashtags": {
              "type": "array",
              "title": "Hashtags",
              "items": {
                "type": "object"
              }
            },
            "mentions": {
              "type": "array",
              "title": "Mentions",
              "items": {
                "type": "object"
              }
            },
            "comments": {
              "type": "array",
              "title": "Comments",
              "description": "Comments attached to a post row, each optionally carrying a replies array",
              "items": {
                "type": "object"
              }
            },
            "commentId": {
              "type": "string",
              "title": "Comment ID"
            },
            "text": {
              "type": "string",
              "title": "Comment Text"
            },
            "user": {
              "type": "object",
              "title": "Commenter"
            },
            "replyCount": {
              "type": "integer",
              "title": "Reply Count"
            },
            "isPinnedByAuthor": {
              "type": "boolean",
              "title": "Pinned By Author"
            },
            "isLikedByAuthor": {
              "type": "boolean",
              "title": "Liked By Author"
            },
            "isReply": {
              "type": "boolean",
              "title": "Is Reply"
            },
            "replyToCommentId": {
              "type": "string",
              "title": "Reply To Comment ID"
            },
            "threadId": {
              "type": "string",
              "title": "Thread ID"
            },
            "language": {
              "type": "string",
              "title": "Language"
            },
            "textExtra": {
              "type": "array",
              "title": "Text Extra"
            },
            "item_control": {
              "type": "object",
              "title": "Item Control"
            },
            "contents": {
              "type": "array",
              "title": "Contents"
            },
            "effectStickers": {
              "type": "array",
              "title": "Effect Stickers"
            },
            "stickersOnItem": {
              "type": "array",
              "title": "Stickers On Item"
            }
          }
        }
      }
    },
    "buildNumber": "2.0.1",
    "imageDigest": "672bdbc690cf2200d33a5c87464234b54f5844c7720f0bc328e5eedff92138b4",
    "actVersion": {
      "sourceType": "SOURCE_FILES",
      "buildTag": "latest",
      "versionNumber": "2.0"
    },
    "usage": {
      "ACTOR_COMPUTE_UNITS": 0.03625444444444444
    }
  }
}