Nimbryxo Studio

TikTok Hashtag Video Feed & Stats Extractor

Collect a TikTok hashtag's total view/video counts plus the video feed underneath it - captions, author, music, and engagement - with automatic fallback when the feed is blocked.

Input schema

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

FieldTypeRequiredDescription
hashtagsarrayrequiredHashtags to collect. Accepts a bare tag, #tag, or a TikTok tag URL. Case-insensitive, duplicates removed. Up to 10.
modestringoptionalWhat to collect. 'stats' returns one aggregate row per hashtag (cheapest). 'feed' returns the public hashtag video feed. 'search' returns one page of TikTok keyword/hashtag search results. 'auto' returns stats plus the feed and adds search results only when the feed is shorter than one page.
maxVideosPerHashtagintegeroptionalUpper bound on videos per hashtag for feed, search and auto modes (0–300). Use 0 for stats only. TikTok usually returns fewer than requested: the feed is served in pages of about 30 and many tags stop after roughly 100–150 rows.
Show all input fields
FieldTypeRequiredDescription
hashtagsarrayrequiredHashtags to collect. Accepts a bare tag, #tag, or a TikTok tag URL. Case-insensitive, duplicates removed. Up to 10.
modestringoptionalWhat to collect. 'stats' returns one aggregate row per hashtag (cheapest). 'feed' returns the public hashtag video feed. 'search' returns one page of TikTok keyword/hashtag search results. 'auto' returns stats plus the feed and adds search results only when the feed is shorter than one page.
maxVideosPerHashtagintegeroptionalUpper bound on videos per hashtag for feed, search and auto modes (0–300). Use 0 for stats only. TikTok usually returns fewer than requested: the feed is served in pages of about 30 and many tags stop after roughly 100–150 rows.
maxHashtagsintegeroptionalProcess no more than this many hashtags from the list (1–10). Extra hashtags are skipped.
includeStatsRowbooleanoptionalEmit the aggregate hashtag stats row before video rows in feed, search and auto modes. Stats mode always emits it.
regionstringoptionalMarket used for the hashtag feed. Only the United States is verified to return a public feed; other markets are not exposed yet.
proxyConfigurationobjectoptionalOptional. Leave empty to use the built-in US residential connection with automatic fallback. Set your own Apify proxy configuration here to override it. Feed mode uses the most residential bandwidth, which is billed by Apify proxy usage.

Download the published input schema

View complete JSON
{
  "description": "Collect all-time stats for one or more TikTok hashtags and/or the videos underneath them. Stats mode is the cheapest; feed mode returns the public US hashtag feed; search mode returns one page of keyword search; auto combines stats and feed and adds search when the feed is short.",
  "properties": {
    "hashtags": {
      "description": "Hashtags to collect. Accepts a bare tag, #tag, or a TikTok tag URL. Case-insensitive, duplicates removed. Up to 10.",
      "editor": "stringList",
      "items": {
        "type": "string"
      },
      "maxItems": 10,
      "minItems": 1,
      "prefill": [
        "fitness"
      ],
      "title": "Hashtags",
      "type": "array"
    },
    "includeStatsRow": {
      "default": true,
      "description": "Emit the aggregate hashtag stats row before video rows in feed, search and auto modes. Stats mode always emits it.",
      "title": "Include Stats Row",
      "type": "boolean"
    },
    "maxHashtags": {
      "default": 10,
      "description": "Process no more than this many hashtags from the list (1–10). Extra hashtags are skipped.",
      "maximum": 10,
      "minimum": 1,
      "title": "Max Hashtags",
      "type": "integer"
    },
    "maxVideosPerHashtag": {
      "default": 30,
      "description": "Upper bound on videos per hashtag for feed, search and auto modes (0–300). Use 0 for stats only. TikTok usually returns fewer than requested: the feed is served in pages of about 30 and many tags stop after roughly 100–150 rows.",
      "maximum": 300,
      "minimum": 0,
      "prefill": 0,
      "title": "Max Videos Per Hashtag",
      "type": "integer"
    },
    "mode": {
      "default": "stats",
      "description": "What to collect. 'stats' returns one aggregate row per hashtag (cheapest). 'feed' returns the public hashtag video feed. 'search' returns one page of TikTok keyword/hashtag search results. 'auto' returns stats plus the feed and adds search results only when the feed is shorter than one page.",
      "editor": "select",
      "enum": [
        "stats",
        "feed",
        "search",
        "auto"
      ],
      "enumTitles": [
        "Stats only — video and view totals",
        "Hashtag feed — videos under the tag",
        "Search — one page of TikTok results",
        "Auto — stats + feed, search when short"
      ],
      "prefill": "stats",
      "title": "Mode",
      "type": "string"
    },
    "proxyConfiguration": {
      "description": "Optional. Leave empty to use the built-in US residential connection with automatic fallback. Set your own Apify proxy configuration here to override it. Feed mode uses the most residential bandwidth, which is billed by Apify proxy usage.",
      "editor": "proxy",
      "title": "Proxy Configuration",
      "type": "object"
    },
    "region": {
      "default": "US",
      "description": "Market used for the hashtag feed. Only the United States is verified to return a public feed; other markets are not exposed yet.",
      "editor": "select",
      "enum": [
        "US"
      ],
      "enumTitles": [
        "United States (verified)"
      ],
      "title": "Region",
      "type": "string"
    }
  },
  "required": [
    "hashtags"
  ],
  "schemaVersion": 1,
  "title": "TikTok Hashtag Stats and Video Feed 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 containing hashtag_metadata stats rows and/or post video rows. Stats rows carry all-time totals; post rows carry the video payload plus source, position, region, scrapedAt, and carriesSearchedHashtag.

Download the published output schema

View complete JSON
{
  "actorOutputSchemaVersion": 1,
  "description": "Aggregate hashtag stats rows (video/view totals, source, region) and video rows (caption, author, music, engagement, provenance). Use the recordType field to distinguish rows.",
  "properties": {
    "data": {
      "description": "Dataset containing hashtag_metadata stats rows and/or post video rows. Stats rows carry all-time totals; post rows carry the video payload plus source, position, region, scrapedAt, and carriesSearchedHashtag.",
      "template": "{{links.apiDefaultDatasetUrl}}/items",
      "title": "Hashtag Data",
      "type": "string"
    }
  },
  "title": "TikTok Hashtag Stats and Video Feed 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": {
    "announcementBody": {
      "description": "Optional challenge announcement body text",
      "title": "Announcement Body",
      "type": "string"
    },
    "announcementTitle": {
      "description": "Optional challenge announcement title",
      "title": "Announcement Title",
      "type": "string"
    },
    "author": {
      "description": "Author snapshot at time of scraping (id, username, displayName, followerCount, etc.)",
      "title": "Author",
      "type": "object"
    },
    "authorStats": {
      "description": "Follower, following, heart, video and digg counts for the author",
      "title": "Author Stats",
      "type": "object"
    },
    "caption": {
      "description": "Post caption/description text",
      "title": "Caption",
      "type": "string"
    },
    "carriesAnyHashtag": {
      "description": "True when the video's caption carries at least one hashtag entity",
      "title": "Carries Any Hashtag",
      "type": "boolean"
    },
    "carriesSearchedHashtag": {
      "description": "True when the video's own hashtag entities include the searched tag",
      "title": "Carries Searched Hashtag",
      "type": "boolean"
    },
    "collectCount": {
      "description": "Number of times post was saved/collected",
      "title": "Collect Count",
      "type": "integer"
    },
    "commentCount": {
      "description": "Number of comments",
      "title": "Comment Count",
      "type": "integer"
    },
    "coverLarger": {
      "description": "Large cover banner image URL",
      "title": "Cover Image (Large)",
      "type": "string"
    },
    "coverMedium": {
      "description": "Medium cover banner image URL",
      "title": "Cover Image (Medium)",
      "type": "string"
    },
    "coverThumb": {
      "description": "Thumbnail cover image URL",
      "title": "Cover Image (Thumb)",
      "type": "string"
    },
    "createTime": {
      "description": "Unix timestamp when the post was created",
      "title": "Create Time",
      "type": "integer"
    },
    "createdAt": {
      "description": "ISO 8601 timestamp of post creation (UTC)",
      "title": "Created At",
      "type": "string"
    },
    "desc": {
      "description": "Optional hashtag description",
      "title": "Description",
      "type": "string"
    },
    "duetEnabled": {
      "description": "Whether duets are allowed on this post",
      "title": "Duet Enabled",
      "type": "boolean"
    },
    "hashtag": {
      "description": "The requested hashtag (without #) on stats rows",
      "title": "Hashtag",
      "type": "string"
    },
    "hashtagId": {
      "description": "TikTok internal challenge ID (string)",
      "title": "Hashtag ID",
      "type": "string"
    },
    "hashtagName": {
      "description": "Canonical hashtag name as TikTok stores it",
      "title": "Hashtag Name",
      "type": "string"
    },
    "hashtags": {
      "description": "Hashtags used in the post, each with id and name",
      "title": "Hashtags",
      "type": "array"
    },
    "images": {
      "description": "Slideshow images for photo posts (postType='slideshow'). Each item has ordinal, width, height, urlList.",
      "title": "Images",
      "type": "array"
    },
    "isAd": {
      "description": "Whether the post is a paid advertisement",
      "title": "Is Ad",
      "type": "boolean"
    },
    "isCommerce": {
      "description": "Whether this is a commercial/brand hashtag",
      "title": "Is Commerce",
      "type": "boolean"
    },
    "likeCount": {
      "description": "Number of likes",
      "title": "Like Count",
      "type": "integer"
    },
    "locationCreated": {
      "description": "Country code where the post was created",
      "title": "Location Created",
      "type": "string"
    },
    "mediaUrlExpiresAt": {
      "description": "ISO 8601 UTC timestamp when signed media URLs expire",
      "title": "Media URL Expires At",
      "type": "string"
    },
    "mentions": {
      "description": "@mentions in the caption with username and offsets",
      "title": "Mentions",
      "type": "array"
    },
    "music": {
      "description": "Music/sound used in the post (id, title, authorName, duration, playUrl, coverUrl)",
      "title": "Music",
      "type": "object"
    },
    "playCount": {
      "description": "Number of video plays",
      "title": "Play Count",
      "type": "integer"
    },
    "position": {
      "description": "1-based position in the collected feed/search list for the hashtag",
      "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"
    },
    "profileLarger": {
      "description": "Large profile/cover image URL for the hashtag",
      "title": "Profile Image (Large)",
      "type": "string"
    },
    "profileMedium": {
      "description": "Medium profile image URL for the hashtag",
      "title": "Profile Image (Medium)",
      "type": "string"
    },
    "profileThumb": {
      "description": "Thumbnail profile image URL for the hashtag",
      "title": "Profile Image (Thumb)",
      "type": "string"
    },
    "recordType": {
      "description": "Discriminator: 'hashtag_metadata' for aggregate stats rows, 'post' for video rows",
      "title": "Record Type",
      "type": "string"
    },
    "region": {
      "description": "Market used for the run (US is the verified feed market)",
      "title": "Region",
      "type": "string"
    },
    "repostCount": {
      "description": "Number of reposts (from statsV2)",
      "title": "Repost Count",
      "type": "integer"
    },
    "scrapedAt": {
      "description": "UTC ISO 8601 timestamp when the record was built",
      "title": "Scraped At",
      "type": "string"
    },
    "searchedHashtag": {
      "description": "The normalized hashtag input that produced this record (without #)",
      "title": "Searched Hashtag",
      "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"
    },
    "shareMeta": {
      "description": "Share metadata object with title and desc fields",
      "title": "Share Meta",
      "type": "object"
    },
    "source": {
      "description": "'challenge_detail' on stats rows; 'feed' or 'search' on video rows",
      "title": "Source",
      "type": "string"
    },
    "stitchEnabled": {
      "description": "Whether stitching is allowed on this post",
      "title": "Stitch Enabled",
      "type": "boolean"
    },
    "textExtra": {
      "description": "Raw TikTok caption entities (hashtags and mentions) when present",
      "title": "Text Extra",
      "type": "array"
    },
    "textLanguage": {
      "description": "Language code of the caption text",
      "title": "Text Language",
      "type": "string"
    },
    "video": {
      "description": "Video metadata (width, height, duration, ratio, playUrl, cover)",
      "title": "Video",
      "type": "object"
    },
    "videoCount": {
      "description": "All-time number of videos using this hashtag, from statsV2 (string to avoid overflow)",
      "title": "Video Count",
      "type": "string"
    },
    "viewCount": {
      "description": "All-time views across all videos using this hashtag, from statsV2 (string)",
      "title": "View Count",
      "type": "string"
    }
  },
  "title": "TikTok Hashtag Stats and Video Feed Dataset",
  "views": {
    "overview": {
      "display": {
        "component": "table",
        "properties": {
          "caption": {
            "format": "text",
            "label": "Caption"
          },
          "carriesSearchedHashtag": {
            "format": "text",
            "label": "Has Tag"
          },
          "commentCount": {
            "format": "number",
            "label": "Comments"
          },
          "hashtagName": {
            "format": "text",
            "label": "Hashtag Name"
          },
          "likeCount": {
            "format": "number",
            "label": "Likes"
          },
          "playCount": {
            "format": "number",
            "label": "Plays"
          },
          "position": {
            "format": "number",
            "label": "Position"
          },
          "postId": {
            "format": "text",
            "label": "Post ID"
          },
          "postUrl": {
            "format": "link",
            "label": "Post URL"
          },
          "recordType": {
            "format": "text",
            "label": "Type"
          },
          "region": {
            "format": "text",
            "label": "Region"
          },
          "scrapedAt": {
            "format": "text",
            "label": "Scraped At"
          },
          "searchedHashtag": {
            "format": "text",
            "label": "Hashtag"
          },
          "source": {
            "format": "text",
            "label": "Source"
          },
          "videoCount": {
            "format": "text",
            "label": "Video Count"
          },
          "viewCount": {
            "format": "text",
            "label": "View Count"
          }
        }
      },
      "title": "Overview",
      "transformation": {
        "fields": [
          "recordType",
          "searchedHashtag",
          "hashtagName",
          "videoCount",
          "viewCount",
          "source",
          "postId",
          "caption",
          "carriesSearchedHashtag",
          "position",
          "region",
          "scrapedAt",
          "playCount",
          "likeCount",
          "commentCount",
          "postUrl"
        ]
      }
    }
  }
}

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 Hashtag Stats and Video Feed Collector

Collect the numbers behind a TikTok hashtag and the videos underneath it. For each tag you get one all-time stats row — total videos, total views, cover images, commerce flag — plus, when you ask for them, video rows with captions, authors, music, engagement counts, and the exact source each video came from. No TikTok login, cookies, or API key is needed.

What this actor does

  • Four modes: stats (cheapest), feed (the public hashtag video feed), search (one page of TikTok keyword/hashtag results), and auto (stats plus feed, with search added when the feed is short)
  • One aggregate row per hashtag: all-time videoCount and viewCount, canonical hashtag name and ID, description, commerce flag, cover images, and campaign announcement text when TikTok provides it
  • Video rows with provenance: source (feed or search), 1-based position, region, scrapedAt, and carriesSearchedHashtag so you can tell a true tag video from a search match
  • Rich video payload: post ID/URL/caption/language/creation time, play/like/comment/share/collect/repost counts, author profile and stats, music metadata, video dimensions and cover, slideshow images, hashtags, and mentions
  • Deduplicated and capped: repeated videos are removed by post ID, and every mode honours maxVideosPerHashtag
  • Empty fields are omitted from every record instead of being written as null

Output per hashtag stats row

Stats rows have recordType: "hashtag_metadata".

  • recordType — always "hashtag_metadata"
  • searchedHashtag — the requested hashtag, lowercased and without #
  • hashtag — the same requested hashtag, for convenient filtering
  • hashtagId — TikTok's internal challenge ID
  • hashtagName — hashtag name as TikTok stores it
  • desc — hashtag description, when present
  • isCommerce — whether TikTok marks the hashtag as commercial
  • videoCount — all-time video count, as a string because values can be very large
  • viewCount — all-time view count, as a string
  • profileLarger / profileMedium / profileThumb — hashtag image URLs
  • coverLarger / coverMedium / coverThumb — cover images, when present
  • announcementTitle / announcementBody — campaign announcement text, when present
  • shareMeta — {title, desc} share preview, when present
  • source — always "challenge_detail" on stats rows
  • region — market used for the run
  • scrapedAt — UTC ISO 8601 timestamp

Output per video

Video rows have recordType: "post".

  • recordType — always "post"
  • searchedHashtag — the hashtag whose run produced this video
  • source — "feed" for the hashtag feed or "search" for keyword-search results
  • position — 1-based order in the collected list for that hashtag
  • carriesSearchedHashtag — true when the video's own hashtag entities include the searched tag
  • carriesAnyHashtag — true when the caption carries at least one hashtag
  • region — market used for the run
  • scrapedAt — UTC ISO 8601 timestamp
  • postId, postType (video or slideshow), postUrl
  • caption, textLanguage, locationCreated
  • createTime (Unix epoch), createdAt (ISO 8601)
  • stitchEnabled, shareEnabled, duetEnabled, isAd
  • playCount, likeCount, commentCount, shareCount, collectCount, repostCount
  • author — {id, secUid, username, displayName, bio, verified, privateAccount, avatarUrl, followerCount, followingCount, heartCount, videoCount}
  • authorStats — the author's follower, following, heart, video, and digg counts as integers
  • music — {id, title, authorName, original, duration, playUrl, coverUrl}
  • video — {width, height, duration, ratio, playUrl, cover}
  • hashtags[] — {name, id, isCommerce} entries from the caption
  • mentions[] — {username, start, end, awemeId} entries from the caption
  • textExtra — raw caption entities when present
  • images[] — {ordinal, width, height, urlList} for slideshow posts
  • mediaUrlExpiresAt — UTC ISO 8601 expiry of the signed media URLs, when detectable

Input

Field Type Default Description
hashtags array – Hashtags to collect. Accepts fitness, #fitness, or a TikTok tag URL. Required, 1–10 values
mode string stats stats, feed, search, or auto
maxVideosPerHashtag integer 30 Upper bound per hashtag (0–300). Use 0 for stats only. This is a cap, not a promise — TikTok often returns fewer
maxHashtags integer 10 Process only the first N hashtags (1–10)
includeStatsRow boolean true Emit the stats row before video rows in feed, search, and auto. stats mode always emits it
region string US Market used for the feed. Only US is verified
proxyConfiguration object – Optional. Leave empty to use the built-in US residential connection with automatic fallback

Example: cheapest stats check

{
  "hashtags": ["fitness"],
  "mode": "stats",
  "maxVideosPerHashtag": 0
}

Example: collect 60 videos from a campaign hashtag

{
  "hashtags": ["summerfashion"],
  "mode": "feed",
  "maxVideosPerHashtag": 60,
  "includeStatsRow": true
}

Example: keyword search fallback

{
  "hashtags": ["booktok"],
  "mode": "search",
  "maxVideosPerHashtag": 40,
  "includeStatsRow": false
}

Example: several tags with auto mode

{
  "hashtags": ["#fitness", "gymtok", "https://www.tiktok.com/tag/workout"],
  "mode": "auto",
  "maxVideosPerHashtag": 90,
  "maxHashtags": 3
}

Use cases

  • Campaign sizing — compare all-time video and view counts across several brand or campaign hashtags in one run
  • Content research — sample current top videos under a niche tag and study hooks, captions, and creators
  • Creator discovery — rank authors by follower and engagement counts inside a hashtag feed
  • Social listening — track how a hashtag's totals change across repeated snapshots
  • Agency reporting — export one clean dataset of hashtag totals plus video-level evidence for a client deck
  • Search-gap analysis — compare true feed rows against keyword-search rows using source and carriesSearchedHashtag

Limits and data freshness

  • Region: the public video feed is verified for the United States only. Other markets are not exposed until each one is verified.
  • Feed size: TikTok serves roughly 30 videos per page and stops early for many tags. A typical run gets about 100–150 videos before the tag's own feed stops; very large tags can stop sooner. The platform's practical ceiling is far below “all videos”.
  • Search size: search returns one page of roughly 50–90 results. It is keyword search, not guaranteed tag membership — use carriesSearchedHashtag to filter.
  • No date or sort filter: TikTok's public hashtag feed does not expose a reliable date range or sort order, so this actor does not offer one.
  • Stats are all-time: videoCount and viewCount describe every video ever posted with the tag, not only the rows in your dataset.
  • Freshness: every record carries scrapedAt. Stats and video counts move continuously on TikTok.
  • Media URLs expire: video, audio, cover, and avatar URLs are signed and time-limited. mediaUrlExpiresAt tells you when they were expected to expire. Download or copy media promptly if you need it; this actor does not rehost files.
  • Empty fields are omitted: if TikTok does not return a field for a record, the field is absent rather than null.

FAQ

Do I need a TikTok login or cookies? No. The actor reads public hashtag pages and public search results without any account.

Do I need to configure a proxy? No. Leave proxyConfiguration empty and the actor picks a working US connection, falling back automatically if the preferred one is unavailable. Feed mode depends on a US residential connection, which is billed by Apify proxy usage. As a guide, feed runs often add roughly $0.05–0.06 per hashtag in residential transfer; stats-only runs are cheaper. Actual cost varies with run time, row count, and current Apify pricing.

Why did I get fewer videos than maxVideosPerHashtag? TikTok stops serving the public feed after its own internal limit. The actor also stops when TikTok reports no more pages, when no new videos arrive after repeated scrolls, or when the tag's feed is empty. maxVideosPerHashtag is an upper bound, never a guarantee.

Why is the stats row's view count larger than the views in my videos? Stats are all-time aggregates for the whole hashtag. Your dataset holds only the videos this run could collect.

Why do some search videos not carry my hashtag? TikTok search matches keywords and related content, not exact tag membership. Check carriesSearchedHashtag; false means the video appeared in search but does not list the searched tag as a hashtag entity.

What is the difference between feed and search? feed is TikTok's hashtag video feed and is the closest thing to "videos posted under this tag". search is a single page of keyword results and may include only loosely related videos. auto uses the feed and only adds search when the feed returns less than one page.

Does auto always add search results? No. Search is a supplement, not a replacement. It is used only when the feed delivers no rows or fewer than a full page, up to your per-hashtag cap.

Are duplicate videos removed? Yes. Videos are deduplicated by post ID within each hashtag. The same video can still appear under two different hashtags because each row records its searchedHashtag.

Do old saved tasks still work? Yes. Inputs using metadata, posts, topPosts, maxPostsPerHashtag, or includeHashtagMetadata are mapped to the new fields automatically.

Will media links still work tomorrow? TikTok media links are signed and expire. Use mediaUrlExpiresAt as a guide and refresh the dataset when you need current URLs.

Is this affiliated with TikTok? No. This is an independent third-party actor that reads publicly available TikTok pages and is not operated 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 Jaudae6KZydhY2aLp. Documentation is regenerated from the default published build; private drafts are not included.