Nimbryxo Studio

TikTok Explore Feed Trending Post Scraper

Collect trending posts from TikTok's Explore feed across one or more content categories, with engagement stats, author snapshot, sound and video details. Can also try to discover category IDs.

Input schema

Input fields, types, required flags, descriptions, defaults, and prefills come from the published definition. Prefill examples are distinct from defaults.

FieldTypeRequiredDescription
categoriesarrayrequiredCategories to collect. Accepts names, numeric IDs, or legacy {id,name} objects. All=120; topics 100-119: Anime & Comics, Shows, Beauty Care, Games, Comedy, Daily Life, Family, Relationship, Drama, Outfit, Lipsync, Food, Sports, Animals, Society, Cars, Education, Fitness & Health, Technology, Singing & Dancing. 123 is accepted as a legacy alias for All. 121/122 (empty upstream) and 124+ (invalid upstream) are skipped with a warning.
maxItemsPerCategoryintegeroptionalHard cap on emitted posts per category. The cap is applied before any page is pushed, so a 30-item page never overshoots. The default is 3 because media rehosting is on by default and adds one browser page plus several HTTP media fetches per emitted post.
maxItemsintegeroptionalLegacy alias for maxItemsPerCategory. It is used when maxItemsPerCategory is absent or exactly the default 3; an explicitly non-default maxItemsPerCategory wins.
Show all input fields
FieldTypeRequiredDescription
categoriesarrayrequiredCategories to collect. Accepts names, numeric IDs, or legacy {id,name} objects. All=120; topics 100-119: Anime & Comics, Shows, Beauty Care, Games, Comedy, Daily Life, Family, Relationship, Drama, Outfit, Lipsync, Food, Sports, Animals, Society, Cars, Education, Fitness & Health, Technology, Singing & Dancing. 123 is accepted as a legacy alias for All. 121/122 (empty upstream) and 124+ (invalid upstream) are skipped with a warning.
maxItemsPerCategoryintegeroptionalHard cap on emitted posts per category. The cap is applied before any page is pushed, so a 30-item page never overshoots. The default is 3 because media rehosting is on by default and adds one browser page plus several HTTP media fetches per emitted post.
maxItemsintegeroptionalLegacy alias for maxItemsPerCategory. It is used when maxItemsPerCategory is absent or exactly the default 3; an explicitly non-default maxItemsPerCategory wins.
categoryIdintegeroptionalLegacy shorthand for one category. It replaces the categories list. 100-120 are accepted; 123 maps to 120; other values are skipped with a warning.
autoDiscoverCategoriesbooleanoptionalWhen true, an empty or All-only selection expands to all 21 verified Explore categories. If specific categories are selected, the missing categories are appended.
countriesarrayoptionalOptional ISO 3166-1 alpha-2 markets. Empty means the actor's current egress and no proxy. Each selected country uses a per-country Apify proxy (AUTO first, residential only after a proven block). The feed follows the egress IP, not a query parameter.
countrystringoptionalSingle-country shorthand. Ignored when countries is non-empty.
postedWithinstringoptionalClient-side filter on createTime relative to the run start.
minViewsintegeroptionalDrop posts with fewer playCount views. Missing stats fail a positive threshold.
minLikesintegeroptionalDrop posts with fewer likes. Missing stats fail a positive threshold.
minCommentsintegeroptionalDrop posts with fewer comments. Missing stats fail a positive threshold.
minSharesintegeroptionalDrop posts with fewer shares. Missing stats fail a positive threshold.
onlyNewbooleanoptionalEmit only posts that were not present in the named state store. State is read and written only when this is true.
stateKeystringoptionalOptional key-value store key for onlyNew. When empty, a deterministic key is derived from categories, countries and filters.
includeTranscriptsbooleanoptionalFetch subtitle WebVTT URLs and add subtitles[].text. Subtitle metadata is always returned when available; text is opt-in and fail-soft.
includePostDetailsbooleanoptionalFetch the public post detail page for each selected post (bounded to the emitted maxItems) and merge locationCreated plus poi/imagePost when the feed omitted them. Detail requests are fail-soft: a 403, stub page or parse failure keeps the original post. Adds one page request per emitted row.
rehostVideobooleanoptionalOpen each emitted post with Playwright to capture the real video/mp4 response, then HTTP-fetch the signed cover, originCover, dynamicCover, zoomCover, avatar, music, slideshow image and subtitle files. Every captured payload is stored in the run's default key-value store and the original signed URL is kept as a <field>Original value plus in mediaSources. video.playUrl and video.downloadUrl use tiktok-video-{postId}.mp4; other media use typed tiktok-{kind}-{postId}-... keys. Rehosting is fail-soft per field and never drops a post. It adds one browser page plus several media fetches per emitted record and increases run time and KV storage use; keep maxItemsPerCategory small.

Download the published input schema

View complete JSON
{
  "description": "Collect the public TikTok Explore feed with no login or cookies. All=120 is the default; 123 is accepted as a legacy alias. Optional country selection, filters, only-new monitoring, transcript text, post-detail enrichment and Key-Value Store media rehosting are additive.",
  "properties": {
    "autoDiscoverCategories": {
      "default": false,
      "description": "When true, an empty or All-only selection expands to all 21 verified Explore categories. If specific categories are selected, the missing categories are appended.",
      "prefill": false,
      "title": "Auto-Discover All Categories",
      "type": "boolean"
    },
    "categories": {
      "default": [
        "All"
      ],
      "description": "Categories to collect. Accepts names, numeric IDs, or legacy {id,name} objects. All=120; topics 100-119: Anime & Comics, Shows, Beauty Care, Games, Comedy, Daily Life, Family, Relationship, Drama, Outfit, Lipsync, Food, Sports, Animals, Society, Cars, Education, Fitness & Health, Technology, Singing & Dancing. 123 is accepted as a legacy alias for All. 121/122 (empty upstream) and 124+ (invalid upstream) are skipped with a warning.",
      "editor": "json",
      "prefill": [
        "All"
      ],
      "title": "Categories",
      "type": "array"
    },
    "categoryId": {
      "description": "Legacy shorthand for one category. It replaces the categories list. 100-120 are accepted; 123 maps to 120; other values are skipped with a warning.",
      "maximum": 100000,
      "minimum": 1,
      "title": "Category ID (legacy shorthand)",
      "type": "integer"
    },
    "countries": {
      "default": [],
      "description": "Optional ISO 3166-1 alpha-2 markets. Empty means the actor's current egress and no proxy. Each selected country uses a per-country Apify proxy (AUTO first, residential only after a proven block). The feed follows the egress IP, not a query parameter.",
      "editor": "stringList",
      "items": {
        "pattern": "^[A-Za-z]{2}$",
        "type": "string"
      },
      "maxItems": 10,
      "prefill": [],
      "title": "Countries / Markets",
      "type": "array"
    },
    "country": {
      "default": "",
      "description": "Single-country shorthand. Ignored when countries is non-empty.",
      "editor": "textfield",
      "pattern": "^([A-Za-z]{2})?$",
      "title": "Country (shorthand)",
      "type": "string"
    },
    "includePostDetails": {
      "default": false,
      "description": "Fetch the public post detail page for each selected post (bounded to the emitted maxItems) and merge locationCreated plus poi/imagePost when the feed omitted them. Detail requests are fail-soft: a 403, stub page or parse failure keeps the original post. Adds one page request per emitted row.",
      "prefill": false,
      "title": "Include Post Details",
      "type": "boolean"
    },
    "includeTranscripts": {
      "default": false,
      "description": "Fetch subtitle WebVTT URLs and add subtitles[].text. Subtitle metadata is always returned when available; text is opt-in and fail-soft.",
      "prefill": false,
      "title": "Include Transcript Text",
      "type": "boolean"
    },
    "maxItems": {
      "description": "Legacy alias for maxItemsPerCategory. It is used when maxItemsPerCategory is absent or exactly the default 3; an explicitly non-default maxItemsPerCategory wins.",
      "maximum": 1000,
      "minimum": 1,
      "title": "Max Items (legacy alias)",
      "type": "integer"
    },
    "maxItemsPerCategory": {
      "default": 3,
      "description": "Hard cap on emitted posts per category. The cap is applied before any page is pushed, so a 30-item page never overshoots. The default is 3 because media rehosting is on by default and adds one browser page plus several HTTP media fetches per emitted post.",
      "maximum": 1000,
      "minimum": 1,
      "prefill": 3,
      "title": "Max Items Per Category",
      "type": "integer"
    },
    "minComments": {
      "default": 0,
      "description": "Drop posts with fewer comments. Missing stats fail a positive threshold.",
      "maximum": 1000000000000,
      "minimum": 0,
      "prefill": 0,
      "title": "Minimum Comments",
      "type": "integer"
    },
    "minLikes": {
      "default": 0,
      "description": "Drop posts with fewer likes. Missing stats fail a positive threshold.",
      "maximum": 1000000000000,
      "minimum": 0,
      "prefill": 0,
      "title": "Minimum Likes",
      "type": "integer"
    },
    "minShares": {
      "default": 0,
      "description": "Drop posts with fewer shares. Missing stats fail a positive threshold.",
      "maximum": 1000000000000,
      "minimum": 0,
      "prefill": 0,
      "title": "Minimum Shares",
      "type": "integer"
    },
    "minViews": {
      "default": 0,
      "description": "Drop posts with fewer playCount views. Missing stats fail a positive threshold.",
      "maximum": 1000000000000,
      "minimum": 0,
      "prefill": 0,
      "title": "Minimum Views",
      "type": "integer"
    },
    "onlyNew": {
      "default": false,
      "description": "Emit only posts that were not present in the named state store. State is read and written only when this is true.",
      "prefill": false,
      "title": "Only New Posts",
      "type": "boolean"
    },
    "postedWithin": {
      "default": "any",
      "description": "Client-side filter on createTime relative to the run start.",
      "enum": [
        "any",
        "24h",
        "7d",
        "30d",
        "90d"
      ],
      "prefill": "any",
      "title": "Posted Within",
      "type": "string"
    },
    "rehostVideo": {
      "default": true,
      "description": "Open each emitted post with Playwright to capture the real video/mp4 response, then HTTP-fetch the signed cover, originCover, dynamicCover, zoomCover, avatar, music, slideshow image and subtitle files. Every captured payload is stored in the run's default key-value store and the original signed URL is kept as a <field>Original value plus in mediaSources. video.playUrl and video.downloadUrl use tiktok-video-{postId}.mp4; other media use typed tiktok-{kind}-{postId}-... keys. Rehosting is fail-soft per field and never drops a post. It adds one browser page plus several media fetches per emitted record and increases run time and KV storage use; keep maxItemsPerCategory small.",
      "prefill": false,
      "title": "Rehost Media in Key-Value Store",
      "type": "boolean"
    },
    "stateKey": {
      "default": "",
      "description": "Optional key-value store key for onlyNew. When empty, a deterministic key is derived from categories, countries and filters.",
      "editor": "textfield",
      "maxLength": 200,
      "title": "Only-New State Key",
      "type": "string"
    }
  },
  "required": [
    "categories"
  ],
  "schemaVersion": 1,
  "title": "TikTok Explore/Trending Scraper Input",
  "type": "object"
}

Output schema

Published result links and storage locations. This schema is not an exhaustive contract for every field in a dataset record.

FieldTypeRequiredDescription
datastringoptionalDataset of Explore posts. Each row preserves the legacy feedType/categoryId/categoryName identity and adds rank, position, marketRegion, itemCategoryId/itemCategoryName, derived engagement metrics, place, adFlags, subtitles, mediaExpiresAt, provenance fields and optional KVS media rehosting fields (mediaRehosted, mediaRehostedAll, rehostStatus, rehostStatusByField, mediaRehostedFields, mediaSources, and per-field <field>Original values).

Download the published output schema

View complete JSON
{
  "actorOutputSchemaVersion": 1,
  "properties": {
    "data": {
      "description": "Dataset of Explore posts. Each row preserves the legacy feedType/categoryId/categoryName identity and adds rank, position, marketRegion, itemCategoryId/itemCategoryName, derived engagement metrics, place, adFlags, subtitles, mediaExpiresAt, provenance fields and optional KVS media rehosting fields (mediaRehosted, mediaRehostedAll, rehostStatus, rehostStatusByField, mediaRehostedFields, mediaSources, and per-field <field>Original values).",
      "template": "{{links.apiDefaultDatasetUrl}}/items",
      "title": "Trending Post Data",
      "type": "string"
    }
  },
  "title": "TikTok Explore Feed Trending Post Scraper Output"
}

Dataset schema

Published dataset views and any declared field definitions. A view can select only a subset of the returned record fields.

Download the published dataset schema

View complete JSON
{
  "actorSpecification": 1,
  "fields": {
    "adFlags": {
      "description": "adAuthorization, brandOrganicType, adLabelVersion, aigcLabelType, moderationAigcLabelType and isAiGenerated; empty subkeys are omitted",
      "title": "Ad / AI Flags",
      "type": "object"
    },
    "ageHours": {
      "description": "(run start - createTime) / 3600, 2 decimals",
      "title": "Age Hours",
      "type": "number"
    },
    "author": {
      "description": "Author profile snapshot; includes commentSetting, duetSetting, stitchSetting, downloadSetting and isEmbedBanned when present. avatarUrl uses a KV URL after rehosting and avatarUrlOriginal keeps the signed source.",
      "title": "Author",
      "type": "object"
    },
    "caption": {
      "description": "Post caption/description text",
      "title": "Caption",
      "type": "string"
    },
    "categoryId": {
      "description": "Requested category ID. All is 120; legacy 123 inputs are normalized to 120.",
      "title": "Category ID",
      "type": "integer"
    },
    "categoryName": {
      "description": "Canonical requested category name (e.g. All, Sports, Technology)",
      "title": "Category Name",
      "type": "string"
    },
    "collectCount": {
      "description": "Number of times post was saved/collected",
      "title": "Collect Count",
      "type": "integer"
    },
    "commentCount": {
      "description": "Number of comments",
      "title": "Comment Count",
      "type": "integer"
    },
    "createTime": {
      "description": "Unix timestamp when the post was created",
      "title": "Create Time",
      "type": "integer"
    },
    "createdAt": {
      "description": "ISO 8601 UTC timestamp of post creation",
      "title": "Created At",
      "type": "string"
    },
    "duetEnabled": {
      "description": "Whether duets are allowed on this post",
      "title": "Duet Enabled",
      "type": "boolean"
    },
    "engagementRate": {
      "description": "(likes + comments + shares + collects) / plays, 6 decimals. Omitted when playCount is zero or missing.",
      "title": "Engagement Rate",
      "type": "number"
    },
    "feedType": {
      "description": "Always 'explore' for rows from this actor",
      "title": "Feed Type",
      "type": "string"
    },
    "firstSeen": {
      "description": "Present only for onlyNew runs: ISO timestamp when the post first entered the state store",
      "title": "First Seen",
      "type": "string"
    },
    "hashtags": {
      "description": "Hashtags used in the post",
      "title": "Hashtags",
      "type": "array"
    },
    "images": {
      "description": "Slideshow images for photo posts (postType='slideshow'). With rehosting, urlList becomes a single KV image URL and urlListOriginal keeps the signed candidate list.",
      "title": "Images",
      "type": "array"
    },
    "isAd": {
      "description": "Whether the post is marked as an advertisement",
      "title": "Is Ad",
      "type": "boolean"
    },
    "isNew": {
      "description": "Present only for onlyNew runs; true for posts emitted during this run",
      "title": "Is New",
      "type": "boolean"
    },
    "itemCategoryId": {
      "description": "The item's own CategoryType. Explore feeds are mixed, so this can differ from categoryId.",
      "title": "Item Category ID",
      "type": "integer"
    },
    "itemCategoryName": {
      "description": "Canonical name for itemCategoryId",
      "title": "Item Category Name",
      "type": "string"
    },
    "lastSeen": {
      "description": "Present only for onlyNew runs: ISO timestamp for this observation",
      "title": "Last Seen",
      "type": "string"
    },
    "likeCount": {
      "description": "Number of likes (from statsV2)",
      "title": "Like Count",
      "type": "integer"
    },
    "locationCreated": {
      "description": "Declared for backward compatibility. The Explore item payload does not contain it; it is emitted only when includePostDetails=true and the post detail page supplies a value.",
      "title": "Location Created (legacy)",
      "type": "string"
    },
    "marketRegion": {
      "description": "Selected ISO country, or the actual region detected from the Explore page state when no country was requested. Never guessed.",
      "title": "Market Region",
      "type": "string"
    },
    "mediaExpiresAt": {
      "description": "Per-asset ISO 8601 expiries: video, cover, avatar, music, subtitles",
      "title": "Media Expiries",
      "type": "object"
    },
    "mediaRehosted": {
      "description": "True when the video bytes were stored in the default Key-Value Store, or, for a post without video, when at least one other signed media field was stored. False when no media field succeeded; the original signed URLs remain.",
      "title": "Media Rehosted",
      "type": "boolean"
    },
    "mediaRehostedAll": {
      "description": "True when every attempted media field was stored in the Key-Value Store. False when any field failed or was skipped.",
      "title": "All Media Rehosted",
      "type": "boolean"
    },
    "mediaRehostedFields": {
      "description": "Boolean per-field success map derived from rehostStatusByField.",
      "title": "Media Rehosted Fields",
      "type": "object"
    },
    "mediaSources": {
      "description": "Original signed URL or URL list for every rehosted field. Rehosted fields also keep a matching <field>Original value.",
      "title": "Media Sources",
      "type": "object"
    },
    "mediaUrlExpiresAt": {
      "description": "Earliest ISO 8601 UTC expiry across the original signed video, cover, avatar, music and subtitle URLs. It remains the source expiry after rehosting; KV URLs do not use it.",
      "title": "Media URL Expires At",
      "type": "string"
    },
    "mentions": {
      "description": "@mentions in the caption",
      "title": "Mentions",
      "type": "array"
    },
    "music": {
      "description": "Sound/music used in the post (id, title, authorName, duration, album, playUrl, coverUrl). After rehosting, playUrl and coverUrl use KV URLs and playUrlOriginal/coverUrlOriginal keep the signed sources.",
      "title": "Music",
      "type": "object"
    },
    "place": {
      "description": "POI object when present: id, name, address, city, province, country, countryCode, category, typeName. No latitude/longitude is invented.",
      "title": "Place / POI",
      "type": "object"
    },
    "playCount": {
      "description": "Number of video plays",
      "title": "Play Count",
      "type": "integer"
    },
    "playsPerHour": {
      "description": "playCount / ageHours, 1 decimal. Omitted when ageHours is not positive.",
      "title": "Plays Per Hour",
      "type": "number"
    },
    "position": {
      "description": "0-based emitted order within the market/category after filters and dedupe",
      "title": "Position",
      "type": "integer"
    },
    "postId": {
      "description": "Unique TikTok post/video ID (string)",
      "title": "Post ID",
      "type": "string"
    },
    "postType": {
      "description": "Either 'video' or 'slideshow'",
      "title": "Post Type",
      "type": "string"
    },
    "postUrl": {
      "description": "Direct URL to the TikTok post",
      "title": "Post URL",
      "type": "string"
    },
    "rank": {
      "description": "1-based emitted order within the market/category after filters and dedupe",
      "title": "Rank",
      "type": "integer"
    },
    "recordType": {
      "description": "Always 'explorePost'",
      "title": "Record Type",
      "type": "string"
    },
    "rehostStatus": {
      "description": "Record-level outcome: ok, partial, no_media_fields, no_post_url, missing_post_id, no_video_response, navigation_failed, playwright_unavailable, browser_unavailable, body_unavailable, empty_body, invalid_content_type, not_mp4, not_vtt, http_<status>, network_error, capture_error, kv_error, or kv_url_error. Failures keep the original URLs and never drop the record.",
      "title": "Rehost Status",
      "type": "string"
    },
    "rehostStatusByField": {
      "description": "Per-field status keyed by output path, for example video.playUrl, video.cover, video.zoomCover.240, music.playUrl, author.avatarUrl, images[0].urlList and subtitles[0].url. Values are ok or a fail-soft status.",
      "title": "Rehost Status By Field",
      "type": "object"
    },
    "repostCount": {
      "description": "Number of reposts",
      "title": "Repost Count",
      "type": "integer"
    },
    "scrapedAt": {
      "description": "ISO 8601 UTC timestamp of the run start",
      "title": "Scraped At",
      "type": "string"
    },
    "shareCount": {
      "description": "Number of shares",
      "title": "Share Count",
      "type": "integer"
    },
    "shareEnabled": {
      "description": "Whether sharing is enabled on this post",
      "title": "Share Enabled",
      "type": "boolean"
    },
    "sourceUrl": {
      "description": "Provenance alias for postUrl",
      "title": "Source URL",
      "type": "string"
    },
    "stitchEnabled": {
      "description": "Whether stitching is allowed on this post",
      "title": "Stitch Enabled",
      "type": "boolean"
    },
    "subtitles": {
      "description": "Subtitle metadata from subtitleInfos, or video.claInfo.captionInfos when subtitleInfos is absent. text is added only with includeTranscripts=true. With rehosting, url is a KV VTT URL and urlOriginal keeps the signed source.",
      "title": "Subtitles",
      "type": "array"
    },
    "textLanguage": {
      "description": "Language code of the caption",
      "title": "Text Language",
      "type": "string"
    },
    "video": {
      "description": "Video metadata. With rehostVideo=true, playUrl, downloadUrl, cover, originCover, dynamicCover and zoomCover are stable Key-Value Store URLs, each with a matching <field>Original value and a mediaSources entry. sourcePlayUrl keeps the original signed play URL, and sourceDownloadUrl keeps a distinct signed download URL. Without rehosting, playUrl prefers the working www.tiktok.com/aweme/v1/play/ carrier and downloadUrl falls back to it.",
      "title": "Video",
      "type": "object"
    }
  },
  "title": "TikTok Explore/Trending Scraper Dataset",
  "views": {
    "overview": {
      "display": {
        "component": "table",
        "properties": {
          "ageHours": {
            "format": "number",
            "label": "Age (hours)"
          },
          "caption": {
            "format": "text",
            "label": "Caption"
          },
          "categoryId": {
            "format": "number",
            "label": "Category ID"
          },
          "categoryName": {
            "format": "text",
            "label": "Category"
          },
          "createdAt": {
            "format": "text",
            "label": "Created At"
          },
          "engagementRate": {
            "format": "number",
            "label": "Engagement Rate"
          },
          "feedType": {
            "format": "text",
            "label": "Feed Type"
          },
          "itemCategoryName": {
            "format": "text",
            "label": "Item Category"
          },
          "likeCount": {
            "format": "number",
            "label": "Likes"
          },
          "marketRegion": {
            "format": "text",
            "label": "Market"
          },
          "mediaRehosted": {
            "format": "text",
            "label": "Media Rehosted"
          },
          "mediaRehostedAll": {
            "format": "text",
            "label": "All Media Rehosted"
          },
          "playCount": {
            "format": "number",
            "label": "Plays"
          },
          "playsPerHour": {
            "format": "number",
            "label": "Plays / Hour"
          },
          "postId": {
            "format": "text",
            "label": "Post ID"
          },
          "postUrl": {
            "format": "link",
            "label": "Post URL"
          },
          "rank": {
            "format": "number",
            "label": "Rank"
          },
          "rehostStatus": {
            "format": "text",
            "label": "Rehost Status"
          }
        }
      },
      "title": "Overview",
      "transformation": {
        "fields": [
          "rank",
          "marketRegion",
          "categoryName",
          "categoryId",
          "itemCategoryName",
          "feedType",
          "postId",
          "caption",
          "playCount",
          "likeCount",
          "engagementRate",
          "ageHours",
          "playsPerHour",
          "createdAt",
          "postUrl",
          "mediaRehosted",
          "mediaRehostedAll",
          "rehostStatus"
        ]
      }
    }
  }
}

Key-value schema

Published key-value store definitions, when provided by this build.

This definition is not present in the default published build. See the published README for available documentation.

Published README

Read the full published documentation

TikTok Explore Feed Trending Post Scraper

Collect the public TikTok Explore feed by category with no login, cookies or API keys. Choose All or any of the 20 topic feeds, target one or more country markets, filter by post age and engagement, and get one row per post with author stats, music, hashtags, mentions, subtitles, POI data and derived performance metrics. Signed TikTok media URLs can be copied to stable key-value store URLs in the same run, covering video, covers, avatars, music, slideshow images and subtitle files.

What this actor does

  • 21 Explore categories: All (120) plus the 20 topic feeds; accepts category names, numeric IDs and legacy {id,name} objects.
  • Market and filter controls: optional ISO country markets, post age, and minimum views, likes, comments and shares.
  • Rich post records: rank, engagement rates, author stats, music, hashtags, mentions, POI, ad/AI flags and subtitle metadata.
  • Stable media: with rehostVideo, TikTok video, covers, avatars, music, slideshow images and subtitle files are stored in the run's key-value store and emitted as durable public URLs.
  • Empty fields are omitted — records contain no null, empty string, empty list or empty object.

Output per post

One row per Explore post. feedType is always explore and recordType is explorePost. Empty values are removed, so a field that is not listed is absent for that post rather than null.

Identity and position

Field Description
postId TikTok post/video ID (string)
postType video or slideshow
postUrl, sourceUrl Public post URL; sourceUrl mirrors postUrl
feedType Always explore
categoryId, categoryName Requested Explore category; All is 120
itemCategoryId, itemCategoryName The post's own TikTok category, which can differ from the requested category
rank, position 1-based rank and 0-based position within the market/category after filters and dedupe
marketRegion Selected ISO country, or the region read from Explore page state when no country was selected; omitted rather than guessed

Content, engagement and flags

Field Description
caption, textLanguage Post caption and detected caption language
createTime, createdAt Original Unix timestamp and ISO 8601 UTC creation time
locationCreated Emitted only when post-detail enrichment supplies it; the Explore feed payload does not contain it
hashtags[] name, id and isCommerce
mentions[] username, start, end and awemeId
playCount, likeCount, commentCount, shareCount, collectCount, repostCount Raw TikTok counters
engagementRate (likes + comments + shares + collects) / plays, 6 decimals; omitted when plays is zero
ageHours Hours between post creation and run start, 2 decimals
playsPerHour playCount / ageHours, 1 decimal; omitted when age is not positive
stitchEnabled, duetEnabled, shareEnabled Post interaction settings
isAd Whether the post is marked as an ad
adFlags adAuthorization, brandOrganicType, adLabelVersion, aigcLabelType, moderationAigcLabelType and isAiGenerated

Author, music, video, place and subtitles

Field Description
author Profile snapshot: id, secUid, username, displayName, bio, verified, privateAccount, roomId, avatarUrl (plus avatarUrlOriginal after rehosting), followerCount, followingCount, heartCount, videoCount, and commentSetting, duetSetting, stitchSetting, downloadSetting, isEmbedBanned when present
music id, title, authorName, original, duration, album, playUrl (plus playUrlOriginal), coverUrl (plus coverUrlOriginal)
video Metadata width, height, duration, ratio, definition, bitrate; URLs playUrl, downloadUrl, sourcePlayUrl, sourceDownloadUrl; images cover, originCover, dynamicCover and resolution-keyed zoomCover; after rehosting, the signed sources are kept in playUrlOriginal, downloadUrlOriginal, coverOriginal, originCoverOriginal, dynamicCoverOriginal and zoomCoverOriginal
place POI data: id, name, address, city, province, country, countryCode, category and typeName
images[] Slideshow images: ordinal, width, height, urlList, and urlListOriginal after rehosting
subtitles[] languageCode, format, url (plus urlOriginal after rehosting), expiresAt, sizeBytes, source, version, and text when includeTranscripts=true
mediaExpiresAt Per-asset source expiry: video, cover, avatar, music, subtitles
mediaUrlExpiresAt Earliest of the media expiries

Media rehosting status

Field Description
mediaRehosted true when the video was stored, or for a post without video when at least one other media field was stored
mediaRehostedAll true when every attempted media field was stored
rehostStatus Record outcome: ok, partial, or a fail-soft reason such as http_403, no_video_response, not_mp4, not_vtt, network_error or kv_error
rehostStatusByField Per-field status keyed by output path, for example video.cover, music.playUrl, author.avatarUrl, images[0].urlList and subtitles[0].url
mediaRehostedFields Boolean per-field success map derived from rehostStatusByField
mediaSources Original signed URL or URL list for every rehosted field

Monitoring and provenance

Field Description
isNew, firstSeen, lastSeen Present only when onlyNew=true: whether the post is new in this run, plus its state-store timestamps
scrapedAt ISO 8601 UTC run-start time
recordType Always explorePost

Input

Field Type Default Description
categories array ["All"] Required. Names, numeric IDs or legacy {id,name} objects. Prefill is ["All"].
maxItemsPerCategory integer 1–1,000 3 Hard cap per category, checked before a page is emitted. Prefill is 3.
maxItems integer 1–1,000 – Legacy alias used only when maxItemsPerCategory is absent or exactly 3; an explicit non-default maxItemsPerCategory wins.
categoryId integer 1–100,000 – Legacy single-category shorthand. It replaces categories; 123 maps to 120.
autoDiscoverCategories boolean false Expand an empty or All-only selection to all 21 categories, or append missing categories to a partial selection.
countries array of ISO 3166-1 alpha-2 [] Up to 10 markets. Empty means the current connection and no proxy. Each selected market needs its own egress.
country string "" Single-country shorthand; ignored when countries is non-empty.
postedWithin string any any, 24h, 7d, 30d or 90d, applied to createTime.
minViews integer 0 Drop posts below this play count.
minLikes integer 0 Drop posts below this like count.
minComments integer 0 Drop posts below this comment count.
minShares integer 0 Drop posts below this share count.
onlyNew boolean false Emit only post IDs that are not already in the state store.
stateKey string derived Key inside the tiktok-explore-only-new state store; blank derives one from your categories, markets and filters.
includeTranscripts boolean false Fetch subtitle WebVTT files and add subtitles[].text. Subtitle metadata is returned even when this is off.
includePostDetails boolean false Fetch one public post-detail page per emitted post to add locationCreated and missing POI/slideshow data. Fail-soft.
rehostVideo boolean true Copy every signed media field into the key-value store and emit stable public URLs. Default true; the form prefills false for a lighter first run.

Category IDs: 100 Anime & Comics, 101 Shows, 102 Beauty Care, 103 Games, 104 Comedy, 105 Daily Life, 106 Family, 107 Relationship, 108 Drama, 109 Outfit, 110 Lipsync, 111 Food, 112 Sports, 113 Animals, 114 Society, 115 Cars, 116 Education, 117 Fitness & Health, 118 Technology, 119 Singing & Dancing, 120 All. 121 and 122 are empty upstream and 124+ are invalid upstream, so they are skipped with a warning. No TikTok account, cookies, API keys or user proxy credentials are needed.

Example: default metadata run

{
  "categories": ["All"],
  "maxItemsPerCategory": 3,
  "rehostVideo": false
}

Example: rehost every media field

{
  "categories": ["All"],
  "maxItemsPerCategory": 10,
  "rehostVideo": true
}

Example: filtered multi-market run

{
  "categories": ["All", "Sports"],
  "countries": ["PK", "TH"],
  "maxItemsPerCategory": 100,
  "postedWithin": "7d",
  "minViews": 10000,
  "minLikes": 500,
  "rehostVideo": false
}

Example: only-new monitoring

{
  "categories": ["Technology"],
  "maxItemsPerCategory": 100,
  "onlyNew": true,
  "stateKey": "tech-daily",
  "rehostVideo": false
}

Use cases

  • Social media marketing — see which formats, sounds and captions are trending across Explore categories and markets.
  • Creator and agency research — benchmark author follower counts, engagement rates and post performance against category peers.
  • Content strategy — compare hooks, hashtags and mentions across markets before planning a campaign.
  • Brand and ad monitoring — filter ad-flagged posts and AI/AIGC labels within a category or country.
  • Dataset building — export metadata-rich rows for recommendation models, trend dashboards and research datasets.
  • Localization research — collect the same category through different country egresses to compare regional feeds.

FAQ

Do I need a TikTok login, cookies, or an API key?

No. The actor reads publicly available Explore data and needs no account, cookies, tokens or user proxy credentials.

How many posts can I get per category?

Up to 1,000 per category. TikTok returns at most 30 items per request, and Explore has no cursor, so the actor repeats requests and removes duplicates until it reaches your cap or the feed stops producing new posts. A category can run short when TikTok rotates the feed; the run status message reports the actual count and stop reason.

What is the difference between categoryId and itemCategoryId?

categoryId is the Explore feed you requested. itemCategoryId is the post's own TikTok category. Explore feeds are mixed, so a Sports request can contain posts classified as Comedy or Society. All is 120; 123 is accepted as a legacy alias for All, while 121 and 122 are empty upstream and 124+ are invalid.

Why can the same post appear more than once, and is rank stable?

Each selected country is a separate collection. Dedupe is per market and post ID, so a post appears once per market but can appear once for each requested country. rank and position are run-local emission order after filters and dedupe, not a permanent TikTok ranking; the Explore feed changes between calls, so ranks can differ in a later run.

How do country and market targets work?

The TikTok feed follows the network egress, not a query parameter. With countries empty the actor uses the current connection and no proxy. Each selected country uses a per-country Apify proxy, starting with the automatic pool and escalating to residential only after a proven block. If a country is unavailable, it is skipped with a status message instead of silently using another market.

Do TikTok media URLs expire?

Yes. Signed video, cover and avatar URLs usually expire in about 48 hours; music is often about 24 hours, and the fallback play carrier can be shorter. mediaExpiresAt exposes per-asset expiry and mediaUrlExpiresAt gives the earliest. With rehostVideo=true, the emitted media URLs point at the key-value store instead.

What does media rehosting store?

For every emitted post it stores the video plus cover, origin cover, dynamic cover, all zoom-cover sizes, author avatar, music audio and cover, slideshow images, and subtitle VTT files in the run's key-value store. The main field becomes a stable public key-value store URL, while the signed source stays in <field>Original and in mediaSources. Rehosting is fail-soft per field: a failed cover or subtitle keeps the original URL and never drops the post, and the status fields tell you exactly what succeeded. Rehosted files last for the lifetime of the store record, not forever; the URLs are public, need no Apify token, and store retention settings still apply. Rehosting adds one post-page load and several media downloads per record plus storage, so keep maxItemsPerCategory small or set rehostVideo=false for metadata-only runs.

Why are some fields missing?

Empty values are omitted, so a record contains only fields TikTok returned. locationCreated is not in the Explore feed payload and appears only with includePostDetails=true when the post detail page supplies it. Explore rarely exposes slideshows, so images is usually absent; detail enrichment can recover it for a known photo post. Subtitle metadata is source-dependent.

What does onlyNew do?

It keeps a named set of post IDs in the tiktok-explore-only-new state store and emits only IDs that were not seen before. stateKey names the set; leave it blank to derive one from your categories, markets and filters. State is pruned to 90 days and capped at 200,000 IDs.

Is this affiliated with TikTok?

No. This is an independent third-party actor that reads publicly available Explore data. It is not affiliated with or endorsed by TikTok.

Python quickstart

Use a schema-derived input and the Python standard library to start a run. An Apify account and token are required for execution.

Open the step-by-step Python guide →

Source build 75caFFr2NTE3Pf7ul. Documentation is regenerated from the default published build; private drafts are not included.