TikTok Keyword Search & Discovery Scraper
Search TikTok by keyword or phrase and get matching videos with engagement stats, author profiles, music metadata, hashtags, and location tags, with optional discovery expansion for more results.
Input schema
Input fields, types, required flags, descriptions, defaults, and prefills come from the published definition. Prefill examples are distinct from defaults.
| Field | Type | Required | Description |
|---|---|---|---|
searchType | string | required | Videos: keyword video search. Photos: slideshow/photo search. Hashtags: hashtag metadata plus its top videos (uses the Hashtags field). |
queries | array | optional | Keywords or phrases to search for (searchType=videos or photos). Each query is searched independently. |
hashtags | array | optional | Hashtags to look up (searchType=hashtags). The # is optional, e.g. coffee or #coffee. |
Show all input fields
| Field | Type | Required | Description |
|---|---|---|---|
searchType | string | required | Videos: keyword video search. Photos: slideshow/photo search. Hashtags: hashtag metadata plus its top videos (uses the Hashtags field). |
queries | array | optional | Keywords or phrases to search for (searchType=videos or photos). Each query is searched independently. |
hashtags | array | optional | Hashtags to look up (searchType=hashtags). The # is optional, e.g. coffee or #coffee. |
maxResultsPerQuery | integer | optional | Maximum posts to collect per query or hashtag, before filters. TikTok itself usually returns about 150-220 posts per query even when you ask for more. For hashtags, the metadata row is not counted against this limit. |
sortBy | string | optional | Client-side sort applied after collecting results. TikTok's web search does not support server-side sorting, so this only reorders the posts TikTok returned. |
publishedWithin | string | optional | Client-side filter that drops posts older than the selected window, based on each post's creation time. TikTok ranks results by relevance, not date, so a narrow window can return fewer posts than requested; the actor scans up to 5x the requested count (capped at 500) to fill it. |
minDurationSeconds | integer | optional | Drop videos shorter than this. Leave blank for no minimum. |
maxDurationSeconds | integer | optional | Drop videos longer than this. Leave blank for no maximum. |
excludeAds | boolean | optional | Drop posts TikTok flags as paid advertisements. |
minLikeCount | integer | optional | Drop posts with fewer likes than this. Leave blank for no minimum. |
minViewCount | integer | optional | Drop posts with fewer plays/views than this. Leave blank for no minimum. |
uniqueAuthors | boolean | optional | Keep at most one post per creator, so the results read like a creator shortlist. When a sort is selected, the top-ranked post from each creator is kept. |
maxPostsPerAuthor | integer | optional | Optional cap on how many posts one creator may contribute. Ignored when One Post Per Author is on. |
enableExpansion | boolean | optional | After each keyword search, run extra searches derived from its own results: related searches, its top hashtags and its most-reused sound. Increases run time and the number of posts found. Videos only. |
expansionSources | array | optional | Which discovery sources to use when expansion is on. All three can be combined. |
maxExpansionQueriesPerSeed | integer | optional | Maximum number of extra searches to run per keyword when expansion is on. Each one costs roughly as much run time as the original search. |
includeHashtagMetadata | boolean | optional | For hashtag searches: emit a metadata row with the hashtag's post count, view count, description and images. Also attaches metadata to hashtag-expansion posts when expansion is on. |
Download the published input schema
View complete JSON
{
"description": "Search TikTok by keyword for videos or photo posts, or look up hashtag metadata and top posts. Optional discovery expansion finds more posts from related searches, hashtags and sounds.",
"properties": {
"enableExpansion": {
"default": false,
"description": "After each keyword search, run extra searches derived from its own results: related searches, its top hashtags and its most-reused sound. Increases run time and the number of posts found. Videos only.",
"prefill": false,
"title": "Enable Discovery Expansion",
"type": "boolean"
},
"excludeAds": {
"default": false,
"description": "Drop posts TikTok flags as paid advertisements.",
"prefill": false,
"title": "Exclude Ads",
"type": "boolean"
},
"expansionSources": {
"default": [
"suggestions",
"hashtags",
"sounds"
],
"description": "Which discovery sources to use when expansion is on. All three can be combined.",
"editor": "select",
"items": {
"enum": [
"suggestions",
"hashtags",
"sounds"
],
"enumTitles": [
"Related searches",
"Hashtags",
"Sounds"
],
"type": "string"
},
"prefill": [
"suggestions",
"hashtags",
"sounds"
],
"title": "Expansion Sources",
"type": "array"
},
"hashtags": {
"description": "Hashtags to look up (searchType=hashtags). The # is optional, e.g. coffee or #coffee.",
"editor": "stringList",
"example": [
"coffee",
"fitness"
],
"title": "Hashtags",
"type": "array"
},
"includeHashtagMetadata": {
"default": true,
"description": "For hashtag searches: emit a metadata row with the hashtag's post count, view count, description and images. Also attaches metadata to hashtag-expansion posts when expansion is on.",
"prefill": true,
"title": "Include Hashtag Metadata",
"type": "boolean"
},
"maxDurationSeconds": {
"description": "Drop videos longer than this. Leave blank for no maximum.",
"maximum": 7200,
"minimum": 0,
"title": "Maximum Duration (seconds)",
"type": "integer"
},
"maxExpansionQueriesPerSeed": {
"default": 3,
"description": "Maximum number of extra searches to run per keyword when expansion is on. Each one costs roughly as much run time as the original search.",
"maximum": 10,
"minimum": 0,
"prefill": 3,
"title": "Max Expansion Searches Per Query",
"type": "integer"
},
"maxPostsPerAuthor": {
"description": "Optional cap on how many posts one creator may contribute. Ignored when One Post Per Author is on.",
"maximum": 100,
"minimum": 1,
"title": "Max Posts Per Author",
"type": "integer"
},
"maxResultsPerQuery": {
"default": 30,
"description": "Maximum posts to collect per query or hashtag, before filters. TikTok itself usually returns about 150-220 posts per query even when you ask for more. For hashtags, the metadata row is not counted against this limit.",
"maximum": 500,
"minimum": 1,
"prefill": 3,
"title": "Max Results Per Query",
"type": "integer"
},
"minDurationSeconds": {
"description": "Drop videos shorter than this. Leave blank for no minimum.",
"maximum": 7200,
"minimum": 0,
"title": "Minimum Duration (seconds)",
"type": "integer"
},
"minLikeCount": {
"description": "Drop posts with fewer likes than this. Leave blank for no minimum.",
"minimum": 0,
"title": "Minimum Like Count",
"type": "integer"
},
"minViewCount": {
"description": "Drop posts with fewer plays/views than this. Leave blank for no minimum.",
"minimum": 0,
"title": "Minimum View Count",
"type": "integer"
},
"publishedWithin": {
"default": "all",
"description": "Client-side filter that drops posts older than the selected window, based on each post's creation time. TikTok ranks results by relevance, not date, so a narrow window can return fewer posts than requested; the actor scans up to 5x the requested count (capped at 500) to fill it.",
"editor": "select",
"enum": [
"all",
"day",
"week",
"month",
"3months",
"6months"
],
"enumTitles": [
"All time",
"Past 24 hours",
"Past week",
"Past month",
"Past 3 months",
"Past 6 months"
],
"prefill": "all",
"title": "Published Within",
"type": "string"
},
"queries": {
"description": "Keywords or phrases to search for (searchType=videos or photos). Each query is searched independently.",
"editor": "stringList",
"example": [
"cooking recipes",
"travel vlog",
"@khaby.lame"
],
"prefill": [
"viral"
],
"title": "Search Queries",
"type": "array"
},
"searchType": {
"default": "videos",
"description": "Videos: keyword video search. Photos: slideshow/photo search. Hashtags: hashtag metadata plus its top videos (uses the Hashtags field).",
"editor": "select",
"enum": [
"videos",
"photos",
"hashtags"
],
"enumTitles": [
"Videos by keyword",
"Photo / slideshow posts",
"Hashtags (metadata + top posts)"
],
"prefill": "videos",
"title": "What to Search",
"type": "string"
},
"sortBy": {
"default": "relevance",
"description": "Client-side sort applied after collecting results. TikTok's web search does not support server-side sorting, so this only reorders the posts TikTok returned.",
"editor": "select",
"enum": [
"relevance",
"mostLiked",
"mostViewed",
"newest"
],
"enumTitles": [
"Relevance (TikTok's default order)",
"Most liked",
"Most viewed",
"Newest first"
],
"prefill": "relevance",
"title": "Sort By",
"type": "string"
},
"uniqueAuthors": {
"default": false,
"description": "Keep at most one post per creator, so the results read like a creator shortlist. When a sort is selected, the top-ranked post from each creator is kept.",
"prefill": false,
"title": "One Post Per Author",
"type": "boolean"
}
},
"required": [
"searchType"
],
"schemaVersion": 1,
"title": "TikTok Keyword Discovery — Videos, Photos, Hashtags & Creators 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.
| Field | Type | Required | Description |
|---|---|---|---|
data | string | optional | Dataset with one row per TikTok video/photo result (rowType='video', including slideshows) plus hashtag metadata rows (rowType='hashtag') for hashtag searches. |
Download the published output schema
View complete JSON
{
"actorOutputSchemaVersion": 1,
"properties": {
"data": {
"description": "Dataset with one row per TikTok video/photo result (rowType='video', including slideshows) plus hashtag metadata rows (rowType='hashtag') for hashtag searches.",
"template": "{{links.apiDefaultDatasetUrl}}/items",
"title": "Search Results",
"type": "string"
}
},
"title": "TikTok Keyword Discovery — Videos, Photos, Hashtags & Creators 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": {
"AIGCDescription": {
"description": "Description for AI-generated content label",
"title": "AIGC Description",
"type": "string"
},
"adLabelVersion": {
"description": "Ad label version; non-zero when the post is a sponsored ad",
"title": "Ad Label Version",
"type": "integer"
},
"aiContentEligible": {
"description": "Whether TikTok's own AI-content detection flagged this post as eligible for AI-generated-content review",
"title": "AI Content Eligible",
"type": "boolean"
},
"aigcLabelType": {
"description": "TikTok's AI-generated-content moderation label type, when present",
"title": "AIGC Label Type",
"type": "integer"
},
"author": {
"description": "Author/creator information at time of scraping",
"properties": {
"avatarUrl": {
"title": "Avatar URL",
"type": "string"
},
"bio": {
"title": "Bio",
"type": "string"
},
"displayName": {
"title": "Display Name",
"type": "string"
},
"followerCount": {
"title": "Followers",
"type": "integer"
},
"followingCount": {
"title": "Following",
"type": "integer"
},
"heartCount": {
"title": "Hearts/Likes",
"type": "integer"
},
"id": {
"title": "User ID",
"type": "string"
},
"isVirtualAccount": {
"description": "Whether TikTok flags this as an AI-generated/virtual-avatar account",
"title": "Is Virtual Account",
"type": "boolean"
},
"privateAccount": {
"title": "Private Account",
"type": "boolean"
},
"roomId": {
"title": "Live Room ID (non-empty when live)",
"type": "string"
},
"secUid": {
"title": "Secure UID",
"type": "string"
},
"username": {
"title": "Username",
"type": "string"
},
"verified": {
"title": "Verified",
"type": "boolean"
},
"videoCount": {
"title": "Videos",
"type": "integer"
}
},
"title": "Author",
"type": "object"
},
"caption": {
"description": "Post caption/description text including hashtags and mentions",
"title": "Caption",
"type": "string"
},
"categoryType": {
"description": "TikTok's internal numeric content-category ID",
"title": "Category Type",
"type": "integer"
},
"collectCount": {
"description": "Number of times post was bookmarked/collected",
"title": "Collect Count",
"type": "integer"
},
"commentCount": {
"description": "Number of comments",
"title": "Comment Count",
"type": "integer"
},
"commentStatus": {
"description": "Comment permission setting (0=all, 1=friends, 2=none)",
"title": "Comment Status",
"type": "integer"
},
"contents": {
"description": "Localized/alternate caption variants for this post, when TikTok provides more than one",
"title": "Content Variants",
"type": "array"
},
"createTime": {
"description": "Unix timestamp (seconds) when the post was created",
"title": "Create Time (Unix)",
"type": "integer"
},
"createdAt": {
"description": "ISO 8601 UTC datetime when the post was created",
"title": "Created At",
"type": "string"
},
"duetEnabled": {
"description": "Whether the creator allows duets with this video",
"title": "Duet Enabled",
"type": "boolean"
},
"effectStickers": {
"description": "AR effects/filters used in the post, with usage-count stats",
"title": "Effect Stickers",
"type": "array"
},
"expansionSource": {
"description": "How this result was discovered when Enable Expansion is on: 'sound', 'hashtag', or 'suggestion'. Absent for primary/seed-query results.",
"title": "Expansion Source",
"type": "string"
},
"expansionTerm": {
"description": "The sound title, hashtag, or suggested search phrase that surfaced this result via expansion. Absent for primary/seed-query results.",
"title": "Expansion Term",
"type": "string"
},
"forFriend": {
"description": "Whether the post is restricted to friends only",
"title": "For Friend",
"type": "boolean"
},
"hashtagMetadata": {
"description": "Hashtag details from TikTok for searchType=hashtags and hashtag-expansion rows.",
"properties": {
"desc": {
"title": "Description",
"type": "string"
},
"hashtagId": {
"title": "Hashtag ID",
"type": "string"
},
"isCommerce": {
"title": "Commerce Hashtag",
"type": "boolean"
},
"name": {
"title": "Name",
"type": "string"
},
"profileUrl": {
"title": "Profile Image URL",
"type": "string"
},
"shareDesc": {
"title": "Share Description",
"type": "string"
},
"shareTitle": {
"title": "Share Title",
"type": "string"
},
"videoCount": {
"title": "Videos",
"type": "integer"
},
"viewCount": {
"title": "Views",
"type": "integer"
}
},
"title": "Hashtag Metadata",
"type": "object"
},
"hashtags": {
"description": "Hashtags used in the post caption",
"items": {
"properties": {
"id": {
"title": "Hashtag ID",
"type": "string"
},
"isCommerce": {
"title": "Is Commerce",
"type": "boolean"
},
"name": {
"title": "Hashtag Name",
"type": "string"
}
},
"type": "object"
},
"title": "Hashtags",
"type": "array"
},
"images": {
"description": "Images for slideshow posts (postType='slideshow')",
"items": {
"properties": {
"height": {
"title": "Height",
"type": "integer"
},
"ordinal": {
"title": "Index",
"type": "integer"
},
"urlList": {
"title": "Image URLs",
"type": "array"
},
"width": {
"title": "Width",
"type": "integer"
}
},
"type": "object"
},
"title": "Slideshow Images",
"type": "array"
},
"isAd": {
"description": "Whether this post is a paid advertisement",
"title": "Is Ad",
"type": "boolean"
},
"isAigc": {
"description": "Whether TikTok labels this as AI-generated content",
"title": "Is AI Generated Content",
"type": "boolean"
},
"isECVideo": {
"description": "Whether this is a shopping/e-commerce video",
"title": "Is E-Commerce Video",
"type": "boolean"
},
"item_control": {
"description": "Post-level permission flags (e.g. can_repost)",
"title": "Item Control",
"type": "object"
},
"likeCount": {
"description": "Number of likes/diggs",
"title": "Like Count",
"type": "integer"
},
"locationCreated": {
"description": "ISO country code where the post was created (e.g. 'US', 'IT')",
"title": "Location Created",
"type": "string"
},
"mediaUrlExpiresAt": {
"description": "ISO 8601 UTC datetime when signed media URLs (video/cover) expire",
"title": "Media URL Expires At",
"type": "string"
},
"mentions": {
"description": "@mentions in the post caption with text offsets",
"items": {
"properties": {
"awemeId": {
"title": "Mentioned Post ID (if any)",
"type": "string"
},
"end": {
"title": "End Offset",
"type": "integer"
},
"start": {
"title": "Start Offset",
"type": "integer"
},
"username": {
"title": "Username",
"type": "string"
}
},
"type": "object"
},
"title": "Mentions",
"type": "array"
},
"moderationAigcLabelType": {
"description": "TikTok's moderation-specific AI-generated-content label type, when present",
"title": "Moderation AIGC Label Type",
"type": "integer"
},
"music": {
"description": "Sound/music used in the post",
"properties": {
"album": {
"title": "Album",
"type": "string"
},
"authorName": {
"title": "Author",
"type": "string"
},
"coverUrl": {
"title": "Cover URL",
"type": "string"
},
"dspLinks": {
"title": "DSP Links (Spotify, Apple Music, etc.)",
"type": "array"
},
"duration": {
"title": "Duration (s)",
"type": "integer"
},
"id": {
"title": "Music ID",
"type": "string"
},
"isCommerceMusic": {
"title": "Is Commerce Music",
"type": "boolean"
},
"isCopyrighted": {
"title": "Is Copyrighted",
"type": "boolean"
},
"isPrivate": {
"title": "Is Private Sound",
"type": "boolean"
},
"isUnlimitedMusic": {
"title": "Is Unlimited Music",
"type": "boolean"
},
"original": {
"title": "Original Sound",
"type": "boolean"
},
"playUrl": {
"title": "Play URL",
"type": "string"
},
"shootDuration": {
"title": "Shoot Duration (s)",
"type": "number"
},
"title": {
"title": "Title",
"type": "string"
}
},
"title": "Music",
"type": "object"
},
"originalItem": {
"description": "Whether this post is an original (non-duet, non-stitch) creation",
"title": "Original Item",
"type": "boolean"
},
"playCount": {
"description": "Number of video plays",
"title": "Play Count",
"type": "integer"
},
"poi": {
"description": "Location tag attached to the post (restaurant, landmark, etc.) — only present when TikTok attaches a POI to the search result",
"properties": {
"address": {
"type": "string"
},
"category": {
"type": "string"
},
"city": {
"type": "string"
},
"country": {
"type": "string"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
}
},
"title": "Point of Interest",
"type": "object"
},
"postId": {
"description": "TikTok's unique post/video ID (string to preserve 64-bit precision)",
"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"
},
"privateItem": {
"description": "Whether the post is set to private",
"title": "Private Item",
"type": "boolean"
},
"query": {
"description": "The search keyword or phrase used to find this post",
"title": "Search Query",
"type": "string"
},
"queryVariant": {
"description": "Casing/spacing variant of the query that found this post, when a query pass was used.",
"title": "Query Variant",
"type": "string"
},
"rank": {
"description": "0-based position of this post in the search results for the given query",
"title": "Rank",
"type": "integer"
},
"repostCount": {
"description": "Number of reposts",
"title": "Repost Count",
"type": "integer"
},
"rowType": {
"description": "'video' for all post rows (video or slideshow; use postType to distinguish) or 'hashtag' for hashtag metadata rows.",
"title": "Row Type",
"type": "string"
},
"searchType": {
"description": "How the row was found: 'general' keyword search, 'photo' search, 'hashtag' search, or 'sound' expansion.",
"title": "Search Type",
"type": "string"
},
"shareCount": {
"description": "Number of shares",
"title": "Share Count",
"type": "integer"
},
"shareEnabled": {
"description": "Whether sharing is enabled for this post",
"title": "Share Enabled",
"type": "boolean"
},
"stickersOnItem": {
"description": "Text/interactive sticker overlays on the post",
"title": "Stickers On Item",
"type": "array"
},
"stitchEnabled": {
"description": "Whether the creator allows stitching this video",
"title": "Stitch Enabled",
"type": "boolean"
},
"textLanguage": {
"description": "Detected language code of the caption text",
"title": "Text Language",
"type": "string"
},
"video": {
"description": "Video technical metadata and signed play/cover URLs",
"properties": {
"bitrate": {
"title": "Bitrate",
"type": "integer"
},
"bitrateInfo": {
"description": "All available quality/bitrate variants with play URLs",
"title": "Bitrate Info",
"type": "array"
},
"codecType": {
"title": "Codec",
"type": "string"
},
"cover": {
"title": "Cover Image URL",
"type": "string"
},
"coverVariants": {
"description": "Additional cover-image resolutions keyed by pixel size",
"title": "Cover Variants",
"type": "object"
},
"definition": {
"title": "Definition",
"type": "string"
},
"downloadAddr": {
"description": "Signed TikTok CDN download URL. Reference-only: the CDN commonly rejects direct downloads (HTTP 403) outside TikTok.",
"title": "Download URL",
"type": "string"
},
"duration": {
"title": "Duration (s)",
"type": "integer"
},
"dynamicCover": {
"title": "Dynamic Cover URL",
"type": "string"
},
"format": {
"title": "Format",
"type": "string"
},
"height": {
"title": "Height (px)",
"type": "integer"
},
"originCover": {
"title": "Original Cover URL",
"type": "string"
},
"playUrl": {
"description": "Signed TikTok CDN play URL. Reference-only: the CDN can reject direct downloads (HTTP 403) outside TikTok.",
"title": "Play URL",
"type": "string"
},
"qualityScore": {
"description": "TikTok's internal video-quality score (VQScore)",
"title": "Quality Score",
"type": "number"
},
"qualityTags": {
"description": "UI badge labels TikTok attaches to the video (e.g. 'Top liked')",
"title": "Quality Tags",
"type": "array"
},
"ratio": {
"title": "Aspect Ratio",
"type": "string"
},
"size": {
"title": "File Size (bytes)",
"type": "integer"
},
"subtitles": {
"description": "Available caption tracks (auto-generated or original) with expiring VTT URLs",
"items": {
"properties": {
"format": {
"type": "string"
},
"isAutoGenerated": {
"type": "boolean"
},
"isOriginal": {
"type": "boolean"
},
"language": {
"type": "string"
},
"languageCode": {
"type": "string"
},
"url": {
"type": "string"
}
},
"type": "object"
},
"title": "Subtitles/Captions",
"type": "array"
},
"videoQuality": {
"title": "Video Quality Label",
"type": "string"
},
"volumeInfo": {
"description": "Audio loudness and peak level metadata",
"title": "Volume Info",
"type": "object"
},
"width": {
"title": "Width (px)",
"type": "integer"
}
},
"title": "Video",
"type": "object"
},
"viewerRegion": {
"description": "ISO country code of the inferred viewer region",
"title": "Viewer Region",
"type": "string"
}
},
"title": "TikTok Search Results",
"views": {
"hashtags": {
"description": "Hashtag metadata rows emitted by searchType=hashtags, including post and view counts.",
"display": {
"component": "table",
"properties": {
"hashtagMetadata": {
"format": "object",
"label": "Metadata"
},
"query": {
"format": "text",
"label": "Hashtag"
},
"searchType": {
"format": "text",
"label": "Search Type"
}
}
},
"title": "Hashtag Results",
"transformation": {
"fields": [
"query",
"searchType",
"hashtagMetadata"
]
}
},
"overview": {
"description": "TikTok video and photo posts returned by keyword search with rank, query context, and engagement metrics.",
"display": {
"component": "table",
"properties": {
"caption": {
"format": "text",
"label": "Caption"
},
"commentCount": {
"format": "number",
"label": "Comments"
},
"createdAt": {
"format": "text",
"label": "Created At"
},
"likeCount": {
"format": "number",
"label": "Likes"
},
"playCount": {
"format": "number",
"label": "Views"
},
"postId": {
"format": "text",
"label": "Post ID"
},
"postUrl": {
"format": "link",
"label": "Post URL"
},
"query": {
"format": "text",
"label": "Search Query"
},
"rank": {
"format": "number",
"label": "Rank"
},
"searchType": {
"format": "text",
"label": "Search Type"
},
"shareCount": {
"format": "number",
"label": "Shares"
}
}
},
"title": "Search Results Overview",
"transformation": {
"fields": [
"postId",
"query",
"rank",
"searchType",
"caption",
"createdAt",
"author",
"playCount",
"likeCount",
"commentCount",
"shareCount",
"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 Keyword Discovery — Videos, Photos, Hashtags & Creators
Search TikTok by keyword and get structured video and photo posts, or look up a hashtag's own metadata. Each row carries the caption, engagement counts, creator snapshot, sound details, hashtags and mentions, and the post's stable URL. Optional discovery expansion runs related searches, top hashtags and the most-reused sound from a keyword's own results to surface more posts. No login, cookies or proxy setup required.
What this actor does
- Three search types:
videosby keyword,photos/slideshows by keyword, andhashtagswith metadata plus the tag's top posts - Deep pagination: keeps loading until TikTok's own result pool ends or your per-query limit is reached (TikTok typically serves about 150–220 posts per query)
- Video and photo rows: captions, creation time, plays, likes, comments, shares, saves, reposts, creator profile snapshot, sound metadata, video technical details and slideshow images
- Discovery expansion: related searches, top hashtags and sound pages, deduplicated against the seed results
- Client-side controls: sort by relevance/likes/views/newest, date window, duration, ads, minimum likes/views, one post per creator and max posts per creator
- Honest empty fields: fields TikTok does not provide for a post are omitted, never
null
Output per post
rowType—videofor all post rows (including slideshows;postTypesays which) orhashtagfor hashtag metadata rowspostId,postUrl,postType— post identity;postTypeisvideoorslideshowcaption,textLanguage,createTime,createdAtquery— the search phrase that returned this post;rank— zero-based position in that search;queryVariant— casing/spacing variant when a query pass found itsearchType—general,photo,hashtagorsoundexpansionSource,expansionTerm— only on expansion-discovered postsplayCount,likeCount,commentCount,shareCount,collectCount,repostCountauthor.id,author.secUid,author.username,author.displayName,author.bio,author.verified,author.privateAccount,author.avatarUrl,author.followerCount,author.followingCount,author.heartCount,author.videoCount,author.roomId,author.isVirtualAccountmusic.id,music.title,music.authorName,music.original,music.duration,music.album,music.playUrl,music.coverUrl, and licensing flagsvideo.width,video.height,video.duration,video.ratio,video.definition,video.bitrate,video.codecType,video.format,video.size,video.videoQuality,video.playUrl,video.downloadAddr— signed CDN URLs, reference-only: TikTok's CDN can reject direct downloads (HTTP 403) even while the post plays on TikTokvideo.cover,video.originCover,video.dynamicCover,video.subtitles,video.qualityScore,video.qualityTags,video.coverVariants,video.bitrateInfo,video.volumeInfoimages[]— real image URLs for slideshow/photo postshashtags[],mentions[],poi— caption tags, @mentions and a location tag when presentisAd,isECVideo,adLabelVersion,privateItem,forFriend,isAigc,AIGCDescription,originalItem,categoryType,item_control,contents,effectStickers,stickersOnItemmediaUrlExpiresAt— when the signed media URLs expire, when it can be read
Output per hashtag
Hashtag searches emit one metadata row (rowType: "hashtag") before the tag's post rows:
query— the hashtag, e.g.#coffeehashtagMetadata.hashtagId,name,desc,isCommerce,videoCount,viewCount,profileUrl,shareTitle,shareDesc
Input
| Field | Type | Default | Description |
|---|---|---|---|
searchType |
string | videos |
videos, photos or hashtags |
queries |
string[] | — | Keywords for video/photo search |
hashtags |
string[] | — | Hashtags for searchType=hashtags; # optional |
maxResultsPerQuery |
integer | 30 | Posts per query/tag before filters (1–500) |
sortBy |
string | relevance |
relevance, mostLiked, mostViewed, newest |
publishedWithin |
string | all |
all, day, week, month, 3months, 6months; narrow windows scan up to 5× the requested count |
minDurationSeconds |
integer | — | Drop shorter videos |
maxDurationSeconds |
integer | — | Drop longer videos |
excludeAds |
boolean | false | Drop posts flagged as ads |
minLikeCount |
integer | — | Drop posts with fewer likes |
minViewCount |
integer | — | Drop posts with fewer plays |
uniqueAuthors |
boolean | false | Keep at most one post per creator |
maxPostsPerAuthor |
integer | — | Cap posts per creator (ignored when uniqueAuthors is on) |
enableExpansion |
boolean | false | Run related searches, hashtags and sounds |
expansionSources |
string[] | ["suggestions", "hashtags", "sounds"] |
suggestions, hashtags, sounds |
maxExpansionQueriesPerSeed |
integer | 3 | Extra searches per keyword (0–10) |
includeHashtagMetadata |
boolean | true | Emit hashtag metadata rows/expansion metadata |
Examples
Keyword video search
{
"searchType": "videos",
"queries": ["cottagecore aesthetic", "dark academia"],
"maxResultsPerQuery": 100,
"sortBy": "mostLiked",
"publishedWithin": "month"
}
Photo / slideshow search
{
"searchType": "photos",
"queries": ["coffee"],
"maxResultsPerQuery": 50
}
Hashtag discovery
{
"searchType": "hashtags",
"hashtags": ["coffee", "#fitness"],
"maxResultsPerQuery": 30,
"includeHashtagMetadata": true
}
Creator shortlist with expansion
{
"searchType": "videos",
"queries": ["skincare routine"],
"maxResultsPerQuery": 200,
"uniqueAuthors": true,
"enableExpansion": true,
"expansionSources": ["suggestions", "hashtags"],
"maxExpansionQueriesPerSeed": 3
}
Use cases
- Content research — see which videos and slideshows rank for a topic, newest or most liked
- Creator discovery — collapse results to one post per creator, then review follower counts
- Brand and campaign monitoring — track keyword mentions, hashtags and sounds across markets
- Trend exploration — expand a seed keyword through TikTok's own related searches and tags
- Dataset building — export structured post, engagement and creator fields for analysis
FAQ
Do I need a TikTok account or cookies?
No. The actor reads the search results TikTok shows to signed-out visitors. There is no cookie, token or proxy field to fill in. TikTok sometimes asks anonymous cloud IPs to log in; the actor automatically retries with a different proxy session and never asks you for credentials.
How many results can I get per query?
TikTok's own search pool usually ends around 150–220 posts per query, even when a larger limit is requested. The actor follows TikTok's cursor until the pool ends, then optionally runs casing and spacing query passes. To collect more, add more queries or turn on discovery expansion.
Why is my result count lower than maxResultsPerQuery?
Two reasons: TikTok may exhaust its result pool before your limit, and the date, duration, ads, likes, views and creator filters run after collection. The final dataset is always the post-filter set.
Are the sort and filters applied by TikTok or by the actor?
By the actor. The actor collects posts in TikTok's default relevance order and then applies your sort and filters locally. For a narrow date window it scans up to 5× the requested count (capped at 500) before applying the window; because that scan is bounded, a week or day window can legitimately yield fewer posts than requested.
What is the difference between the video and photo search types?
Both return posts for a keyword. Video search uses TikTok's main search ranking; photo search
uses its photo/slideshow vertical. Photo rows always include images[]; video playback fields
are omitted because slideshows do not carry a direct video stream.
Why are some fields missing on some posts?
TikTok includes fields such as subtitles, location, mentions, AR effects or quality badges only when they apply. Empty fields are omitted rather than filled with placeholders.
Do the media URLs work forever?
TikTok's media URLs are signed and expire; mediaUrlExpiresAt shows when the actor can read the
expiry. Use postUrl as the stable reference. Cover images, avatars and sound preview URLs are
generally directly accessible, but TikTok's video CDN may reject direct downloads even while the
video plays on TikTok itself.
Can I search a specific country or language?
Queries can be in any language and return results in that language. TikTok's web search does not
expose a working country filter: its region parameter is cosmetic and results still follow the
request's network location, so no region input is offered.
Can I search for creators directly?
This actor focuses on posts and hashtags. To find creators, use uniqueAuthors on a keyword
search (one post per creator), or turn on expansion to reach more creators through related
searches, hashtags and sounds. A dedicated user-search mode is not included because TikTok
serves it inconsistently to signed-out sessions.
Is this affiliated with TikTok?
No. It is an independent tool that reads TikTok's public search results and is not run 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.
Source build glvpB5jtOmv1fSco2. Documentation is regenerated from the default published build; private drafts are not included.