How to Track Trending TikTok Hashtags Programmatically

Published on May 29, 2026

Hashtags on TikTok are not just labels. They are the closest thing the platform has to a public trend signal, and tracking them programmatically lets brands, creators, and social listening teams move faster than competitors who rely on screenshots and gut feeling.

This tutorial walks through a complete Python workflow: look up a hashtag, pull its posts, compute a velocity score, search by keyword, save a watchlist, and detect rising trends. Every API call below uses real endpoints from TikLiveAPI, with response shapes verified against live data.

Why hashtag trend tracking matters

For brands, a rising hashtag is a free distribution window. Jumping on a trend in the first 48 hours can multiply organic reach by 10x compared to joining on day five, when the algorithm has already redistributed attention. For creators, it is a content calendar generator: instead of guessing what to post, you can let the data tell you which sounds, formats, and topics are accelerating right now.

For social listening teams, programmatic hashtag tracking turns TikTok from an opaque black box into a measurable channel. You can spot challenger brands hijacking your branded hashtag, detect when a meme is starting to associate with your category, or surface user-generated content for paid amplification before everyone else does.

The data model: hashtag equals challenge

Inside TikTok's own data layer, a hashtag is called a "challenge". Every hashtag has two identifiers you can use:

  • Name: the human-readable text without the "#" prefix (for example, fyp or booktok).
  • Challenge ID: a long numeric string TikTok assigns internally. This is what most endpoints actually want.

You will almost always start with a name (because that is what humans type) and resolve it to an ID before pulling posts. Once you have the ID, you can fetch posts, count engagement, and compute trend metrics. Two endpoints handle the lookup: /challenge-info-name/ when you have the name, and /challenge-info-id/ when you already have the numeric ID.

Step 1: Look up a hashtag by name

Start with the name-based lookup. The response is a flat object (no wrapper) containing id, cha_name, desc, user_count, view_count, and several boolean flags. Note the key is cha_name, not challenge_name, and the description key is desc.

import os
import requests

BASE_URL = "https://api.tikliveapi.com"
API_KEY = os.environ["TIKLIVE_API_KEY"]
HEADERS = {"X-Api-Key": API_KEY}


def lookup_hashtag(name: str) -> dict | None:
    """Resolve a hashtag name to its challenge metadata."""
    r = requests.get(
        f"{BASE_URL}/challenge-info-name/",
        headers=HEADERS,
        params={"name": name.lstrip("#")},
        timeout=15,
    )
    if r.status_code != 200:
        return None
    data = r.json()
    return {
        "challenge_id": data.get("id"),
        "name": data.get("cha_name"),
        "description": data.get("desc"),
        "post_count": data.get("user_count"),
        "view_count": data.get("view_count"),
        "is_commerce": data.get("is_commerce"),
    }


if __name__ == "__main__":
    info = lookup_hashtag("booktok")
    print(info)

The user_count field is the total number of unique posts using the hashtag, and view_count is the cumulative view count across all of them. These two numbers are your baseline. Save them with a timestamp so you can compare next week's snapshot against this one.

If the name resolves to nothing (some hashtags only respond to the ID lookup), you can fall back to /challenge-info-id/ once you have obtained the ID through search.

Step 2: Get top posts under a hashtag

With the challenge_id in hand, call /challenge-posts/ to retrieve videos. The response has a single top-level key videos, and each item is a snake_case object with engagement counters: play_count, digg_count (likes), comment_count, share_count, collect_count, plus create_time as a Unix timestamp and play as the no-watermark download URL.

def fetch_challenge_posts(challenge_id: str, count: int = 35) -> list[dict]:
    """Fetch up to 35 recent posts for a challenge_id."""
    r = requests.get(
        f"{BASE_URL}/challenge-posts/",
        headers=HEADERS,
        params={"challenge_id": challenge_id, "count": count},
        timeout=15,
    )
    r.raise_for_status()
    return r.json().get("videos", [])


def summarize_posts(posts: list[dict]) -> dict:
    if not posts:
        return {"posts": 0}
    return {
        "posts": len(posts),
        "total_views": sum(p.get("play_count", 0) for p in posts),
        "total_likes": sum(p.get("digg_count", 0) for p in posts),
        "total_comments": sum(p.get("comment_count", 0) for p in posts),
        "total_shares": sum(p.get("share_count", 0) for p in posts),
    }

Notice that count maxes out at 35 per request. To page deeper, pass the cursor value the API returns alongside the videos array, and add an optional region parameter if you want to bias results toward a specific country.

Step 3: Calculate a velocity score

Total post count tells you how big a hashtag is. Velocity tells you whether it is accelerating. The simplest useful metric is: posts created in the last 24 hours divided by the average daily post rate over the past 7 days. Anything above 1.5 is heating up; above 3.0 is a clear trend candidate.

Since create_time is a Unix timestamp, the math is straightforward. Pull the most recent posts you can (multiple pages if needed), bucket them by day, and compute the ratio.

from datetime import datetime, timezone, timedelta
from collections import Counter


def velocity_score(posts: list[dict]) -> dict:
    """Posts in last 24h vs 7-day daily average."""
    now = datetime.now(timezone.utc)
    cutoff_24h = now - timedelta(hours=24)
    cutoff_7d = now - timedelta(days=7)

    days = Counter()
    last_24h = 0

    for p in posts:
        ts = p.get("create_time")
        if not ts:
            continue
        dt = datetime.fromtimestamp(ts, tz=timezone.utc)
        if dt < cutoff_7d:
            continue
        days[dt.date()] += 1
        if dt >= cutoff_24h:
            last_24h += 1

    if not days:
        return {"velocity": 0.0, "last_24h": 0, "daily_avg_7d": 0.0}

    daily_avg = sum(days.values()) / 7
    velocity = last_24h / daily_avg if daily_avg else 0.0
    return {
        "velocity": round(velocity, 2),
        "last_24h": last_24h,
        "daily_avg_7d": round(daily_avg, 2),
    }

A practical refinement: weight the velocity score by engagement, not just post count. A hashtag where 100 new posts averaged 50,000 views is worth more than one where 500 new posts averaged 500 views. Multiply the velocity by the average play_count per recent post to get an engagement-weighted version.

Step 4: Search for hashtags by keyword

You will not always know the hashtag you want to track. To discover candidates around a topic, use /search-challenge/ with a keyword. The response key is challenge_list, and each item carries the same flat shape as the lookup endpoints: id, cha_name, user_count, view_count.

def search_hashtags(keyword: str, count: int = 30) -> list[dict]:
    r = requests.get(
        f"{BASE_URL}/search-challenge/",
        headers=HEADERS,
        params={"keyword": keyword, "count": count},
        timeout=15,
    )
    r.raise_for_status()
    items = r.json().get("challenge_list", [])
    return [
        {
            "challenge_id": c.get("id"),
            "name": c.get("cha_name"),
            "post_count": c.get("user_count", 0),
            "view_count": c.get("view_count", 0),
        }
        for c in items
        if c.get("id")
    ]

If you want to look at related video content rather than the hashtags themselves (useful when the hashtag landscape around a niche keyword is too sparse to be interesting), /search-video/ accepts a publish_time integer (1 = past 24 hours, 7 = week) and a sort_by code, which is a quick way to surface trending content even when no single hashtag dominates.

Step 5: Build a daily watchlist

Now wire the pieces together into a script that runs once a day, walks a list of hashtags, computes velocity, and appends a row to SQLite. A small local database is enough for most teams; you can graduate to BigQuery or Snowflake later.

import sqlite3
import time
from pathlib import Path

DB_PATH = Path("hashtag_watchlist.sqlite")

WATCHLIST = [
    "booktok",
    "cleantok",
    "moneytok",
    "skincareroutine",
    "smallbusinesscheck",
]


def init_db():
    con = sqlite3.connect(DB_PATH)
    con.execute("""
        CREATE TABLE IF NOT EXISTS hashtag_snapshot (
            captured_at INTEGER,
            name TEXT,
            challenge_id TEXT,
            post_count INTEGER,
            view_count INTEGER,
            velocity REAL,
            last_24h INTEGER,
            daily_avg_7d REAL,
            PRIMARY KEY (captured_at, challenge_id)
        )
    """)
    con.commit()
    return con


def run_watchlist():
    con = init_db()
    captured_at = int(time.time())
    for name in WATCHLIST:
        meta = lookup_hashtag(name)
        if not meta or not meta["challenge_id"]:
            continue
        posts = fetch_challenge_posts(meta["challenge_id"], count=35)
        v = velocity_score(posts)
        con.execute(
            "INSERT OR REPLACE INTO hashtag_snapshot VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
            (
                captured_at,
                meta["name"],
                meta["challenge_id"],
                meta["post_count"],
                meta["view_count"],
                v["velocity"],
                v["last_24h"],
                v["daily_avg_7d"],
            ),
        )
        time.sleep(0.5)
    con.commit()
    con.close()


if __name__ == "__main__":
    run_watchlist()

Schedule this with cron (0 6 * * * for 6 AM UTC daily) or a managed scheduler. Each request costs one credit on a pay-as-you-go plan, so a 50-hashtag watchlist running daily for a month is about 3,000 credits. The standard rate limit is 200 requests per minute, well above what you need here.

Step 6: Detect rising trends

Now you have a time series. The cleanest signal for "rising trend" is: today's view count is more than two standard deviations above the rolling 14-day mean. This is classic statistical bump detection, and it works well because TikTok view counts grow roughly log-linearly during a trend.

import statistics


def detect_bumps(con: sqlite3.Connection, window_days: int = 14) -> list[dict]:
    cutoff = int(time.time()) - window_days * 86400
    rows = con.execute(
        "SELECT name, captured_at, view_count "
        "FROM hashtag_snapshot WHERE captured_at >= ? "
        "ORDER BY name, captured_at",
        (cutoff,),
    ).fetchall()

    by_hashtag: dict[str, list[int]] = {}
    for name, _, views in rows:
        by_hashtag.setdefault(name, []).append(views)

    bumps = []
    for name, series in by_hashtag.items():
        if len(series) < 7:
            continue
        history = series[:-1]
        today = series[-1]
        mean = statistics.mean(history)
        sd = statistics.pstdev(history) or 1
        z = (today - mean) / sd
        if z >= 2:
            bumps.append({"name": name, "z_score": round(z, 2), "today": today})
    return sorted(bumps, key=lambda x: x["z_score"], reverse=True)

For a more sensitive detector, compute the daily delta in view_count rather than the absolute number, since cumulative views always rise. The delta of a trending hashtag will spike in a way that the absolute series cannot, because absolute views are dominated by historical accumulation.

How to use this data

Creator brief. Each Monday, export the top five hashtags by velocity that are also relevant to the creator's niche. Pair each hashtag with the three top-performing recent posts (highest play_count from /challenge-posts/) so the creator can see the format that is winning, not just the tag.

Ad targeting. Rising hashtags signal rising organic interest, which is a leading indicator for ad cost efficiency. Feed your detected trends into a creative brief for TikTok Spark Ads (boosting organic content) before competitor brands notice the same trend and bid the rate up.

Content calendar. Plot your watchlist as a heatmap with hashtags on the y-axis and days on the x-axis. Schedule posts when a hashtag's velocity crosses 2.0, and stop pushing content when it falls back below 1.0. This converts a vague "post on trends" instruction into a measurable trigger.

Try the endpoints live in the interactive playground before wiring up your daily script, and check the full documentation for parameters like region filters that can refine your watchlist by market.

FAQ

How fresh is the data the API returns?

Responses come straight from TikTok with no caching layer, at roughly 750ms average latency. Counts you see match what is publicly visible on the platform at request time, which makes velocity calculations reliable.

Can I track branded hashtags my brand owns?

Yes. Branded hashtags work the same as any other challenge. Look them up by name, pull posts, and use velocity tracking to measure campaign momentum. The is_commerce field tells you whether TikTok itself flags the hashtag as commercial.

What if /challenge-info-name/ returns nothing for a hashtag I know exists?

Some hashtags only resolve by ID. Use /search-challenge/ with the keyword first, take the id from the top result, and then call /challenge-info-id/ for the full metadata.

How many credits does a daily watchlist cost?

One credit per request. A 50-hashtag watchlist using two calls per hashtag (lookup plus posts) costs 100 credits per day, or about 3,000 per month. Credits never expire, so you can buy in bulk without time pressure.

Can I detect trends in a specific country?

Yes. The /challenge-posts/ endpoint accepts an optional region parameter, and /region-list/ returns the supported country codes. Run separate watchlists per market for region-aware velocity scores.

Build with the TikTok API

Ready to put what you read into code? Try our endpoints live or grab the full reference.

Open Playground Read Documentation