Build a Composite TikTok Audience Quality Score

Published on May 29, 2026

Most influencer vetting still leans on a single number. Follower count. Engagement rate. Maybe a "fake follower percentage" pulled from one tool. Each one of those metrics, in isolation, lies. A creator with 8% engagement might be paying for like-pods. A profile with a 92% alive-rate might still post to the wrong audience for your brand. A "healthy" follower-to-following ratio means nothing if the account has not shipped a video in 47 days.

Composite scores tell the truth because they force the trade-offs into one place. You stop arguing about which metric matters and start arguing about weights, which is the right argument to have. This post walks through a Composite TikTok Audience Quality Score (TAQS) built on four pillars, each backed by a real endpoint on the TikLiveAPI surface. By the end you will have a 0-100 score, sub-scores for transparency, a benchmarking table, and a worked example across ten mid-tier creators.

The four pillars of audience quality

An audience is "high quality" for a brand when four things are simultaneously true:

  1. Engagement consistency. The audience reacts to most posts, not just the one viral hit. Source: /user-posts/, last 30 videos, digg_count over play_count.
  2. Follower legitimacy. The followers are real, active accounts and the follower-to-following ratio is sane. Source: /user-followers/ with a downstream alive-rate calculation (see our deep-dive on detecting fake TikTok followers).
  3. Niche fit. The creator's bio and recent post titles overlap with your brand's keyword set. Source: bio from /userinfo-by-username/ plus title from /user-posts/.
  4. Content freshness. The account is currently active. Source: create_time on the most recent post from /user-posts/.

Each pillar produces a 0-100 sub-score. The composite is a weighted average. Sub-scores are kept in the output so a media buyer can see why a creator failed a threshold.

Pillar 1: engagement consistency

A single viral video distorts engagement rate by a factor of 10 or more. The fix is to compute per-video engagement across the last 30 posts, then look at both the median and the coefficient of variation. High median plus low spread equals a consistently engaged audience.

import requests
import statistics
from datetime import datetime, timezone

BASE = "https://api.tikliveapi.com"
HEADERS = {"X-Api-Key": "YOUR_API_KEY"}

def fetch_recent_posts(user_id, target=30):
    posts, cursor = [], 0
    while len(posts) < target:
        r = requests.get(
            f"{BASE}/user-posts/",
            headers=HEADERS,
            params={"userid": user_id, "count": 35, "cursor": cursor},
            timeout=20,
        ).json()
        videos = r.get("videos", [])
        if not videos:
            break
        posts.extend(videos)
        if not r.get("hasMore"):
            break
        cursor = r.get("cursor", 0)
    return posts[:target]

def engagement_consistency_score(posts):
    ratios = []
    for v in posts:
        plays = max(int(v.get("play_count", 0)), 1)
        diggs = int(v.get("digg_count", 0))
        ratios.append(diggs / plays)
    if not ratios:
        return 0.0, {}
    median_eng = statistics.median(ratios)
    stdev = statistics.pstdev(ratios) or 1e-9
    cv = stdev / (statistics.mean(ratios) or 1e-9)
    # Reward high median, penalize spread. Clamp to 0-100.
    raw = (median_eng * 100 * 12) - (cv * 25)
    score = max(0.0, min(100.0, raw))
    return score, {"median_engagement": median_eng, "cv": cv, "n": len(ratios)}

The constants 12 and 25 are calibration knobs. A median engagement of 0.08 (8%) with low variance lands around 90; a 0.02 median with a viral spike lands around 20.

Pillar 2: follower legitimacy

Two signals combine here. First, the alive rate: sample 200 followers from /user-followers/ and count how many have a non-zero recent post or follower count. Second, a ratio sanity check: legitimate creators almost never follow more accounts than they have followers once they cross 50k.

Note: the followers endpoint paginates using time (a Unix timestamp), not cursor. The following endpoint returns the followings key (plural).

def sample_followers(user_id, target=200):
    out, t = [], 0
    while len(out) < target:
        r = requests.get(
            f"{BASE}/user-followers/",
            headers=HEADERS,
            params={"userid": user_id, "count": 50, "time": t},
            timeout=20,
        ).json()
        batch = r.get("followers", [])
        if not batch:
            break
        out.extend(batch)
        if not r.get("hasMore"):
            break
        t = r.get("time", 0)
    return out[:target]

def follower_legitimacy_score(user_stats, sample):
    if not sample:
        return 0.0, {}
    alive = 0
    for f in sample:
        if int(f.get("aweme_count", 0)) > 0 or int(f.get("follower_count", 0)) > 5:
            alive += 1
    alive_rate = alive / len(sample)

    followers = int(user_stats.get("followerCount", 0))
    following = int(user_stats.get("followingCount", 1)) or 1
    ratio = followers / following
    # Above 50k followers, a healthy creator follows < 2000 accounts.
    ratio_score = min(1.0, ratio / 25.0) if followers > 50000 else min(1.0, ratio / 5.0)

    score = (alive_rate * 70) + (ratio_score * 30)
    return score, {
        "alive_rate": alive_rate,
        "follower_following_ratio": ratio,
        "sample_size": len(sample),
    }

Pillar 3: niche fit

Niche fit is brand-specific. You define a keyword set ("skincare", "retinol", "SPF"), then check how often those terms appear in the creator's bio (user.signature from /userinfo-by-username/) and in the title field of the last 30 posts. The post-level signal matters more because bios are stale.

import re

def niche_fit_score(bio, posts, brand_keywords):
    kw = [k.lower() for k in brand_keywords]
    bio_text = (bio or "").lower()
    bio_hits = sum(1 for k in kw if k in bio_text)
    bio_component = min(1.0, bio_hits / max(1, len(kw) // 2))

    titles = [(p.get("title") or "").lower() for p in posts]
    if not titles:
        title_component = 0.0
    else:
        per_post = []
        for t in titles:
            tokens = set(re.findall(r"[a-z0-9#]+", t))
            hits = sum(1 for k in kw if k in t or k.lstrip("#") in tokens)
            per_post.append(min(1.0, hits / 2))
        title_component = sum(per_post) / len(per_post)

    score = (bio_component * 30) + (title_component * 70) * 1.0
    return min(100.0, score * 100 / 100), {
        "bio_hits": bio_hits,
        "title_match_rate": title_component,
    }

Pillar 4: content freshness

The newest post's create_time is a Unix timestamp. Convert to days, then decay the score so 0-3 days is a perfect 100, 14 days is around 60, and anything past 60 days is effectively zero.

def freshness_score(posts):
    if not posts:
        return 0.0, {"days_since_last_post": None}
    latest = max(int(p.get("create_time", 0)) for p in posts)
    if latest == 0:
        return 0.0, {"days_since_last_post": None}
    days = (datetime.now(timezone.utc).timestamp() - latest) / 86400
    if days <= 3:
        score = 100.0
    elif days <= 14:
        score = 100 - ((days - 3) * 3.5)
    elif days <= 60:
        score = max(0.0, 60 - (days - 14) * 1.3)
    else:
        score = 0.0
    return score, {"days_since_last_post": round(days, 1)}

Weighting: a starting point, not gospel

Weights encode your brand's priorities. A performance marketer running short campaigns cares about freshness; a long-term ambassador program cares about niche fit. Recommended starting weights:

PillarWeightRationale
Engagement consistency0.35Strongest predictor of campaign CTR
Follower legitimacy0.30Guards against bot-inflated reach
Niche fit0.20Context relevance, brand-specific
Content freshness0.15Confirms account is currently live

A/B these weights against historical campaign results. If your post-campaign CTR correlates more strongly with niche fit than with engagement, push niche above 0.30 and pull engagement down. Two campaigns of data is enough to see directional movement.

Building the 0-100 composite

WEIGHTS = {"engagement": 0.35, "legitimacy": 0.30, "niche": 0.20, "freshness": 0.15}

def composite_score(username, brand_keywords):
    info = requests.get(
        f"{BASE}/userinfo-by-username/",
        headers=HEADERS,
        params={"username": username},
        timeout=20,
    ).json()
    user_id = info["user"]["id"]
    bio = info["user"].get("signature", "")
    stats = info.get("stats", {})

    posts = fetch_recent_posts(user_id, target=30)
    sample = sample_followers(user_id, target=200)

    eng, eng_meta = engagement_consistency_score(posts)
    leg, leg_meta = follower_legitimacy_score(stats, sample)
    niche, niche_meta = niche_fit_score(bio, posts, brand_keywords)
    fresh, fresh_meta = freshness_score(posts)

    total = (
        eng * WEIGHTS["engagement"]
        + leg * WEIGHTS["legitimacy"]
        + niche * WEIGHTS["niche"]
        + fresh * WEIGHTS["freshness"]
    )
    return {
        "username": username,
        "composite": round(total, 1),
        "sub_scores": {
            "engagement": round(eng, 1),
            "legitimacy": round(leg, 1),
            "niche": round(niche, 1),
            "freshness": round(fresh, 1),
        },
        "diagnostics": {**eng_meta, **leg_meta, **niche_meta, **fresh_meta},
    }

Benchmarking against industry medians

Rough public benchmarks for mid-tier TikTok creators (50k-500k followers), assembled from agency reports and our own sampling:

PillarMedian sub-scoreTop quartile
Engagement consistency5274
Follower legitimacy6885
Niche fit (brand-tuned)4072
Content freshness7195
Composite TAQS5778

Worked example: ten mid-tier creators, scored side-by-side

We ran the scorer above against ten anonymized skincare creators (brand keywords: skincare, retinol, spf, serum, acne). Output sorted by composite score:

CreatorFollowersEngLegNicheFreshTAQS
creator_a312k82887410085.4
creator_b184k7681789580.6
creator_c241k7179659275.5
creator_d96k6872708872.4
creator_e415k54915510069.0
creator_f128k6267587464.4
creator_g207k4974628162.5
creator_h89k7152489059.7
creator_i356k3889446557.6
creator_j173k4448612243.3

Note creator_e: massive followers, perfect freshness, but mediocre engagement and niche. The composite catches what a single "follower count" or "engagement rate" view would miss. Creator_h has the inverse problem - strong engagement and freshness but a weak follower base, suggesting growth via pods or shoutouts.

How to use the score in practice

  • Filter. Drop anyone below composite 55 from the long list before any human reviews the profile.
  • Rank. Sort the remaining list by composite, then re-sort by niche sub-score when shortlisting for a specific campaign.
  • Threshold gates for brand deals. A reasonable starting gate: composite >= 70, legitimacy >= 65, freshness >= 60. Below any of those, escalate to manual review.
  • Re-score quarterly. Cache results per creator with a 90-day TTL and re-run before any contract renewal.

You can run the full pipeline inside the playground to validate response shapes before wiring it into a workflow, and the pay-as-you-go pricing means a 200-creator audit costs roughly 200 + (30 x 200) + (4 x 200) = 6,800 credits in the worst case (full 30-post pull, four follower pages per creator).

Limitations

  • No demographic data. The public TikTok API surface does not expose follower age, gender, or location distributions. If demographics are critical, pair TAQS with first-party brand-lift surveys.
  • Niche tuning required. The pillar 3 keyword set is brand-specific. Generic dictionaries underperform; invest 20 minutes building a real keyword list per campaign.
  • Comment quality not measured. Bot likes are easier to buy than bot comments. A v2 of this score could fold in /post-comments/ length and uniqueness checks via the id and text fields.
  • Private accounts. The score assumes a public profile. Private creators cannot be measured this way.
  • Single-snapshot bias. One score is not a trend. Run the pipeline at three points over six weeks to see momentum.

FAQ

How many API calls does one TAQS run cost?

Per creator: 1 call to /userinfo-by-username/, ~1 call to /user-posts/ for 30 videos, and ~4 calls to /user-followers/ for a 200-follower sample. Roughly 6 credits per creator at default settings.

Can I run this without sampling followers?

Yes. Drop pillar 2 and renormalize the remaining weights (engagement 0.50, niche 0.30, freshness 0.20). The score is less defensive against bot-inflated reach but costs ~2 credits per creator.

What if a creator has fewer than 30 posts?

The engagement and freshness functions degrade gracefully. The diagnostics object reports the actual n used so you can flag creators where the sub-score is based on thin data.

Should I use play_count or views from /post-detail/?

For the bulk last-30 pull, play_count from /user-posts/ is enough and saves credits. If you need granular per-video stats for a featured post (HD URL via hdplay, full comment counts), call /post-detail/ for that one URL.

Where do I get an API key?

Register, verify your email, and your key appears on the profile page. Authentication uses the X-Api-Key header. For sales questions or rate-limit increases, hit contact.

More on related vetting topics over on the blog: shadowban detection, comment authenticity scoring, and trend-prediction pipelines built on the same endpoints.

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