{
  "data": {
    "id": "0qqLJHUzNNtm8LCyD",
    "actId": "dslkxaZlYGt90DdOe",
    "userId": "IYgBDiXtHOijAaUel",
    "startedAt": "2026-10-25T15:38:22.176Z",
    "finishedAt": "2026-10-25T15:38:24.792Z",
    "status": "SUCCEEDED",
    "meta": {
      "origin": "CLI"
    },
    "stats": {
      "durationMillis": 2546,
      "runTimeSecs": 2.546,
      "computeUnits": 0.002828888888888889,
      "imageSizeBytes": 3103155200
    },
    "options": {
      "useCache": true,
      "betaPackages": false,
      "memoryMbytes": 4096,
      "diskMbytes": 8192
    },
    "createdByOrganizationMemberUserId": "QYo53EZkJk0prePWY",
    "exitCode": 0,
    "inputSchema": "{\n  \"title\": \"TikTok Downloader API Input\",\n  \"description\": \"Download TikTok videos (clean or watermarked), audio, covers, slideshow frames, and post metadata. Clean MP4 files are downloaded inside a browser session and re-hosted in Apify Key-Value Store; watermarked/direct assets can be re-hosted or returned as expiring CDN URLs.\",\n  \"type\": \"object\",\n  \"schemaVersion\": 1,\n  \"properties\": {\n    \"postUrls\": {\n      \"title\": \"Post URLs\",\n      \"type\": \"array\",\n      \"description\": \"TikTok post URLs or IDs. Accepts /@user/video/ID, /@user/photo/ID, /video/ID, /photo/ID, embed URLs (/embed/v2/ID), vm.tiktok.com / vt.tiktok.com / /t/ short links, m.tiktok.com/v/ID.html, and bare numeric post IDs. Duplicates are processed once.\",\n      \"editor\": \"stringList\",\n      \"items\": {\n        \"type\": \"string\"\n      },\n      \"prefill\": [\n        \"https://www.tiktok.com/@nba/video/7693329153680035102\"\n      ]\n    },\n    \"assetTypes\": {\n      \"title\": \"Asset Types\",\n      \"type\": \"array\",\n      \"description\": \"Assets to produce for each post. 'video' = MP4; 'audio' = TikTok sound; 'cover' = cover image; 'slideshow' = ordered photo frames; 'metadata' = post/author/stats/music row.\",\n      \"editor\": \"select\",\n      \"items\": {\n        \"type\": \"string\",\n        \"enum\": [\n          \"video\",\n          \"audio\",\n          \"cover\",\n          \"slideshow\",\n          \"metadata\"\n        ],\n        \"enumTitles\": [\n          \"Video (MP4)\",\n          \"Audio\",\n          \"Cover image\",\n          \"Slideshow frames\",\n          \"Metadata\"\n        ]\n      },\n      \"default\": [\n        \"video\"\n      ],\n      \"prefill\": [\n        \"video\"\n      ]\n    },\n    \"videoVariant\": {\n      \"title\": \"Video Variant\",\n      \"type\": \"string\",\n      \"description\": \"'clean' = no-watermark MP4 (requires a same-page browser session and is always re-hosted). 'watermarked' = logo copy, usable as an expiring CDN URL. 'both' = one row per variant.\",\n      \"editor\": \"select\",\n      \"enum\": [\n        \"clean\",\n        \"watermarked\",\n        \"both\"\n      ],\n      \"enumTitles\": [\n        \"Clean (no watermark)\",\n        \"Watermarked\",\n        \"Both variants\"\n      ],\n      \"default\": \"clean\",\n      \"prefill\": \"clean\"\n    },\n    \"quality\": {\n      \"title\": \"Video Quality\",\n      \"type\": \"string\",\n      \"description\": \"Target clean-video quality. 'best' sorts by resolution first, then bitrate. Watermarked copies are single-resolution.\",\n      \"editor\": \"select\",\n      \"enum\": [\n        \"best\",\n        \"1080p\",\n        \"720p\",\n        \"540p\",\n        \"360p\",\n        \"smallest\"\n      ],\n      \"enumTitles\": [\n        \"Best (highest resolution)\",\n        \"1080p\",\n        \"720p\",\n        \"540p\",\n        \"360p\",\n        \"Smallest file\"\n      ],\n      \"default\": \"best\",\n      \"prefill\": \"540p\"\n    },\n    \"downloadToKv\": {\n      \"title\": \"Download Files to Apify Key-Value Store\",\n      \"type\": \"boolean\",\n      \"description\": \"Store watermarked video, audio, cover, and slideshow files in the run's Key-Value Store and return stable kvsUrl links. Clean video is always stored because its source URL is session-bound. Set false to return expiring CDN URLs only.\",\n      \"default\": true,\n      \"prefill\": true\n    },\n    \"maxFileSizeMb\": {\n      \"title\": \"Max File Size (MB)\",\n      \"type\": \"integer\",\n      \"description\": \"Per-file size cap. Files above the cap produce a 'too_large' status row instead of a partial download.\",\n      \"minimum\": 1,\n      \"maximum\": 100,\n      \"default\": 100,\n      \"prefill\": 100\n    },\n    \"maxItems\": {\n      \"title\": \"Max Posts\",\n      \"type\": \"integer\",\n      \"description\": \"Maximum number of unique posts to process. Inputs beyond the cap receive a 'limit_reached' status row.\",\n      \"minimum\": 1,\n      \"maximum\": 1000,\n      \"default\": 1000,\n      \"prefill\": 1\n    },\n    \"maxConcurrency\": {\n      \"title\": \"Max Concurrency\",\n      \"type\": \"integer\",\n      \"description\": \"Number of posts processed in parallel (1-3). Embed requests are retried with backoff when TikTok rate-limits them.\",\n      \"minimum\": 1,\n      \"maximum\": 3,\n      \"default\": 3,\n      \"prefill\": 1\n    },\n    \"proxyConfiguration\": {\n      \"title\": \"Proxy Configuration\",\n      \"type\": \"object\",\n      \"description\": \"Optional. The actor uses a direct connection by default and does not require a proxy. Use this only if you want to route requests through Apify Proxy.\",\n      \"editor\": \"proxy\"\n    }\n  },\n  \"required\": [\n    \"postUrls\"\n  ]\n}",
    "readme": "# TikTok Video, Audio & Cover Downloader\n\nDownload TikTok posts as **clean no-watermark MP4**, watermarked MP4, sound, cover images, slideshow frames, and post metadata. Each file can be re-hosted in Apify Key-Value Store with a stable public link, and every input gets a dataset row — including posts that are deleted, private, region-blocked, age-gated, duplicated, or too large to download.\n\nNo TikTok account, cookies, API key, or proxy setup is required.\n\n## What you get\n\n- One row per asset: video variant, audio, cover, slideshow frame, or metadata\n- Explicit status rows instead of silent skips\n- Clean MP4 (no watermark) downloaded inside the page session and re-hosted to Apify storage\n- Watermarked MP4, covers, audio, and slideshow images returned as stable KV links and/or expiring CDN URLs\n- Resolution-first quality selection (`best`, `1080p`, `720p`, `540p`, `360p`, `smallest`)\n- Post and author metadata: caption, create time, stats, sound details, post type\n- Short-link support (`vm.tiktok.com`, `vt.tiktok.com`, `/t/`) and embed/bare-ID inputs\n- Duplicate detection, `maxItems`, `maxFileSizeMb`, and 1–3 way concurrency\n\n## How it works\n\nTikTok exposes two different media paths, and this actor uses both:\n\n1. **Embed fast path** — the public embed page returns the watermarked MP4, cover, sound, slideshow images, author, stats, and caption without a browser or login.\n2. **Browser page session** — the clean `playAddr` URL is bound to first-party cookies from the same page load and returns `403` when fetched outside that session. The actor opens the post in Chromium, downloads the selected clean stream inside the session, and stores it in Apify Key-Value Store so the output link keeps working.\n\nWatermarked and direct assets are downloaded to Key-Value Store when `downloadToKv` is `true`. When it is `false`, their source URLs are returned instead. Clean video is always downloaded and re-hosted because a bare source URL would not work for you.\n\n## Input\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `postUrls` | array of strings | required | Canonical post URLs, short links, embed URLs, or numeric post IDs. Duplicates are processed once. |\n| `assetTypes` | multi-select | `[\"video\"]` | `video`, `audio`, `cover`, `slideshow`, `metadata`. |\n| `videoVariant` | select | `clean` | `clean`, `watermarked`, or `both`. |\n| `quality` | select | `best` | `best` sorts by resolution then bitrate. `1080p`, `720p`, `540p`, `360p`, `smallest` target the closest available stream. Watermarked copies are single-resolution. |\n| `downloadToKv` | boolean | `true` | Store watermarked video, audio, cover, and slideshow files and return `kvsUrl`. Clean video is always stored. |\n| `maxFileSizeMb` | integer | `100` | Per-file cap (1–100 MB). Larger files produce a `too_large` row. |\n| `maxItems` | integer | `1000` | Maximum unique posts to process (1–1000). Extra inputs get a `limit_reached` row. |\n| `maxConcurrency` | integer | `3` | Posts processed in parallel (1–3). |\n| `proxyConfiguration` | object | none | Optional. The actor uses a direct connection by default. |\n\n### Example — one clean video at 540p\n\n```json\n{\n  \"postUrls\": [\"https://www.tiktok.com/@nba/video/7693329153680035102\"],\n  \"assetTypes\": [\"video\"],\n  \"videoVariant\": \"clean\",\n  \"quality\": \"540p\",\n  \"downloadToKv\": true,\n  \"maxItems\": 1\n}\n```\n\n### Example — both video variants, cover, and metadata\n\n```json\n{\n  \"postUrls\": [\"https://www.tiktok.com/@nba/video/7693329153680035102\"],\n  \"assetTypes\": [\"video\", \"cover\", \"metadata\"],\n  \"videoVariant\": \"both\",\n  \"quality\": \"720p\",\n  \"downloadToKv\": true\n}\n```\n\n### Example — slideshow frames and sound\n\n```json\n{\n  \"postUrls\": [\"https://www.tiktok.com/@khaby.lame/photo/7574844184952081686\"],\n  \"assetTypes\": [\"slideshow\", \"audio\", \"metadata\"],\n  \"downloadToKv\": true\n}\n```\n\n### Example — URL-only mode for direct assets\n\n```json\n{\n  \"postUrls\": [\"https://www.tiktok.com/@nba/video/7693329153680035102\"],\n  \"assetTypes\": [\"audio\", \"cover\"],\n  \"downloadToKv\": false\n}\n```\n\n## Output\n\nEach dataset row contains the non-empty fields that apply:\n\n- `postId`, `postUrl`, `postType`\n- `assetType`: `video`, `audio`, `cover`, `slideshow`, `metadata`, or `post` for post-level status rows\n- `variant`: `clean` or `watermarked` for video\n- `quality`: selected quality label\n- `ordinal`: slideshow frame index\n- `mimeType`, `byteSize`, `width`, `height`, `duration`\n- `kvsKey`, `kvsUrl` for stored files\n- `originalUrl`, `expiresAt` for the source CDN URL\n- `downloadedAt`, `requiresSession`\n- `status`, `error`\n- Metadata rows add `caption`, `createTime`, author fields, play/like/comment/share/collect counts, music fields, and `imageCount`\n\n### Status values\n\n| Status | Meaning |\n|---|---|\n| `ok` | At least one requested asset or metadata row delivered |\n| `not_found` | Deleted, removed, or unavailable post |\n| `private` | Private account or friends-only post |\n| `region_blocked` | Not available in the current region |\n| `login_required` | Age-gated or login-gated post |\n| `blocked` | TikTok bot wall or rate limit after retries |\n| `unsupported_url` | Input is not a recognizable TikTok post |\n| `short_link_dead` | Short link redirected to a homepage/profile, not a post |\n| `no_video` | Video requested for a slideshow post |\n| `no_slideshow` | Slideshow requested for a video post |\n| `no_audio` | No sound track exposed |\n| `no_cover` | No cover image exposed |\n| `no_assets` | Post resolved but none of the requested assets were available |\n| `download_failed` | CDN download or Key-Value Store write failed |\n| `too_large` | File exceeds `maxFileSizeMb` |\n| `duplicate` | Same post ID appeared earlier in the input |\n| `limit_reached` | `maxItems` cap reached before this input was processed |\n\n## URL expiry and storage\n\n- TikTok CDN URLs (`originalUrl`) are signed and expire in **about 48 hours** (some music URLs expire within 24 hours). They are useful for traceability, not long-term hosting.\n- `kvsUrl` points to the run's Apify Key-Value Store and lasts for that store's retention period. Download important files to your own storage.\n- Clean video source URLs only work with the extracting page session (`requiresSession: true`). That is why the actor always downloads and re-hosts clean video instead of selling a bare URL.\n\n## Limits and behaviour\n\n- **Caps:** 1–1000 posts per run, 3 concurrent posts, 100 MB per file.\n- **Deduplication:** the first occurrence of a post ID is processed; later occurrences get a `duplicate` row.\n- **Clean video:** requires a browser session; if the page is blocked, the actor returns a status row for the clean asset while still delivering metadata/watermarked assets when the embed fast path succeeded.\n- **Region, private, and age-gated posts:** no login and no user cookies are used. Posts that require either are reported with a status row rather than silently omitted.\n- **Field drift:** the parser accepts both `imagePost.images` (modern page data) and `imagePostInfo.displayImages` (embed/legacy), and MIME types are taken from the CDN response rather than assumed.\n- **Watermarked resolution:** TikTok exposes one watermarked copy per post, so `quality` affects clean video only.\n\n## FAQ\n\n**Do I need a TikTok account or cookies?**\nNo. The actor uses public pages and browser-generated first-party cookies only.\n\n**Why can't I get a no-watermark URL without downloading?**\nTikTok's clean `playAddr` returns `403 text/html` unless it is fetched with cookies from the same page session. The actor downloads the file in that session and re-hosts it.\n\n**How long do links stay valid?**\n`kvsUrl` lasts for the Key-Value Store retention period. `originalUrl` is a signed CDN URL that expires in about 48 hours.\n\n**What happens to posts that cannot be downloaded?**\nThey produce a status row with `status` and `error`, so you can audit the run instead of diffing post IDs.\n\n**Are short links supported?**\nYes. `vm.tiktok.com`, `vt.tiktok.com`, and `/t/` links are resolved with an HTTP redirect. A dead link gets `short_link_dead`.\n\n**Can I use a proxy?**\nIt is optional. The actor is direct by default. If you want to route requests through Apify Proxy, select a proxy configuration in the input.\n\n**Why is a clean video row missing on a slideshow?**\nSlideshows have no video file. Request `slideshow` or `audio` instead; a `no_video` status row is emitted when video is requested.\n\n**Is this affiliated with TikTok?**\nNo. It is an independent tool for publicly available TikTok media. Only download content you have the right to use.\n",
    "actorDefinition": {
      "actorSpecification": 1,
      "name": "tiktok-video-audio-and-cover-downloader",
      "title": "TikTok Video, Audio & Cover Downloader",
      "description": "Download TikTok posts as clean no-watermark MP4 or watermarked MP4, plus audio, covers, slideshow frames and metadata. Returns stable Apify Key-Value Store links, source URLs with expiry, quality selection and explicit status rows for private, deleted, region-blocked, age-gated and oversized posts.",
      "version": "2.0",
      "buildTag": "latest",
      "dockerfile": "./Dockerfile",
      "readme": "# TikTok Video, Audio & Cover Downloader\n\nDownload TikTok posts as **clean no-watermark MP4**, watermarked MP4, sound, cover images, slideshow frames, and post metadata. Each file can be re-hosted in Apify Key-Value Store with a stable public link, and every input gets a dataset row — including posts that are deleted, private, region-blocked, age-gated, duplicated, or too large to download.\n\nNo TikTok account, cookies, API key, or proxy setup is required.\n\n## What you get\n\n- One row per asset: video variant, audio, cover, slideshow frame, or metadata\n- Explicit status rows instead of silent skips\n- Clean MP4 (no watermark) downloaded inside the page session and re-hosted to Apify storage\n- Watermarked MP4, covers, audio, and slideshow images returned as stable KV links and/or expiring CDN URLs\n- Resolution-first quality selection (`best`, `1080p`, `720p`, `540p`, `360p`, `smallest`)\n- Post and author metadata: caption, create time, stats, sound details, post type\n- Short-link support (`vm.tiktok.com`, `vt.tiktok.com`, `/t/`) and embed/bare-ID inputs\n- Duplicate detection, `maxItems`, `maxFileSizeMb`, and 1–3 way concurrency\n\n## How it works\n\nTikTok exposes two different media paths, and this actor uses both:\n\n1. **Embed fast path** — the public embed page returns the watermarked MP4, cover, sound, slideshow images, author, stats, and caption without a browser or login.\n2. **Browser page session** — the clean `playAddr` URL is bound to first-party cookies from the same page load and returns `403` when fetched outside that session. The actor opens the post in Chromium, downloads the selected clean stream inside the session, and stores it in Apify Key-Value Store so the output link keeps working.\n\nWatermarked and direct assets are downloaded to Key-Value Store when `downloadToKv` is `true`. When it is `false`, their source URLs are returned instead. Clean video is always downloaded and re-hosted because a bare source URL would not work for you.\n\n## Input\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `postUrls` | array of strings | required | Canonical post URLs, short links, embed URLs, or numeric post IDs. Duplicates are processed once. |\n| `assetTypes` | multi-select | `[\"video\"]` | `video`, `audio`, `cover`, `slideshow`, `metadata`. |\n| `videoVariant` | select | `clean` | `clean`, `watermarked`, or `both`. |\n| `quality` | select | `best` | `best` sorts by resolution then bitrate. `1080p`, `720p`, `540p`, `360p`, `smallest` target the closest available stream. Watermarked copies are single-resolution. |\n| `downloadToKv` | boolean | `true` | Store watermarked video, audio, cover, and slideshow files and return `kvsUrl`. Clean video is always stored. |\n| `maxFileSizeMb` | integer | `100` | Per-file cap (1–100 MB). Larger files produce a `too_large` row. |\n| `maxItems` | integer | `1000` | Maximum unique posts to process (1–1000). Extra inputs get a `limit_reached` row. |\n| `maxConcurrency` | integer | `3` | Posts processed in parallel (1–3). |\n| `proxyConfiguration` | object | none | Optional. The actor uses a direct connection by default. |\n\n### Example — one clean video at 540p\n\n```json\n{\n  \"postUrls\": [\"https://www.tiktok.com/@nba/video/7693329153680035102\"],\n  \"assetTypes\": [\"video\"],\n  \"videoVariant\": \"clean\",\n  \"quality\": \"540p\",\n  \"downloadToKv\": true,\n  \"maxItems\": 1\n}\n```\n\n### Example — both video variants, cover, and metadata\n\n```json\n{\n  \"postUrls\": [\"https://www.tiktok.com/@nba/video/7693329153680035102\"],\n  \"assetTypes\": [\"video\", \"cover\", \"metadata\"],\n  \"videoVariant\": \"both\",\n  \"quality\": \"720p\",\n  \"downloadToKv\": true\n}\n```\n\n### Example — slideshow frames and sound\n\n```json\n{\n  \"postUrls\": [\"https://www.tiktok.com/@khaby.lame/photo/7574844184952081686\"],\n  \"assetTypes\": [\"slideshow\", \"audio\", \"metadata\"],\n  \"downloadToKv\": true\n}\n```\n\n### Example — URL-only mode for direct assets\n\n```json\n{\n  \"postUrls\": [\"https://www.tiktok.com/@nba/video/7693329153680035102\"],\n  \"assetTypes\": [\"audio\", \"cover\"],\n  \"downloadToKv\": false\n}\n```\n\n## Output\n\nEach dataset row contains the non-empty fields that apply:\n\n- `postId`, `postUrl`, `postType`\n- `assetType`: `video`, `audio`, `cover`, `slideshow`, `metadata`, or `post` for post-level status rows\n- `variant`: `clean` or `watermarked` for video\n- `quality`: selected quality label\n- `ordinal`: slideshow frame index\n- `mimeType`, `byteSize`, `width`, `height`, `duration`\n- `kvsKey`, `kvsUrl` for stored files\n- `originalUrl`, `expiresAt` for the source CDN URL\n- `downloadedAt`, `requiresSession`\n- `status`, `error`\n- Metadata rows add `caption`, `createTime`, author fields, play/like/comment/share/collect counts, music fields, and `imageCount`\n\n### Status values\n\n| Status | Meaning |\n|---|---|\n| `ok` | At least one requested asset or metadata row delivered |\n| `not_found` | Deleted, removed, or unavailable post |\n| `private` | Private account or friends-only post |\n| `region_blocked` | Not available in the current region |\n| `login_required` | Age-gated or login-gated post |\n| `blocked` | TikTok bot wall or rate limit after retries |\n| `unsupported_url` | Input is not a recognizable TikTok post |\n| `short_link_dead` | Short link redirected to a homepage/profile, not a post |\n| `no_video` | Video requested for a slideshow post |\n| `no_slideshow` | Slideshow requested for a video post |\n| `no_audio` | No sound track exposed |\n| `no_cover` | No cover image exposed |\n| `no_assets` | Post resolved but none of the requested assets were available |\n| `download_failed` | CDN download or Key-Value Store write failed |\n| `too_large` | File exceeds `maxFileSizeMb` |\n| `duplicate` | Same post ID appeared earlier in the input |\n| `limit_reached` | `maxItems` cap reached before this input was processed |\n\n## URL expiry and storage\n\n- TikTok CDN URLs (`originalUrl`) are signed and expire in **about 48 hours** (some music URLs expire within 24 hours). They are useful for traceability, not long-term hosting.\n- `kvsUrl` points to the run's Apify Key-Value Store and lasts for that store's retention period. Download important files to your own storage.\n- Clean video source URLs only work with the extracting page session (`requiresSession: true`). That is why the actor always downloads and re-hosts clean video instead of selling a bare URL.\n\n## Limits and behaviour\n\n- **Caps:** 1–1000 posts per run, 3 concurrent posts, 100 MB per file.\n- **Deduplication:** the first occurrence of a post ID is processed; later occurrences get a `duplicate` row.\n- **Clean video:** requires a browser session; if the page is blocked, the actor returns a status row for the clean asset while still delivering metadata/watermarked assets when the embed fast path succeeded.\n- **Region, private, and age-gated posts:** no login and no user cookies are used. Posts that require either are reported with a status row rather than silently omitted.\n- **Field drift:** the parser accepts both `imagePost.images` (modern page data) and `imagePostInfo.displayImages` (embed/legacy), and MIME types are taken from the CDN response rather than assumed.\n- **Watermarked resolution:** TikTok exposes one watermarked copy per post, so `quality` affects clean video only.\n\n## FAQ\n\n**Do I need a TikTok account or cookies?**\nNo. The actor uses public pages and browser-generated first-party cookies only.\n\n**Why can't I get a no-watermark URL without downloading?**\nTikTok's clean `playAddr` returns `403 text/html` unless it is fetched with cookies from the same page session. The actor downloads the file in that session and re-hosts it.\n\n**How long do links stay valid?**\n`kvsUrl` lasts for the Key-Value Store retention period. `originalUrl` is a signed CDN URL that expires in about 48 hours.\n\n**What happens to posts that cannot be downloaded?**\nThey produce a status row with `status` and `error`, so you can audit the run instead of diffing post IDs.\n\n**Are short links supported?**\nYes. `vm.tiktok.com`, `vt.tiktok.com`, and `/t/` links are resolved with an HTTP redirect. A dead link gets `short_link_dead`.\n\n**Can I use a proxy?**\nIt is optional. The actor is direct by default. If you want to route requests through Apify Proxy, select a proxy configuration in the input.\n\n**Why is a clean video row missing on a slideshow?**\nSlideshows have no video file. Request `slideshow` or `audio` instead; a `no_video` status row is emitted when video is requested.\n\n**Is this affiliated with TikTok?**\nNo. It is an independent tool for publicly available TikTok media. Only download content you have the right to use.\n",
      "input": {
        "title": "TikTok Downloader API Input",
        "description": "Download TikTok videos (clean or watermarked), audio, covers, slideshow frames, and post metadata. Clean MP4 files are downloaded inside a browser session and re-hosted in Apify Key-Value Store; watermarked/direct assets can be re-hosted or returned as expiring CDN URLs.",
        "type": "object",
        "schemaVersion": 1,
        "properties": {
          "postUrls": {
            "title": "Post URLs",
            "type": "array",
            "description": "TikTok post URLs or IDs. Accepts /@user/video/ID, /@user/photo/ID, /video/ID, /photo/ID, embed URLs (/embed/v2/ID), vm.tiktok.com / vt.tiktok.com / /t/ short links, m.tiktok.com/v/ID.html, and bare numeric post IDs. Duplicates are processed once.",
            "editor": "stringList",
            "items": {
              "type": "string"
            },
            "prefill": [
              "https://www.tiktok.com/@nba/video/7693329153680035102"
            ]
          },
          "assetTypes": {
            "title": "Asset Types",
            "type": "array",
            "description": "Assets to produce for each post. 'video' = MP4; 'audio' = TikTok sound; 'cover' = cover image; 'slideshow' = ordered photo frames; 'metadata' = post/author/stats/music row.",
            "editor": "select",
            "items": {
              "type": "string",
              "enum": [
                "video",
                "audio",
                "cover",
                "slideshow",
                "metadata"
              ],
              "enumTitles": [
                "Video (MP4)",
                "Audio",
                "Cover image",
                "Slideshow frames",
                "Metadata"
              ]
            },
            "default": [
              "video"
            ],
            "prefill": [
              "video"
            ]
          },
          "videoVariant": {
            "title": "Video Variant",
            "type": "string",
            "description": "'clean' = no-watermark MP4 (requires a same-page browser session and is always re-hosted). 'watermarked' = logo copy, usable as an expiring CDN URL. 'both' = one row per variant.",
            "editor": "select",
            "enum": [
              "clean",
              "watermarked",
              "both"
            ],
            "enumTitles": [
              "Clean (no watermark)",
              "Watermarked",
              "Both variants"
            ],
            "default": "clean",
            "prefill": "clean"
          },
          "quality": {
            "title": "Video Quality",
            "type": "string",
            "description": "Target clean-video quality. 'best' sorts by resolution first, then bitrate. Watermarked copies are single-resolution.",
            "editor": "select",
            "enum": [
              "best",
              "1080p",
              "720p",
              "540p",
              "360p",
              "smallest"
            ],
            "enumTitles": [
              "Best (highest resolution)",
              "1080p",
              "720p",
              "540p",
              "360p",
              "Smallest file"
            ],
            "default": "best",
            "prefill": "540p"
          },
          "downloadToKv": {
            "title": "Download Files to Apify Key-Value Store",
            "type": "boolean",
            "description": "Store watermarked video, audio, cover, and slideshow files in the run's Key-Value Store and return stable kvsUrl links. Clean video is always stored because its source URL is session-bound. Set false to return expiring CDN URLs only.",
            "default": true,
            "prefill": true
          },
          "maxFileSizeMb": {
            "title": "Max File Size (MB)",
            "type": "integer",
            "description": "Per-file size cap. Files above the cap produce a 'too_large' status row instead of a partial download.",
            "minimum": 1,
            "maximum": 100,
            "default": 100,
            "prefill": 100
          },
          "maxItems": {
            "title": "Max Posts",
            "type": "integer",
            "description": "Maximum number of unique posts to process. Inputs beyond the cap receive a 'limit_reached' status row.",
            "minimum": 1,
            "maximum": 1000,
            "default": 1000,
            "prefill": 1
          },
          "maxConcurrency": {
            "title": "Max Concurrency",
            "type": "integer",
            "description": "Number of posts processed in parallel (1-3). Embed requests are retried with backoff when TikTok rate-limits them.",
            "minimum": 1,
            "maximum": 3,
            "default": 3,
            "prefill": 1
          },
          "proxyConfiguration": {
            "title": "Proxy Configuration",
            "type": "object",
            "description": "Optional. The actor uses a direct connection by default and does not require a proxy. Use this only if you want to route requests through Apify Proxy.",
            "editor": "proxy"
          }
        },
        "required": [
          "postUrls"
        ]
      },
      "output": {
        "actorOutputSchemaVersion": 1,
        "title": "TikTok Video, Audio & Cover Downloader Output",
        "description": "One dataset row per asset (video clean/watermarked, audio, cover, slideshow frame, metadata) plus explicit status rows for unavailable, private, region-blocked, age-gated, duplicate, over-limit, and too-large inputs. Each row carries post identity, variant, quality, MIME type, byte size, dimensions, duration, stable Key-Value Store link, original CDN URL, expiry, and status.",
        "properties": {
          "posts": {
            "type": "string",
            "title": "Downloaded Posts and Assets",
            "description": "Dataset containing downloaded TikTok assets, post metadata, and status rows.",
            "template": "{{links.apiDefaultDatasetUrl}}/items"
          }
        }
      },
      "categories": [
        "SOCIAL_MEDIA"
      ],
      "storages": {
        "dataset": {
          "actorSpecification": 1,
          "title": "TikTok Downloads",
          "fields": {
            "postId": {
              "type": "string",
              "title": "Post ID",
              "description": "Unique TikTok post ID"
            },
            "postUrl": {
              "type": "string",
              "title": "Post URL",
              "description": "Canonical URL to the TikTok post"
            },
            "postType": {
              "type": "string",
              "title": "Post Type",
              "description": "video or slideshow"
            },
            "assetType": {
              "type": "string",
              "title": "Asset Type",
              "description": "video, audio, cover, slideshow, metadata, or post for post-level status rows"
            },
            "variant": {
              "type": "string",
              "title": "Variant",
              "description": "Video variant: clean or watermarked"
            },
            "quality": {
              "type": "string",
              "title": "Quality",
              "description": "Selected quality label (e.g. 1080p, 720p, 540p)"
            },
            "ordinal": {
              "type": "integer",
              "title": "Ordinal",
              "description": "0-based slideshow frame index"
            },
            "mimeType": {
              "type": "string",
              "title": "MIME Type",
              "description": "MIME type of the stored file"
            },
            "byteSize": {
              "type": "integer",
              "title": "Byte Size",
              "description": "File size in bytes"
            },
            "width": {
              "type": "integer",
              "title": "Width",
              "description": "Media width in pixels"
            },
            "height": {
              "type": "integer",
              "title": "Height",
              "description": "Media height in pixels"
            },
            "duration": {
              "type": "number",
              "title": "Duration (s)",
              "description": "Video or audio duration in seconds"
            },
            "kvsKey": {
              "type": "string",
              "title": "KVS Key",
              "description": "Key in Apify Key-Value Store"
            },
            "kvsUrl": {
              "type": "string",
              "title": "KVS URL",
              "description": "Stable public Apify Key-Value Store URL for the downloaded file"
            },
            "originalUrl": {
              "type": "string",
              "title": "Original URL",
              "description": "Source TikTok CDN URL (expires in about 48 hours)"
            },
            "expiresAt": {
              "type": "string",
              "title": "Expires At",
              "description": "ISO 8601 expiry of the source CDN URL, when signed"
            },
            "downloadedAt": {
              "type": "string",
              "title": "Downloaded At",
              "description": "ISO 8601 timestamp when the asset was stored or metadata was captured"
            },
            "requiresSession": {
              "type": "boolean",
              "title": "Requires Session",
              "description": "True when the source URL only works with the extracting browser session (clean video)"
            },
            "status": {
              "type": "string",
              "title": "Status",
              "description": "ok, not_found, private, region_blocked, login_required, blocked, unsupported_url, short_link_dead, no_video, no_slideshow, no_audio, no_cover, no_assets, download_failed, too_large, duplicate, or limit_reached"
            },
            "error": {
              "type": "string",
              "title": "Error",
              "description": "Human-readable explanation for non-ok status rows"
            },
            "caption": {
              "type": "string",
              "title": "Caption",
              "description": "Post caption (metadata rows)"
            },
            "createTime": {
              "type": "integer",
              "title": "Create Time",
              "description": "Unix timestamp of the post (metadata rows)"
            },
            "authorUsername": {
              "type": "string",
              "title": "Author Username",
              "description": "TikTok username (metadata rows)"
            },
            "authorNickname": {
              "type": "string",
              "title": "Author Nickname",
              "description": "Display name (metadata rows)"
            },
            "authorId": {
              "type": "string",
              "title": "Author ID",
              "description": "TikTok user ID (metadata rows)"
            },
            "authorVerified": {
              "type": "boolean",
              "title": "Author Verified",
              "description": "Whether the author is verified (metadata rows)"
            },
            "playCount": {
              "type": "integer",
              "title": "Play Count",
              "description": "Post play count (metadata rows)"
            },
            "likeCount": {
              "type": "integer",
              "title": "Like Count",
              "description": "Post like/digg count (metadata rows)"
            },
            "commentCount": {
              "type": "integer",
              "title": "Comment Count",
              "description": "Post comment count (metadata rows)"
            },
            "shareCount": {
              "type": "integer",
              "title": "Share Count",
              "description": "Post share count (metadata rows)"
            },
            "collectCount": {
              "type": "integer",
              "title": "Collect Count",
              "description": "Post collect count (metadata rows)"
            },
            "musicId": {
              "type": "string",
              "title": "Music ID",
              "description": "TikTok sound ID (metadata/audio rows)"
            },
            "musicTitle": {
              "type": "string",
              "title": "Music Title",
              "description": "Sound title (metadata/audio rows)"
            },
            "musicAuthor": {
              "type": "string",
              "title": "Music Author",
              "description": "Sound author (metadata rows)"
            },
            "musicDuration": {
              "type": "integer",
              "title": "Music Duration",
              "description": "Sound duration in seconds (metadata rows)"
            },
            "imageCount": {
              "type": "integer",
              "title": "Image Count",
              "description": "Number of slideshow images (metadata rows)"
            },
            "imageWidth": {
              "type": "integer",
              "title": "Image Width",
              "description": "Slideshow frame width"
            },
            "imageHeight": {
              "type": "integer",
              "title": "Image Height",
              "description": "Slideshow frame height"
            }
          },
          "views": {
            "overview": {
              "title": "Downloaded Assets",
              "transformation": {
                "fields": [
                  "postId",
                  "postUrl",
                  "assetType",
                  "variant",
                  "quality",
                  "mimeType",
                  "byteSize",
                  "status",
                  "kvsUrl",
                  "originalUrl",
                  "downloadedAt"
                ]
              },
              "display": {
                "component": "table",
                "properties": {
                  "postId": {
                    "label": "Post ID",
                    "format": "text"
                  },
                  "postUrl": {
                    "label": "Post URL",
                    "format": "link"
                  },
                  "assetType": {
                    "label": "Asset",
                    "format": "text"
                  },
                  "variant": {
                    "label": "Variant",
                    "format": "text"
                  },
                  "quality": {
                    "label": "Quality",
                    "format": "text"
                  },
                  "mimeType": {
                    "label": "MIME",
                    "format": "text"
                  },
                  "byteSize": {
                    "label": "Bytes",
                    "format": "number"
                  },
                  "status": {
                    "label": "Status",
                    "format": "text"
                  },
                  "kvsUrl": {
                    "label": "Download",
                    "format": "link"
                  },
                  "originalUrl": {
                    "label": "Source",
                    "format": "link"
                  },
                  "downloadedAt": {
                    "label": "Downloaded At",
                    "format": "text"
                  }
                }
              }
            },
            "issues": {
              "title": "Status Rows",
              "transformation": {
                "fields": [
                  "postId",
                  "postUrl",
                  "assetType",
                  "variant",
                  "status",
                  "error"
                ]
              },
              "display": {
                "component": "table",
                "properties": {
                  "postId": {
                    "label": "Post ID",
                    "format": "text"
                  },
                  "postUrl": {
                    "label": "Post URL",
                    "format": "link"
                  },
                  "assetType": {
                    "label": "Asset",
                    "format": "text"
                  },
                  "variant": {
                    "label": "Variant",
                    "format": "text"
                  },
                  "status": {
                    "label": "Status",
                    "format": "text"
                  },
                  "error": {
                    "label": "Error",
                    "format": "text"
                  }
                }
              }
            }
          }
        }
      }
    },
    "buildNumber": "2.0.1",
    "imageDigest": "879f561f635f8b997f6f40bdbae3583bc907564c09c32c3fcd41f17f8d5f3813",
    "actVersion": {
      "sourceType": "SOURCE_FILES",
      "buildTag": "latest",
      "versionNumber": "2.0"
    },
    "usage": {
      "ACTOR_COMPUTE_UNITS": 0.002828888888888889
    }
  }
}