TwoSec

Track a TikTok creator's new videos and growth with Python

A small script that checks a list of TikTok creators once a day and tells you what changed: how many followers they gained or lost, which videos they posted, and which posts picked up views. Each creator costs two requests per run.

Brands watch creators before and during a campaign; agencies watch competitors' creators; talent managers watch their own roster. They all need the same daily numbers, and TikTok's official APIs don't provide them for accounts you don't own. The TikTok Video & Creator API reads the public profile and posts without a TikTok login, and this guide turns two of its endpoints into a growth log.

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. Ten creators checked daily use about 600 requests a month.

import json
import os
import time
from datetime import date
from pathlib import Path

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

Read the profile

GET /v1/tiktok/users/{username} returns the public profile. Its follower, like and post counts are exact, so it's the source for growth numbers.

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

Real response from 2026-09-24, 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 }
}

A private account returns 403; an unknown, banned or removed one returns 404.

Read the newest posts

GET /v1/tiktok/users/{username}/videos returns about 30 posts per page, newest first, with pinned posts at the top of the first page. Pass the sec_uid from the profile; it saves a lookup, so the call is faster.

Real response from 2026-09-24, one of 31 posts shown, some fields and media links left out

{
  "username": "zachking",
  "sec_uid": "MS4wLjABAAAANnzJ9pdfxtlf3m5Ku_M-rCXVitJC13w4GavqyUD3RE8h1cZ-7O9FT-uQ9rCJjU_E",
  "count": 31,
  "has_more": true,
  "next_cursor": "1771605525067",
  "videos": [
    {
      "id": "7686898345519942925",
      "url": "https://www.tiktok.com/@zachking/video/7686898345519942925",
      "description": "when you give your friend the aux cord… ",
      "created_at": "2026-09-18T15:31:42Z",
      "duration": 6,
      "is_photo_post": false,
      "is_pinned": false,
      "cover": "https://p19-common-sign.tiktokcdn-us.com/…",
      "hashtags": [],
      "stats": { "plays": 15100000, "likes": 2600000, "comments": 5901, "shares": 157200, "bookmarks": 161956 },
      "author": { "id": "68616495085350913", "username": "zachking", "…": "…" },
      "music": { "id": "7686898373269474062", "title": "original sound", "author": "Zach King", "…": "…" }
    }
  ]
}

The full video object also has width, height, is_ad, dynamic_cover, play_url and images (for photo posts). The tracker only needs the first page; the user videos guide shows how to page through a whole account.

Take a snapshot

One snapshot per creator: the profile counts plus the first page of posts, keyed by video ID.

def snapshot(username):
    profile = get(f"/v1/tiktok/users/{username}")
    page = get(f"/v1/tiktok/users/{username}/videos", sec_uid=profile["sec_uid"])
    return {
        "followers": profile["stats"]["followers"],
        "likes": profile["stats"]["likes"],
        "posts": profile["stats"]["videos"],
        "videos": {
            v["id"]: {
                "created_at": v["created_at"],
                "plays": v["stats"]["plays"],
                "likes": v["stats"]["likes"],
                "comments": v["stats"]["comments"],
                "pinned": v["is_pinned"],
                "url": v["url"],
            }
            for v in page.get("videos") or []
        },
    }

Compare with the last run

The script loads the latest snapshot from an earlier day, prints the differences and saves today's snapshot. A second run on the same day compares with the earlier day, not with itself.

Answers are cached for up to 24 hours, counted from when the previous request fetched them. A job that runs at the same time every day arrives just under 24 hours later, so it often gets the previous run's cached answer. The script spots this: when every number matches the last run exactly, it keeps the older snapshot and prints a note instead of a zero change. The next run then shows the change since that older snapshot, so a day's change can show up a day late, but none is lost. Each report says which date it's measured from.

KEYS = ["followers", "likes", "posts", "videos"]


def same_answer(old, new):
    """True when every number matches the last run: a cached answer."""
    return all(old[k] == new[k] for k in KEYS)


def compare(old, new):
    print(f'  since {old["as_of"]}:')
    print(f'  followers {new["followers"]:,} ({new["followers"] - old["followers"]:+,})')
    fresh = [v for vid, v in new["videos"].items()
             if vid not in old["videos"] and not v["pinned"]]
    for v in sorted(fresh, key=lambda v: v["created_at"]):
        print(f'  new post {v["created_at"][:10]}  {v["plays"]:,} plays  {v["url"]}')
    for vid, v in new["videos"].items():
        if vid in old["videos"]:
            gained = v["plays"] - old["videos"][vid]["plays"]
            if gained >= 100_000:
                print(f'  +{gained:,} plays  {v["url"]}')


CREATORS = ["zachking"]  # the accounts you want to track
SNAPSHOTS = Path("snapshots")
SNAPSHOTS.mkdir(exist_ok=True)
today = date.today().isoformat()

earlier = [p for p in sorted(SNAPSHOTS.glob("*.json")) if p.stem < today]
old = json.loads(earlier[-1].read_text()) if earlier else {}

new = {}
for username in CREATORS:
    s = snapshot(username)
    s["as_of"] = today
    print(f'@{username}: {s["followers"]:,} followers, {s["posts"]} posts, '
          f'{len(s["videos"])} on the first page')
    prev = old.get(username)
    if prev and same_answer(prev, s):
        print(f'  same numbers as the {prev["as_of"]} run (cached answer); '
              'the change shows in the next run')
        s = prev  # keep the date these numbers are really from
    elif prev:
        compare(prev, s)
    new[username] = s

(SNAPSHOTS / f"{today}.json").write_text(json.dumps(new, indent=1))

First run, built from the two responses above

@zachking: 86,890,382 followers, 617 posts, 31 on the first page

From the second day on, each creator also gets lines in this format

  since <date of the snapshot compared with>:
  followers <count> (<change>)
  new post <date>  <plays> plays  <video url>
  +<gained> plays  <video url>

Run it every day

Save the script as track.py and run it once a day with cron:

0 9 * * * cd /path/to/tracker && python3 track.py >> track.log

The snapshots are plain JSON files, one per day, so you can load them into a spreadsheet or a database later and chart each creator's growth.

Ideas to extend it

Things to know

Next steps

Plans

Each creator costs two requests per run: the profile and one page of posts. 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.