TikTok Engagement Rate Math: Beyond Likes Over Followers

Published on May 29, 2026

Ask ten growth analysts how they compute TikTok engagement rate (ER) and you will get ten different formulas - and at least three of them will be wrong for the question being asked. The single most common version, (likes / followers) * 100, was inherited from Instagram circa 2015 and has not described TikTok reality since the For You Page became the default distribution layer. On TikTok, follower count is a lagging vanity signal; views ("play_count") are the closest thing to a delivery denominator the public API exposes; and engagement itself has at least four distinct verbs - digg, comment, share, collect - each with its own intent gradient.

This piece is for analytics engineers and strategists who already pull data from a TikTok scraping layer and need a defensible math layer on top. We will walk through the seven engagement rate formulas in active use today, map each one to a concrete use case (paid attribution, organic audit, influencer rate cards, competitor benchmarking), derive every one of them from raw TikLiveAPI response fields, and finish with a Python module you can drop into a notebook. If you have not pulled a dataset yet, the playground renders sample payloads for every endpoint discussed below.

Why likes-over-followers lies on TikTok

Three structural facts break the legacy formula:

1. Follower count is not the delivery denominator. A creator with 12k followers can land a 4M-view video; a creator with 2M followers can post to 30k views. The denominator that actually gated reach was the algorithm, not the audience list. Dividing engagements by followers measures audience size relative to engagement size, which is a different - and usually less useful - question than "how engaging is this content?"

2. Views are public. Unlike Instagram (where view counts on feed posts are private), TikTok exposes play_count on every public video through the posts endpoints. The industry standard denominator for video-native ER - views - is actually available, and there is no excuse to fall back to followers.

3. The four engagement verbs are not interchangeable. A comment is roughly 30x more effortful than a digg (like). A share signals off-platform intent. A collect (save) signals revisit intent. Bucketing them all into one numerator destroys the signal a content audit needs.

The seven engagement rate formulas in use today

Below, every formula is named, defined, and tagged with the TikLiveAPI field it consumes. The /post-detail/ endpoint returns a flat snake-case object, the /user-posts/ endpoint wraps per-video records in videos[], and /userinfo-by-username/ returns nested user{} and stats{} objects with camelCase counters (followerCount, heartCount, videoCount).

1. Naive Account ER (followers-based)

ER_naive = (digg_count + comment_count + share_count) / followerCount, averaged across the last N videos. This is what influencer marketplaces still print on rate cards because brand managers recognize it. Use it only when forced for cross-platform comparisons and label it clearly.

2. Post ER by Views (the TikTok standard)

ER_post = (digg_count + comment_count + share_count) / play_count. The right default for organic content audits. It answers: "of everyone the algorithm delivered this video to, what fraction reacted?" Industry benchmarks land roughly at 5-9% for healthy accounts, 9-18% for breakout videos, and below 3% for suppressed or off-niche content.

3. Extended Post ER (includes collects)

ER_post_ext = (digg_count + comment_count + share_count + collect_count) / play_count. collect_count (saves) is one of TikTok's strongest ranking signals because it correlates with rewatch and intent to return. Adding it captures the value tutorials, recipes, and reference content generate that pure-entertainment ER misses.

4. Reach ER (account-wide views denominator)

ER_reach = sum(engagements over N videos) / sum(play_count over N videos). Aggregating numerator and denominator separately, then dividing, avoids the small-denominator distortion that plagues per-post averages. Use this when comparing a creator's last 30 days against another's.

5. View ER (raw view efficiency vs. follower base)

ER_view = avg(play_count over last N videos) / followerCount. Not strictly an engagement rate - it is a reach-to-followers ratio - but it sits in the same toolbox. A value above 1.0 means the average video reaches more accounts than the creator has followers (algorithmic lift); below 0.3 means content is being suppressed or only served to the existing audience.

6. Weighted Post ER (intent-weighted)

ER_weighted = (1 * digg + 4 * comment + 6 * share + 3 * collect) / play_count. Weights reflect rough effort/intent gradients - tune them per niche. This is the formula for content audits where you want the dashboard to surface a "save my recipe video" winner above a "drive-by like" winner with identical raw ER.

7. Comment ER (depth-only)

ER_comment = comment_count / play_count. The cleanest single proxy for narrative engagement. Useful when ranking videos for community management workload, identifying content that sparked debate, or detecting astroturfed engagement (commentless videos with high digg rates are suspicious).

The right formula per use case

Paid attribution and influencer rate cards. Use Reach ER (#4) over the last 30 days, computed as summed engagements over summed views. Rate-card negotiations should surface View ER (#5) so the brand sees algorithmic reach independent of follower count. The legacy Naive Account ER (#1) belongs only in the appendix.

Organic content audit. Use Post ER by Views (#2) for the per-video ranking and Weighted Post ER (#6) to surface "high-intent" winners. Always include Comment ER (#7) as a side column to spot suppressed-comment videos.

Competitor benchmarking. Use Reach ER (#4) on a fixed N (typically 30 videos) and pair with View ER (#5). Comparing per-post averages across competitors is statistically noisy; the aggregated form is robust.

Algorithm health check on your own account. Use View ER (#5) tracked over time. A sustained drop while Post ER (#2) stays flat is the textbook shadowban signature - the content still resonates with whoever sees it, but the algorithm has stopped delivering it.

Pulling the fields from TikLiveAPI

Every formula above derives from at most six fields. Three live on the user object, four live on each video object:

  • stats.followerCount from /userinfo-by-username/ or /userinfo-by-id/
  • stats.heartCount and stats.videoCount from the same endpoint (used for sanity checks)
  • play_count, digg_count, comment_count, share_count, collect_count on every video returned by /user-posts/ (wrapped in videos[]) or /post-detail/ (flat)

The flat-versus-wrapped distinction matters. /post-detail/ returns the video fields directly at the top level (and also exposes hdplay, wmplay, and play download URLs); /user-posts/ returns {videos: [...], cursor, hasMore} with each video carrying the same snake-case counters. Both shapes feed the same math.

Handling shadowbans and suppressed videos

A single suppressed video can wreck per-post-averaged ER. The classic signature is a video whose play_count is one or two orders of magnitude below the account's median - say 1,200 views on an account that normally lands 80k. Including it raw inflates Post ER (#2) for that video to absurd levels (a 500-view, 80-like video reads as 16% ER) and drags the account-wide average down on View ER (#5).

Two defensible cleanups:

  1. Outlier flagging. Compute the median play_count across the last N videos. Flag any video whose play_count is less than 10% of the median as "suppressed." Report it separately; do not include it in headline ER.
  2. Trim-and-aggregate. Drop the top and bottom 10% of play_count values before computing Reach ER (#4). This Winsorized version is the version to put on dashboards.

Weighted 30-day vs. lifetime ER

Lifetime ER is almost always misleading because creator output, niche, and algorithm behavior all drift. The defensible windows are 30 days, 90 days, and "last 30 videos." A recency-weighted ER over 30 days, where each video's contribution is multiplied by exp(-age_days / 14), gives you a metric that reacts within a week to content-strategy changes while still being smooth enough for weekly review meetings.

Python implementation

The module below takes a username, pulls the user object and the last 30 posts, computes all seven formulas, applies suppression handling, and returns a dict. It depends only on requests and the standard library.

import math
import time
import requests
from statistics import median

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

# Intent weights for ER #6 - tune per niche
W_DIGG, W_COMMENT, W_SHARE, W_COLLECT = 1, 4, 6, 3
RECENCY_TAU_DAYS = 14
SUPPRESSED_RATIO = 0.10  # flag videos at <10% of median play_count


def _get(path, params):
    r = requests.get(f"{API_BASE}{path}", headers=HEADERS,
                     params=params, timeout=20)
    r.raise_for_status()
    return r.json()


def fetch_user(username):
    data = _get("/userinfo-by-username/", {"username": username})
    return data["user"], data["stats"]


def fetch_posts(userid, count=30):
    videos, cursor = [], 0
    while len(videos) < count:
        page = _get("/user-posts/", {
            "userid": userid, "count": 30, "cursor": cursor
        })
        videos.extend(page.get("videos", []))
        if not page.get("hasMore"):
            break
        cursor = page.get("cursor", 0)
    return videos[:count]


def _engage(v):
    return v["digg_count"] + v["comment_count"] + v["share_count"]


def _engage_ext(v):
    return _engage(v) + v["collect_count"]


def _weighted(v):
    return (W_DIGG * v["digg_count"]
            + W_COMMENT * v["comment_count"]
            + W_SHARE * v["share_count"]
            + W_COLLECT * v["collect_count"])


def flag_suppressed(videos):
    if not videos:
        return [], []
    med = median(v["play_count"] for v in videos)
    threshold = med * SUPPRESSED_RATIO
    healthy = [v for v in videos if v["play_count"] >= threshold]
    suppressed = [v for v in videos if v["play_count"] < threshold]
    return healthy, suppressed


def trim_outliers(videos, pct=0.10):
    if len(videos) < 5:
        return videos
    sv = sorted(videos, key=lambda v: v["play_count"])
    k = int(len(sv) * pct)
    return sv[k:len(sv) - k] if k else sv


def compute_er(username):
    user, stats = fetch_user(username)
    followers = max(stats["followerCount"], 1)
    videos = fetch_posts(user["id"], count=30)
    healthy, suppressed = flag_suppressed(videos)

    total_eng = sum(_engage(v) for v in healthy)
    total_eng_ext = sum(_engage_ext(v) for v in healthy)
    total_w = sum(_weighted(v) for v in healthy)
    total_plays = sum(v["play_count"] for v in healthy) or 1
    n = len(healthy) or 1

    # Recency-weighted Reach ER over 30d
    now = time.time()
    rw_num, rw_den = 0.0, 0.0
    for v in healthy:
        age_days = (now - v["create_time"]) / 86400.0
        w = math.exp(-age_days / RECENCY_TAU_DAYS)
        rw_num += w * _engage(v)
        rw_den += w * v["play_count"]

    # Trim-and-aggregate dashboard headline
    trimmed = trim_outliers(healthy)
    t_eng = sum(_engage(v) for v in trimmed)
    t_plays = sum(v["play_count"] for v in trimmed) or 1

    return {
        "username": user["uniqueId"],
        "followers": stats["followerCount"],
        "videos_analyzed": n,
        "videos_suppressed": len(suppressed),
        "er_naive_account": total_eng / followers / n,
        "er_post_avg":      total_eng / total_plays,
        "er_post_ext_avg":  total_eng_ext / total_plays,
        "er_reach":         total_eng / total_plays,
        "er_view":          (total_plays / n) / followers,
        "er_weighted_avg":  total_w / total_plays,
        "er_comment_avg":   sum(v["comment_count"] for v in healthy)
                            / total_plays,
        "er_reach_30d_rw":  rw_num / max(rw_den, 1),
        "er_reach_trimmed": t_eng / t_plays,
    }

Three notes. Follower count uses the floor of 1 to avoid division-by-zero on brand-new accounts. create_time is a Unix timestamp on TikLiveAPI video objects, which keeps the recency-weighting one-liner cheap. er_reach and er_post_avg are mathematically identical here (both are summed-eng / summed-plays); they are surfaced under separate names because dashboards and rate cards label them differently, and consistency at the column-header layer saves arguments later.

Interpreting seasonal swings

ER will swing 30-50% across a calendar year on most accounts without anything being wrong. Friday-Sunday tends to lift Post ER (#2) because dwell time goes up; Monday-Tuesday drags it. November-December lifts Comment ER (#7) for lifestyle and shopping niches; January drops it. The defensible move is to chart ER as a 7-day rolling average and compare year-over-year deltas, not week-over-week. If your week-over-week chart is your headline, you are reporting noise.

Two anti-patterns to retire: "ER dropped 12% this week" alerts that fire on Monday-only data, and influencer rate cards that average per-post ER across a creator's lifetime - the latter buys you whatever the creator's worst-performing era was, weighted equally with this month.

FAQ

Which ER should I put on a rate card I send to a brand?

Reach ER (#4) over the last 30 videos, plus View ER (#5) as a secondary line. Brands that ask for "engagement rate" usually mean #1 by reflex; explain in one line that you are giving them the views-based number because TikTok views are public and a more honest denominator than followers.

Why are my Post ER numbers higher than the 1-3% quoted for Instagram?

Because the denominator changed. Instagram benchmarks divide by followers; TikTok benchmarks divide by views, and views are smaller than the follower base on most posts. Healthy TikTok Post ER lands in 5-9%; treat anything below 3% as suspect.

Do private accounts skew the math?

You cannot fetch posts from private accounts through any public-data endpoint, so they never enter the dataset. The bias to watch is opposite: creators who go private after a viral run quietly disappear from your benchmark cohort.

How do I detect view-bot activity?

Compute Comment ER (#7) and digg-to-view ratio per video. Real virality has correlated lift across all four verbs. View-bot signatures show massive play_count with flat comment_count and share_count. Flag any video where Comment ER drops more than 5x below the account median while view count spikes 3x above.

Where do I get the music_id or challenge_id for cross-account audits?

Both are exposed on every video object returned by /user-posts/. From there, /music-posts/ and /challenge-posts/ let you pivot from a single creator's hit into the cohort of every account that used the same sound or hashtag - the right move when you are trying to figure out whether a creator's lift came from their content or a rising trend.

How often should I rerun this?

For organic content audits, weekly is plenty. For paid-attribution decisions, rerun the day before negotiation - influencer metrics can drift 10-20% within a week if the creator landed a hit or a flop. Profile usage charts show how much the cadence costs; one full audit (user info + 30 posts) is 2 credits.

Ready to pull your first batch? Spin up the playground to sample the payloads, scan pricing (1 credit = 1 request, no subscription), and reach out via contact if you need a rate-limit bump above 200 rpm for a backfill. Worked examples and field-level deep dives land regularly 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