TikTok Hashtag Strategy: Beyond Volume Metrics in 2026

Published on May 29, 2026

Why hashtag volume is the wrong North Star

Most TikTok hashtag strategies still rank tags by one number: total view count. Pick the biggest hashtag in your niche, slap it on the post, hope the algorithm notices. It almost never works that way, and the data explains why.

A hashtag with 12 billion views is, by definition, saturated. Your post lands in a feed where the median video already has six figures of engagement and a creator with a million followers. Your relative reach inside that pool is rounding error. Meanwhile a hashtag with 80 million views and a steep weekly growth curve will surface your post to a feed where the algorithm is actively probing for new entrants.

What actually correlates with reach is the shape of the hashtag, not its size: how fast it is growing, how much engagement the average post inside it gets, and whether the creators your target audience already follows are using it. None of that is visible in the TikTok app. All of it is computable from the TikLiveAPI hashtag endpoints in a small Python script you can run weekly.

This post walks through the data model, the three metrics that beat raw volume, and an end-to-end scoring pipeline you can drop into a content calendar workflow.

The data: TikTok hashtag = challenge

Internally, TikTok models every hashtag as a "challenge" object. That is the terminology you will see across the API. The relevant endpoints are documented in the documentation index, but the three you need for this workflow are:

The challenge info response is a flat snake_case object. The hashtag name field is cha_name (not name), which is a common gotcha for first-time integrators:

{
  "id": "12345678",
  "cha_name": "smallbusinesscheck",
  "desc": "Show your small business",
  "user_count": 482301,
  "view_count": 1820000000,
  "is_pgcshow": false,
  "is_commerce": false,
  "is_challenge": true,
  "is_strong_music": false,
  "type": 1,
  "cover": "https://..."
}

Three fields matter strategically:

  • user_count - distinct creators who ever used the tag
  • view_count - cumulative views across all posts
  • is_commerce - whether TikTok flags it as a commercial/branded challenge (advertiser dominated)
  • is_challenge - whether it is a real challenge format vs a generic descriptor

The volume numbers (user_count, view_count) are cumulative since the hashtag was born. They tell you nothing about momentum. That is where the next three metrics come in.

Three quality metrics that beat raw volume

1. Velocity: post count growth over time

Velocity is the rate at which new posts are being attached to the hashtag. A hashtag whose user_count doubled in the last 14 days is in a discovery surge. A hashtag whose count has been flat for six months is dead even if the lifetime view number looks impressive.

Since the API does not expose historical user_count directly, you compute velocity by sampling recent posts via /challenge-posts/ and counting how many fall in the last N days using the create_time field (unix seconds, snake_case).

2. Engagement density: avg likes per post

Engagement density tells you whether the hashtag is a quality pool or a dumping ground. Compute it from a sample of recent posts:

density = sum(digg_count for v in sample) / len(sample)

A 50M-view hashtag where the median recent post has 8,000 likes is far more valuable than a 2B-view hashtag where the median recent post has 200 likes. The first is a real community; the second is a graveyard of low-effort uploads.

3. Audience overlap: do your target accounts use it?

The strongest predictor of whether a hashtag will work for you is whether creators in your target audience already use it successfully. If you sell B2B SaaS to designers and the top recent posters under a tag are all gaming streamers, the tag is irrelevant regardless of its volume.

Overlap is computed by taking the author.unique_id of every video in your /challenge-posts/ sample and intersecting it with a curated list of target creators or a set you previously scraped via /search-user/.

Step 1: Pull hashtag metadata

Start with a candidate list of hashtags. For each one, hit /challenge-info-name/ to resolve the challenge object. Auth is a single header: X-Api-Key: YOUR_API_KEY.

import os, requests

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

def get_hashtag(name):
    r = requests.get(
        f"{API}/challenge-info-name/",
        headers=HEADERS,
        params={"name": name},
        timeout=15,
    )
    r.raise_for_status()
    return r.json()

candidates = ["smallbusinesscheck", "founderlife", "saasmarketing"]
meta = {h: get_hashtag(h) for h in candidates}

for name, m in meta.items():
    print(name, m["cha_name"], m["user_count"], m["view_count"], m["is_commerce"])

Drop any candidate where is_commerce is true unless you are running a paid campaign - commerce challenges are advertiser auctions, not organic discovery surfaces.

Step 2: Sample posts and compute engagement density

For each surviving candidate, pull a sample of recent posts via /challenge-posts/. Use the id from the previous step as challenge_id. The response shape is the standard videos payload: top keys videos, cursor, hasMore (camelCase boolean). Each video item is flat snake_case with digg_count, comment_count, share_count, play_count, create_time, and an author object.

import time

def sample_posts(challenge_id, region=None, pages=3, per_page=30):
    out, cursor = [], "0"
    for _ in range(pages):
        params = {
            "challenge_id": challenge_id,
            "count": per_page,
            "cursor": cursor,
        }
        if region:
            params["region"] = region
        r = requests.get(
            f"{API}/challenge-posts/",
            headers=HEADERS,
            params=params,
            timeout=15,
        ).json()
        out.extend(r.get("videos", []))
        if not r.get("hasMore"):
            break
        cursor = r.get("cursor", "0")
    return out

def density_and_velocity(videos, window_days=14):
    if not videos:
        return 0.0, 0
    now = int(time.time())
    cutoff = now - window_days * 86400
    likes = [v.get("digg_count", 0) for v in videos]
    density = sum(likes) / len(likes)
    recent = sum(1 for v in videos if v.get("create_time", 0) >= cutoff)
    velocity = recent / len(videos)
    return density, velocity

Velocity here is expressed as the share of the sample that was posted in the last 14 days. A score of 0.6 means 60% of the sampled posts are recent - a healthy, growing tag. A score of 0.05 means the tag is mostly historical.

Step 3: Identify creator overlap

Take a set of "target creators" - either accounts your audience follows or competitors you track - and check what fraction of your hashtag sample comes from that set or its first-degree neighbors.

TARGET_CREATORS = {"acme_designs", "studio_north", "indiehackerlife"}

def overlap_score(videos, targets):
    authors = {v.get("author", {}).get("unique_id", "") for v in videos}
    authors.discard("")
    if not authors:
        return 0.0
    hits = authors & targets
    return len(hits) / len(authors)

You can grow the TARGET_CREATORS set programmatically by walking /userinfo-by-username/ for each seed account and then their public following list - useful if you want a dynamic target set rather than a hand-curated one.

Step 4: Build a hashtag-fit scoring model

Now combine the signals into a single 0-100 score. The weights below are a starting point - tune them to your goals (paid campaigns weight overlap higher; organic content weights velocity higher).

def normalize(values):
    if not values:
        return {}
    lo, hi = min(values.values()), max(values.values())
    if hi == lo:
        return {k: 0.5 for k in values}
    return {k: (v - lo) / (hi - lo) for k, v in values.items()}

def score_hashtags(candidates, targets, region=None):
    raw = {}
    for name in candidates:
        meta = get_hashtag(name)
        if meta.get("is_commerce"):
            continue
        videos = sample_posts(meta["id"], region=region)
        density, velocity = density_and_velocity(videos)
        overlap = overlap_score(videos, targets)
        ad_penalty = 0.5 if meta.get("is_commerce") else 0.0
        raw[name] = {
            "cha_name": meta["cha_name"],
            "user_count": meta["user_count"],
            "view_count": meta["view_count"],
            "density": density,
            "velocity": velocity,
            "overlap": overlap,
            "ad_penalty": ad_penalty,
        }

    n_density = normalize({k: v["density"] for k, v in raw.items()})
    n_velocity = normalize({k: v["velocity"] for k, v in raw.items()})
    n_overlap = normalize({k: v["overlap"] for k, v in raw.items()})

    for k, v in raw.items():
        v["score"] = round(
            100 * (
                0.35 * n_velocity[k]
                + 0.30 * n_density[k]
                + 0.30 * n_overlap[k]
                - 0.05 * v["ad_penalty"]
            ),
            2,
        )
    return sorted(raw.values(), key=lambda x: x["score"], reverse=True)

The output is a ranked list where the top entries are hashtags that are growing, dense with engagement, and used by creators close to your audience - regardless of whether their lifetime view count is 50 million or 5 billion.

Step 5: Use cases

Content calendar

Run the scoring pipeline weekly. Take the top 5 tags whose velocity score crossed a threshold in the last week and brief them into the content team. These are the tags where the discovery surface is actively expanding.

Paid campaign targeting

For TikTok Ads spark ad campaigns, you want overlap-heavy tags - your audience needs to already live there. Re-weight the scoring function with overlap at 0.5 and velocity at 0.2.

Competitor decoding

Pull a competitor's recent posts via /user-posts/, parse hashtags out of the title field with a regex, then run each through the scoring pipeline. The competitor's tag mix tells you their distribution strategy; the scores tell you which of their tags are actually working.

Anti-patterns to avoid

  • Mega hashtags as the primary tag. #fyp, #foryou, #viral - they have astronomical view counts and zero discovery value. The algorithm has not used them as a signal for serious ranking in years. Use them as filler at most.
  • Banned or limited hashtags. If /challenge-info-name/ returns an unusually low user_count for a tag you know is widely used in the app, the tag is likely shadow-limited. Skip it.
  • Branded vs discovery confusion. A hashtag with is_commerce: true is a branded challenge run by an advertiser. Posting under it for organic reach is almost never effective unless you are part of the campaign.
  • Confusing volume with momentum. A 2B-view hashtag that grew 0.1% last month is dying. A 30M-view hashtag that grew 40% last month is the opportunity.

Multi-language and regional considerations

TikTok is fundamentally a regional graph. A hashtag that ranks in the US has a different velocity profile in Brazil or Indonesia, and many tags exist in language-specific variants (#smallbusinesscheck vs #petitcommerce vs #negocioscaseros).

The /challenge-posts/ endpoint accepts a region parameter using ISO-3166 alpha-2 codes. The full code list comes from /region-list/, which returns a flat object where keys are uppercase country codes and values are country names:

regions = requests.get(
    f"{API}/region-list/", headers=HEADERS, timeout=15
).json()
# regions == {"AD": "Andorra", "AE": "United Arab Emirates", ...}

for code in ["US", "BR", "DE", "ID"]:
    videos = sample_posts(challenge_id, region=code)
    d, v = density_and_velocity(videos)
    print(code, regions[code], "density:", round(d), "velocity:", round(v, 2))

The pattern: score the same hashtag across the regions you care about, then localize content production to the regions where the score is strongest. Many growth teams skip this step and lose 6-12 months to the wrong-region-wrong-tag mismatch.

Operationalize: a weekly hashtag report

The full pipeline runs in under two minutes for ~50 candidate hashtags. Wrap the scoring function in a script that:

  1. Reads a CSV of candidate hashtags (one column: tag)
  2. Reads a CSV of target creator unique_ids
  3. Reads a CSV of target regions
  4. Calls score_hashtags for each region
  5. Writes one Markdown report per region to your shared drive
import csv, pathlib, datetime

def weekly_report(tag_csv, creator_csv, region_csv, out_dir):
    tags = [r["tag"] for r in csv.DictReader(open(tag_csv))]
    targets = {r["unique_id"] for r in csv.DictReader(open(creator_csv))}
    regions = [r["code"] for r in csv.DictReader(open(region_csv))]
    out = pathlib.Path(out_dir)
    out.mkdir(parents=True, exist_ok=True)
    today = datetime.date.today().isoformat()

    for code in regions:
        ranked = score_hashtags(tags, targets, region=code)
        lines = [f"# Hashtag report {code} {today}", ""]
        lines.append("| Tag | Score | Velocity | Density | Overlap |")
        lines.append("|---|---|---|---|---|")
        for r in ranked[:20]:
            lines.append(
                f"| #{r['cha_name']} | {r['score']} | "
                f"{r['velocity']:.2f} | {int(r['density'])} | "
                f"{r['overlap']:.2f} |"
            )
        (out / f"{code}-{today}.md").write_text("\n".join(lines))

if __name__ == "__main__":
    weekly_report(
        "tags.csv", "creators.csv", "regions.csv", "reports/"
    )

Total API spend: roughly 1 call to /challenge-info-name/ + 3 calls to /challenge-posts/ per hashtag per region. For 50 hashtags x 4 regions that is 800 credits per weekly run - trivial under any pay-as-you-go credit balance. See /pricing/ for the credit math; you can prototype the script against your free starter credits, or jump into the /playground/ to run individual calls and inspect the JSON before you write any code. If you hit edge cases on specific hashtags, the team at /contact/ can help.

FAQ

Why does the API call hashtags "challenges"?

It is TikTok's internal terminology. Every hashtag is modeled as a challenge object with the same schema regardless of whether it is a viral dance trend or a generic descriptor like #cooking. The hashtag name lives in the cha_name field, not name.

How big should my post sample be per hashtag?

60-90 recent posts (2-3 pages of /challenge-posts/ at count=30) is enough for stable engagement density and velocity numbers. Smaller samples are noisy; larger samples burn credits without changing the ranking much.

How often should I re-score?

Weekly for content calendar planning. Daily during a campaign launch. Hashtag velocity can shift inside 48-72 hours, so anything slower than weekly will miss real surges.

Can I do this for accounts instead of hashtags?

Yes - the same density and velocity logic works on a creator's recent /user-posts/ output. That gives you a "creator fit score" you can use to vet influencer partnerships before you negotiate.

What about banned or shadow-limited hashtags?

The API will still return the challenge object, but /challenge-posts/ will return very few or very stale videos. If the velocity score is near zero on a tag you know is widely used in the app, treat it as limited and remove it from your rotation. Track your live API health on the status page and explore more workflows on the blog.

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