TwoSec

Search TikTok videos by keyword and export them to CSV with Python

Run a TikTok search for a few keywords, merge the results, keep the videos from the last 30 days and save them to a CSV file. Then rank them by engagement and list the creators behind them. One request returns up to 20 videos.

Keyword search is where most TikTok research starts: what gets posted about a product, a trend in a niche, which creators own a topic. TikTok's official APIs don't offer search for commercial use; the Research API is for academic and not-for-profit research only. The TikTok Video & Creator API returns TikTok's public search results as JSON, and every video already carries its creator and sound, so one search doubles as a creator finder.

Setup

You need Python 3, requests (pip install requests) and a key from the RapidAPI listing; the free plan's 100 requests are enough to try it. Put the key in an environment variable called RAPIDAPI_KEY. The example below (2 keywords, up to 3 pages each) makes at most 6 requests.

import csv
import os
import time
from datetime import datetime, timedelta, timezone

import requests

BASE = "https://tiktok-video-creator-api.p.rapidapi.com"
HEADERS = {
    "X-RapidAPI-Key": os.environ["RAPIDAPI_KEY"],
    "X-RapidAPI-Host": "tiktok-video-creator-api.p.rapidapi.com",
}


def get(path, **params):
    """GET a path and return the JSON. Retries rate limits and upstream errors."""
    for attempt in range(4):
        response = requests.get(BASE + path, headers=HEADERS, params=params, timeout=30)
        if response.status_code in (429, 502, 503) and attempt < 3:
            time.sleep(2 ** attempt)
            continue
        response.raise_for_status()
        return response.json()

Search one page

GET /v1/tiktok/search/videos takes q (1–100 characters; hashtags like #skincare work too) and returns up to 20 videos.

curl --request GET \
  --url 'https://tiktok-video-creator-api.p.rapidapi.com/v1/tiktok/search/videos?q=street%20food' \
  --header "X-RapidAPI-Key: $RAPIDAPI_KEY" \
  --header 'X-RapidAPI-Host: tiktok-video-creator-api.p.rapidapi.com'

Real response for q=street food from 2026-09-24, one of 20 videos shown, media links shortened

{
  "query": "street food",
  "count": 20,
  "has_more": true,
  "next_cursor": "MjA6MjAyNjA5MjQyMTAxMjI2NTlEMjFEMzlGNTQ4MTJGMTdDMA",
  "videos": [
    {
      "id": "7688378448947055885",
      "url": "https://www.tiktok.com/@kimnlizasmr/video/7688378448947055885",
      "description": "Eating only Korean street foods for a full day when this happened… #food #eating #mukbang #streetfood #korea ",
      "created_at": "2026-09-22T15:15:18Z",
      "duration": 62,
      "width": 720,
      "height": 1280,
      "is_photo_post": false,
      "is_pinned": false,
      "is_ad": false,
      "cover": "https://p16-common-sign.tiktokcdn-us.com/…",
      "dynamic_cover": "https://p16-common-sign.tiktokcdn-us.com/…",
      "play_url": "https://v16-webapp-prime.us.tiktok.com/…",
      "images": [],
      "hashtags": ["food", "eating", "mukbang", "streetfood", "korea"],
      "stats": { "plays": 685200, "likes": 53800, "comments": 211, "shares": 1257, "bookmarks": 2766 },
      "author": {
        "id": "6699037984631473157",
        "username": "kimnlizasmr",
        "nickname": "Kimlizasmr",
        "avatar": "https://p16-common-sign.tiktokcdn-us.com/…",
        "verified": false,
        "sec_uid": "MS4wLjABAAAAR_i0OQn_UuZc6oCZ2ChfIYkBVlQdxLhdE4lCXC2nxPvN1LCPP9Ombt-DmWAS39NJ",
        "url": "https://www.tiktok.com/@kimnlizasmr",
        "stats": { "followers": 7200000, "following": 22, "likes": 263900000, "videos": 1901, "friends": 0 }
      },
      "music": {
        "id": "7675014190469335838",
        "title": "original sound",
        "author": "prettylittlepryncess",
        "original": true,
        "duration": 12,
        "cover": "https://p16-common-sign.tiktokcdn-us.com/…",
        "play_url": "https://v16-webapp-prime.us.tiktok.com/…"
      }
    }
  ]
}

The creator (author, with follower counts) and the sound (music) come with every video, so you don't need extra calls for them.

More keywords and pages

Pass next_cursor back unchanged as cursor for the next page, and stop when has_more is false. One video can match two keywords, so key the results by video id.

def search(query, max_pages=3):
    videos, cursor = [], None
    for _ in range(max_pages):
        params = {"q": query}
        if cursor:
            params["cursor"] = cursor
        data = get("/v1/tiktok/search/videos", **params)
        videos.extend(data.get("videos") or [])
        cursor = data.get("next_cursor")
        if not data.get("has_more") or not cursor:
            break
    return videos


KEYWORDS = ["street food", "korean street food"]

found = {}
for keyword in KEYWORDS:
    for v in search(keyword):
        found.setdefault(v["id"], {**v, "keyword": keyword})

videos = list(found.values())
print(len(videos), "unique videos")

Keep the recent ones

Results come in TikTok's relevance order, not by date, and include older hits. Filter on created_at (UTC):

cutoff = datetime.now(timezone.utc) - timedelta(days=30)
recent = [v for v in videos
          if datetime.fromisoformat(v["created_at"].replace("Z", "+00:00")) >= cutoff]
print(len(recent), "from the last 30 days")

Save a CSV

FIELDS = ["id", "created_at", "keyword", "plays", "likes", "comments", "shares",
          "bookmarks", "duration", "username", "followers", "hashtags", "url", "description"]

with open("tiktok_search.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.DictWriter(f, fieldnames=FIELDS)
    writer.writeheader()
    for v in recent:
        author = v["author"]
        writer.writerow({
            "id": v["id"],
            "created_at": v["created_at"],
            "keyword": v["keyword"],
            **{k: v["stats"][k] for k in ("plays", "likes", "comments", "shares", "bookmarks")},
            "duration": v["duration"],
            "username": author["username"],
            "followers": (author.get("stats") or {}).get("followers", ""),
            "hashtags": " ".join(v["hashtags"]),
            "url": v["url"],
            "description": v["description"].strip(),
        })

The row for the video above

7688378448947055885,2026-09-22T15:15:18Z,street food,685200,53800,211,1257,2766,62,kimnlizasmr,7200000,food eating mukbang streetfood korea,https://www.tiktok.com/@kimnlizasmr/video/7688378448947055885,Eating only Korean street foods for a full day when this happened… #food #eating #mukbang #streetfood #korea

Rank videos and creators

Engagement rate here is likes, comments, shares and bookmarks divided by plays. It shows which videos hold attention, whatever the creator's size. The creator list sums plays per account across your results.

def engagement(v):
    s = v["stats"]
    return (s["likes"] + s["comments"] + s["shares"] + s["bookmarks"]) / max(s["plays"], 1)


for v in sorted(recent, key=engagement, reverse=True)[:10]:
    print(f'{engagement(v):.1%}  {v["stats"]["plays"]:>11,} plays  @{v["author"]["username"]}  {v["url"]}')

creators = {}
for v in recent:
    a = v["author"]
    c = creators.setdefault(a["username"], {
        "followers": (a.get("stats") or {}).get("followers", 0), "videos": 0, "plays": 0})
    c["videos"] += 1
    c["plays"] += v["stats"]["plays"]

for name, c in sorted(creators.items(), key=lambda kv: kv[1]["plays"], reverse=True)[:10]:
    print(f'@{name}: {c["followers"]:,} followers, {c["plays"]:,} plays in {c["videos"]} result(s)')

The lines the video above gets

8.5%      685,200 plays  @kimnlizasmr  https://www.tiktok.com/@kimnlizasmr/video/7688378448947055885
@kimnlizasmr: 7,200,000 followers, 685,200 plays in 1 result(s)

To dig into a creator you found, call GET /v1/tiktok/users/{username} for the exact profile counts and GET /v1/tiktok/users/{username}/videos for their posts (see the user videos guide). To search for accounts by keyword directly, use GET /v1/tiktok/search/users?q= (up to 10 per page).

Things to know

Next steps

Plans

Every search page of up to 20 videos is one request. A free plan covers testing. Plans, monthly quotas and rate limits are listed on the RapidAPI listing. For more volume or a custom plan, use Contact provider on the listing.