Searching TikTok for a keyword is how most research starts: what gets posted about a product, a recipe or a hobby, who posts it, and which sounds those videos use. The app shows you a scrolling feed. Your code needs the same results as rows with numbers.
This guide calls /feed/search from Python, pages through the results with the two values TikTok needs, removes the duplicates TikTok sends, and writes a CSV. Then it reads the results three ways: what was posted recently, which creators show up most, and which sounds they use. Every response and output below is real, from a search for "outdoor cooking" on October 6, 2026.
Before you start
- A ScrapingBot API key. Create a free account for 100 credits, enough for 100 pages of search results. No card needed.
- Python 3.9 or newer and
pip install requests. - A keyword. Plain words work best; this guide uses
outdoor cooking.
One page of results: /feed/search
Send the keyword as keywords. region is a two-letter country code and count is how many videos you would like, from 1 to 50.
curl -X POST https://scrapingbot.io/api/v1/tiktok \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"endpoint": "/feed/search", "params": {"keywords": "outdoor cooking", "count": 10, "region": "US"}}'
The videos are in data.videos. Next to them are the three fields that control paging: cursor, hasMore and searchId. Here is the first video of ten:
{
"success": true,
"data": {
"cursor": 10,
"hasMore": true,
"searchId": "202610061617302E02E99C996C1B42EB75",
"videos": [
{
"video_id": "7638768005693967630",
"title": "Steak & Shakshuka: A Primitive Feast in the Wild #menwiththepot #outdoorcooking #asmrfood #steak #bushcraft ",
"create_time": 1778539297,
"duration": 146,
"play_count": 22400,
"digg_count": 618,
"comment_count": 9,
"share_count": 82,
"collect_count": 119,
"author": { "id": "7407408101190485034", "unique_id": "pot_fire", "nickname": "Pot & Fire", … },
"music_info": { "id": "7638768131036531469", "title": "original sound - pot_fire", "original": true, … },
"cha_list": [ { "cha_name": "menwiththepot", … }, { "cha_name": "outdoorcooking", … }, … ],
"share_url": "https://www.tiktok.com/@pot_fire/video/7638768005693967630",
"is_ad": false,
"play": "https://www.tiktok.com/aweme/v1/play/?…",
…
},
…
]
},
"duration": "3.82",
"statusCode": 200,
"creditsUsed": 1
}
| Field | What it is |
|---|---|
video_id | The video's ID. Use it to de-duplicate and to build the link. |
title | The caption, hashtags included. |
create_time | When it was posted, as a Unix timestamp in seconds. This one is May 11, 2026. |
play_count, digg_count | Views and likes. |
comment_count, share_count, collect_count | Comments, shares and saves. |
duration | Length in seconds. |
author.unique_id | The creator's handle, without @. |
music_info.id, music_info.title | The sound the video uses. original is true when the creator recorded it. |
cha_list[].cha_name | The video's hashtags, without #. |
play, wmplay | Video file links without and with the watermark. They are signed and expire, so download soon if you need the file. |
Paging: cursor and search_id
For the next page, send the same keywords plus two values from the page you just got: cursor as offset, and searchId as search_id. The search ID ties your pages to one result set. Leave it out and the call fails with 400, which is not charged:
{ "success": false, "error": "search_id is required when offset is greater than zero." }
Each page comes back with a new searchId; send the latest one each time. Keep going while hasMore is true.
Page sizes vary, and pages overlap
We asked for 10 videos per page. TikTok sent 10 on page 1, 73 on page 2 and 77 on page 3. Of those 160 results, only 94 were different videos: page 2 repeated one video from page 1, and page 3 repeated 65 from earlier pages. So never count results by page size, and always de-duplicate on video_id. The upside: three credits bought 94 videos.
The script
Save this as tiktok_search.py. It fetches up to pages pages, keeps one copy of each video, turns each one into a flat row with an engagement rate, prints a short report and writes the rows to CSV.
import csv
import sys
import time
from collections import Counter
from datetime import datetime, timezone
import requests
API = "https://scrapingbot.io/api/v1/tiktok"
HEADERS = {"x-api-key": "YOUR_API_KEY"}
def call(endpoint, params, tries=4):
"""One API call (1 credit). Failed calls are refunded, so a retry costs nothing extra."""
for attempt in range(tries):
res = requests.post(API, headers=HEADERS, timeout=60,
json={"endpoint": endpoint, "params": params})
body = res.json()
if body.get("success"):
return body["data"]
if res.status_code in (400, 401, 402): # bad input, bad key, no credits
raise RuntimeError(body.get("error"))
time.sleep(2 ** attempt) # 408, 429 or 5xx: wait and try again
raise RuntimeError(f"{endpoint} kept failing: {body.get('error')}")
def search_videos(keywords, pages=3, region="US"):
"""Page through /feed/search and keep one copy of each video."""
videos, rows_seen = {}, 0
params = {"keywords": keywords, "count": 10, "region": region}
for page in range(1, pages + 1):
data = call("/feed/search", params)
batch = data.get("videos") or []
rows_seen += len(batch)
for v in batch:
videos.setdefault(v["video_id"], v)
print(f"page {page}: {len(batch)} results, {len(videos)} unique so far")
if not data.get("hasMore") or not batch:
break
# The next page needs both the cursor and the search ID from this one.
params = {**params, "offset": data["cursor"], "search_id": data["searchId"]}
return list(videos.values()), rows_seen
def to_row(v):
views = v.get("play_count") or 0
actions = v.get("digg_count", 0) + v.get("comment_count", 0) + v.get("share_count", 0)
music = v.get("music_info") or {}
return {
"video_id": v["video_id"],
"url": f"https://www.tiktok.com/@{v['author']['unique_id']}/video/{v['video_id']}",
"posted": datetime.fromtimestamp(v["create_time"], timezone.utc).date().isoformat(),
"author": v["author"]["unique_id"],
"views": views,
"likes": v.get("digg_count", 0),
"comments": v.get("comment_count", 0),
"shares": v.get("share_count", 0),
"saves": v.get("collect_count", 0),
"engagement_pct": round(100 * actions / views, 2) if views else 0,
"sound_id": music.get("id", ""),
"sound": music.get("title", ""),
"hashtags": " ".join(c["cha_name"] for c in v.get("cha_list") or []),
"caption": (v.get("title") or "").split("\n")[0][:120].strip(),
}
if __name__ == "__main__":
keywords = sys.argv[1] if len(sys.argv) > 1 else "outdoor cooking"
pages = int(sys.argv[2]) if len(sys.argv) > 2 else 3
days = int(sys.argv[3]) if len(sys.argv) > 3 else 30
videos, rows_seen = search_videos(keywords, pages)
rows = sorted((to_row(v) for v in videos), key=lambda r: r["views"], reverse=True)
print(f"{len(rows)} unique videos from {rows_seen} results")
now = datetime.now(timezone.utc).timestamp()
recent = [to_row(v) for v in videos if now - v["create_time"] <= days * 86400]
recent.sort(key=lambda r: r["views"], reverse=True)
print(f"\nposted in the last {days} days: {len(recent)}")
for r in recent[:5]:
print(f" {r['posted']} {r['views']:>10,} views @{r['author']}")
print("\ncreators with the most videos in these results:")
for author, n in Counter(r["author"] for r in rows).most_common(3):
views = sum(r["views"] for r in rows if r["author"] == author)
print(f" @{author}: {n} videos, {views:,} views")
print("\nsounds used most:")
sounds = Counter((r["sound_id"], r["sound"]) for r in rows if r["sound_id"])
for (sound_id, title), n in sounds.most_common(3):
print(f" {sound_id} {n} videos {title}")
path = f"tiktok_search_{keywords.replace(' ', '_')}.csv"
with open(path, "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=list(rows[0]) if rows else ["video_id"])
writer.writeheader()
writer.writerows(rows)
print(f"\nsaved {len(rows)} videos to {path}")
- Paging carries both values forward. After each page,
paramsgets the newoffsetandsearch_id. The loop stops whenhasMoreis false, when a page comes back empty, or afterpagespages, so a run never costs more thanpagescredits. videos.setdefaultkeeps the first copy of eachvideo_id, androws_seencounts everything TikTok sent, so the report shows how many duplicates were dropped.- Engagement is likes, comments and shares divided by views. Dividing by views compares a video with 22,400 views fairly against one with 34 million. Add
savesif they matter for your topic; recipes get saved a lot. - Errors: a
400(bad input),401(bad key) or402(out of credits) stops the run at once. Timeouts,429and server errors are retried with a growing pause, and failed calls are refunded.
Run it
Pass the keyword, the number of pages and how many days count as recent:
python tiktok_search.py "outdoor cooking" 3 30
page 1: 10 results, 10 unique so far
page 2: 73 results, 82 unique so far
page 3: 77 results, 94 unique so far
94 unique videos from 160 results
posted in the last 30 days: 14
2026-09-16 2,500,000 views @primeplates4
2026-09-18 1,500,000 views @tftk007
2026-09-25 1,400,000 views @tftk007
2026-09-29 825,200 views @tftk007
2026-09-24 585,700 views @tftk007
creators with the most videos in these results:
@tftk007: 10 videos, 18,582,600 views
@aos_outdoorkitchens: 3 videos, 538,200 views
@tftk001: 2 videos, 13,195,400 views
sounds used most:
7061837257761639194 15 videos nhạc nền - Dương Cường Digital Camera
7586403814951503888 2 videos Papaoutai - Afro Soul
7416011436411472682 2 videos somebody else
saved 94 videos to tiktok_search_outdoor_cooking.csv
The CSV is sorted by views. Its first rows:
video_id,url,posted,author,views,likes,comments,shares,saves,engagement_pct,sound_id,sound,hashtags,caption
7651695143715654942,https://www.tiktok.com/@cookinnature0/video/7651695143715654942,2026-06-15,cookinnature0,34900000,2500000,12100,174100,268400,7.7,7651695168453708575,original sound - cookinnature0,tiktokfood asmr outdoorcooking,#tiktokfood #asmr #outdoorcooking
7455829921554910494,https://www.tiktok.com/@kevgalvv/video/7455829921554910494,2025-01-03,kevgalvv,15600000,2600000,6571,164200,198200,17.76,7295350011269106434,original sound - kevgalvv,foryou fyp foryoupage outdoorboys guinness irish theboys hellyeah cooking,Being a dude rocks🤘🏼 #foryou #fyp #foryoupage #outdoorboys #guinness #irish #theboys #hellyeah #cooking
…
What the results tell you
Most results are not new. TikTok ranks search by relevance and popularity, not date. The 94 videos were posted between April 2023 and October 2026; only 42 are from 2026 and 14 from the last 30 days. If you want what is happening now, the create_time filter is not optional.
One creator dominates the keyword. @tftk007 has 10 of the 94 videos, and 8 of the 14 recent ones. Their median engagement is 11.8%, against 2.9% for all 94 results. For outreach, competitor research or a creator shortlist, "who keeps showing up" is often the most useful line of the report. Look them up with the follower and engagement script next.
The sound is the trend. 15 of the 94 videos, from five different accounts, use the same sound, 7061837257761639194, and together they have 33 million views. That sound ID is the input to the sound endpoints: list every video that uses it with the sound videos guide, or paste it into the free TikTok sound tracker to see its video count and top videos without writing code.
Keyword search is wider than a hashtag. Only 53 of the 94 videos carry #outdoorcooking. The others matched through their caption or other tags, such as #campfirecooking or #bushcraft. To keep only one tag, filter on cha_list; note that tags keep their author's capitalisation, so compare in lower case.
Watch a keyword every day
To get only the videos you have not seen yet, keep their IDs in a file between runs. Save this next to the script as watch_keyword.py and run it once a day with cron:
import json
import os
from tiktok_search import search_videos, to_row
KEYWORDS = "outdoor cooking"
SEEN = "seen_outdoor_cooking.json"
seen = set(json.load(open(SEEN))) if os.path.exists(SEEN) else set()
videos, _ = search_videos(KEYWORDS, pages=2)
new = sorted((to_row(v) for v in videos if v["video_id"] not in seen),
key=lambda r: r["posted"], reverse=True)
for r in new:
print(f"NEW {r['posted']} {r['views']:>10,} views @{r['author']} {r['url']}")
with open(SEEN, "w") as f:
json.dump(sorted(seen | {v["video_id"] for v in videos}), f)
The first run reports everything; later runs report only unseen IDs. "Unseen" is not the same as "just posted": search rankings move during the day, and a search we ran that morning put different videos on page 1 than the one above. An older video that climbs into the top pages will show up as new to you. Its posted date tells you which case it is, which is why the list is sorted by it. Two pages a day costs 2 credits, about 60 a month per keyword.
Searching for accounts instead
When you want creators rather than videos, /user/search takes the same keywords and returns accounts with their counters. It returns a single page; raise count (up to 50) to get more. From the reference, a search for "cooking":
{
"success": true,
"data": {
"user_list": [
{
"user": { "id": "7117645427240911918", "uniqueId": "voy123123", "nickname": "Cooking", "verified": false, … },
"stats": { "followerCount": 2900000, "followingCount": 24, "heartCount": 53000000, "videoCount": 2953, … }
},
…
],
"cursor": 10,
"hasMore": true
},
"creditsUsed": 1
}
Video search tells you who is getting views on a topic right now; account search tells you who has the topic in their name or bio. They often disagree, and both are 1 credit.
Where to go from here
- Follow the trend you found: build a daily TikTok sound tracker for the top sound.
- Size up the creators: TikTok follower and like counts with Python, or the free engagement rate calculator.
- Read what viewers say on the top videos: export TikTok comments to CSV.
- The /feed/search reference lists every parameter, and the TikTok API page lists the other endpoints.
Common questions
Is there an API to search TikTok videos by keyword?
Yes. POST {"endpoint": "/feed/search", "params": {"keywords": "outdoor cooking", "region": "US"}} to https://scrapingbot.io/api/v1/tiktok with your key in the x-api-key header. You get public videos matching the keyword with views, likes, comments, shares, saves, author, sound and hashtags. Each page costs 1 credit and needs no TikTok login.
How do I get the next page of TikTok search results?
Send the previous page's cursor as offset and its searchId as search_id, with the same keywords. Both are required: an offset without a search ID is rejected with 400 and the message "search_id is required when offset is greater than zero." Keep going while hasMore is true.
Why did I get more videos than I asked for?
TikTok decides the page size. In our run, count: 10 returned 10, then 73, then 77 videos, and many of the later ones repeated earlier pages. Always de-duplicate on video_id; three pages gave us 94 unique videos from 160 results.
Are TikTok search results sorted by date?
No. They are ranked by relevance and popularity: one search for "outdoor cooking" returned videos posted between April 2023 and October 2026. Filter on create_time (a Unix timestamp) yourself when you only want recent videos.
Can I search a hashtag?
Search the words of the tag as the keyword, then filter on cha_list, which lists each video's hashtags. Keyword search matches more than tags: 41 of our 94 "outdoor cooking" results did not use #outdoorcooking at all.
How many credits does TikTok search use?
One credit per page, whatever the page size. Three pages is 3 credits, and failed calls are refunded. The 100 free credits on a new account cover 100 pages.