TwoSec

Get all videos from a TikTok user with Python

List a public TikTok creator's videos and photo posts, newest first, with plays, likes, comments, shares, bookmarks, hashtags and sound. Then work out their average views and how often they post. About 30 posts per request.

Influencer vetting, competitor tracking and content research all start with the same list: what a creator posted and how each post did. The TikTok Video & Creator API returns it as JSON without a TikTok login.

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.

import csv
import os
import statistics
import time
from datetime import datetime

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()

Get the profile

GET /v1/tiktok/users/{username} returns the creator's profile with exact counts. One request.

profile = get("/v1/tiktok/users/zachking")

Real response, avatar link shortened

{
  "id": "68616495085350913",
  "username": "zachking",
  "nickname": "Zach King",
  "avatar": "https://p19-common-sign.tiktokcdn-us.com/…",
  "verified": true,
  "sec_uid": "MS4wLjABAAAANnzJ9pdfxtlf3m5Ku_M-rCXVitJC13w4GavqyUD3RE8h1cZ-7O9FT-uQ9rCJjU_E",
  "url": "https://www.tiktok.com/@zachking",
  "bio": "Bringing a little more wonder into the world, 15 seconds at a time",
  "bio_link": "https://youtu.be/ulVscDewRPI?si=-65KnrnwSiGHDeT2",
  "private": false,
  "language": "en",
  "created_at": "2016-02-29T07:29:22Z",
  "stats": { "followers": 86890382, "following": 182, "likes": 1301093987, "videos": 617, "friends": 119 }
}

Read one page of videos

curl --request GET \
  --url 'https://tiktok-video-creator-api.p.rapidapi.com/v1/tiktok/users/zachking/videos' \
  --header "X-RapidAPI-Key: $RAPIDAPI_KEY" \
  --header 'X-RapidAPI-Host: tiktok-video-creator-api.p.rapidapi.com'

Real response, shortened to one video, media links shortened

{
  "username": "zachking",
  "sec_uid": "MS4wLjABAAAANnzJ9pdfxtlf3m5Ku_M-rCXVitJC13w4GavqyUD3RE8h1cZ-7O9FT-uQ9rCJjU_E",
  "count": 31,
  "has_more": true,
  "next_cursor": "1771605525067",
  "videos": [
    {
      "id": "7655004069190192398",
      "url": "https://www.tiktok.com/@zachking/video/7655004069190192398",
      "description": "a message to all AI",
      "created_at": "2026-06-24T16:45:49Z",
      "duration": 27,
      "width": 720,
      "height": 1280,
      "is_photo_post": false,
      "is_pinned": true,
      "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": [],
      "stats": { "plays": 67200000, "likes": 13900000, "comments": 105300, "shares": 560500, "bookmarks": 1012929 },
      "author": {
        "id": "68616495085350913",
        "username": "zachking",
        "nickname": "Zach King",
        "avatar": "https://p19-common-sign.tiktokcdn-us.com/…",
        "verified": true,
        "sec_uid": "MS4wLjABAAAANnzJ9pdfxtlf3m5Ku_M-rCXVitJC13w4GavqyUD3RE8h1cZ-7O9FT-uQ9rCJjU_E",
        "url": "https://www.tiktok.com/@zachking",
        "stats": { "followers": 86900000, "following": 182, "likes": 1300000000, "videos": 617, "friends": 0 }
      },
      "music": {
        "id": "7655004159753718541",
        "title": "original sound",
        "author": "Zach King",
        "original": true,
        "duration": 28,
        "cover": "https://p16-common-sign.tiktokcdn-us.com/…",
        "play_url": "https://v16m.tiktokcdn-us.com/…"
      }
    }
  ]
}

The first page holds pinned posts first (is_pinned: true), then the newest posts. Photo posts have is_photo_post: true, their pictures in images and duration 0.

Get every video

Pass next_cursor back as cursor, and sec_uid from the first page to make later pages faster.

def user_videos(username, max_pages=30):
    videos, params = {}, {}
    for _ in range(max_pages):
        data = get(f"/v1/tiktok/users/{username}/videos", **params)
        for v in data.get("videos") or []:
            videos[v["id"]] = v
        if not data.get("has_more") or not data.get("next_cursor"):
            break
        params = {"cursor": data["next_cursor"], "sec_uid": data["sec_uid"]}
    return list(videos.values())


videos = user_videos("zachking")
print(len(videos), "of", profile["stats"]["videos"], "posts")

A creator with 617 posts takes about 21 requests at about 30 posts each.

Creator stats

Pinned posts are often old hits, so leave them out of averages:

recent = sorted((v for v in videos if not v["is_pinned"]),
                key=lambda v: v["created_at"], reverse=True)[:30]
plays = [v["stats"]["plays"] for v in recent]

if plays:
    first = datetime.fromisoformat(recent[-1]["created_at"].replace("Z", "+00:00"))
    last = datetime.fromisoformat(recent[0]["created_at"].replace("Z", "+00:00"))
    days = max((last - first).days, 1)
    print("median plays, last 30 posts:", int(statistics.median(plays)))
    print("posts per week:", round(len(recent) / days * 7, 1))
    print("median plays per follower:",
          round(statistics.median(plays) / max(profile["stats"]["followers"], 1), 3))

with open("videos.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.writer(f)
    writer.writerow(["id", "created_at", "plays", "likes", "comments", "shares",
                     "bookmarks", "pinned", "photo", "url"])
    for v in videos:
        s = v["stats"]
        writer.writerow([v["id"], v["created_at"], s["plays"], s["likes"], s["comments"],
                         s["shares"], s["bookmarks"], v["is_pinned"], v["is_photo_post"], v["url"]])

The median is steadier than the mean for creators with one viral outlier.

Things to know

Next steps

Plans

The profile is one request and each page of about 30 posts 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.