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.
An audience is "high quality" for a brand when four things are simultaneously true:
digg_count over play_count./userinfo-by-username/ plus title from /user-posts/.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.
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. This pillar extends the approach from our earlier guide to calculating a real engagement score with an explicit penalty for spread.
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),
}
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,
}
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)}
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:
| Pillar | Weight | Rationale |
|---|---|---|
| Engagement consistency | 0.35 | Strongest predictor of campaign CTR |
| Follower legitimacy | 0.30 | Guards against bot-inflated reach |
| Niche fit | 0.20 | Context relevance, brand-specific |
| Content freshness | 0.15 | Confirms 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.
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},
}
Rough public benchmarks for mid-tier TikTok creators (50k-500k followers), assembled from agency reports and our own sampling:
| Pillar | Median sub-score | Top quartile |
|---|---|---|
| Engagement consistency | 52 | 74 |
| Follower legitimacy | 68 | 85 |
| Niche fit (brand-tuned) | 40 | 72 |
| Content freshness | 71 | 95 |
| Composite TAQS | 57 | 78 |
We ran the scorer above against ten anonymized skincare creators (brand keywords: skincare, retinol, spf, serum, acne). Output sorted by composite score:
| Creator | Followers | Eng | Leg | Niche | Fresh | TAQS |
|---|---|---|---|---|---|---|
| creator_a | 312k | 82 | 88 | 74 | 100 | 85.4 |
| creator_b | 184k | 76 | 81 | 78 | 95 | 80.6 |
| creator_c | 241k | 71 | 79 | 65 | 92 | 75.5 |
| creator_d | 96k | 68 | 72 | 70 | 88 | 72.4 |
| creator_e | 415k | 54 | 91 | 55 | 100 | 69.0 |
| creator_f | 128k | 62 | 67 | 58 | 74 | 64.4 |
| creator_g | 207k | 49 | 74 | 62 | 81 | 62.5 |
| creator_h | 89k | 71 | 52 | 48 | 90 | 59.7 |
| creator_i | 356k | 38 | 89 | 44 | 65 | 57.6 |
| creator_j | 173k | 44 | 48 | 61 | 22 | 43.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. If your long list skews toward smaller accounts, the same scorer works on the 10K-100K tier covered in our micro-influencer discovery guide.
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).
/post-comments/ length and uniqueness checks via the id and text fields. Our guide to fetching TikTok comments at scale covers the pagination mechanics you would need. Shares and saves are another candidate signal - we break down what shares and saves actually predict in a separate post.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.
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.
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.
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.
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.
Ready to put what you read into code? Try our endpoints live or grab the full reference.