Fake followers and engagement fraud waste influencer marketing budgets. Detecting fakes on TikTok requires analyzing multiple signals: engagement-to-follower ratios, comment quality patterns, follower growth spikes, and content consistency. This tutorial builds an automated fraud detection pipeline using the Scavio TikTok API. It fetches profile data and recent videos, runs statistical tests for anomalies, and outputs a fraud risk score. Total cost is 2-3 credits ($0.01-0.015) per creator analyzed.
Prerequisites
- Python 3.9+ installed
- requests library installed
- A Scavio API key from scavio.dev
- TikTok usernames to analyze
Walkthrough
Step 1: Fetch profile and engagement data
Get the creator profile stats and recent video performance data. Both are needed to detect inconsistencies that signal fake engagement.
import requests, os
API_KEY = os.environ['SCAVIO_API_KEY']
TIKTOK_URL = 'https://api.scavio.dev/api/v1/tiktok'
def fetch_creator_data(username: str) -> dict:
# Profile
profile_resp = requests.post(f'{TIKTOK_URL}/user/info',
headers={'Authorization': f'Bearer {API_KEY}',
'Content-Type': 'application/json'},
json={'username': username})
profile_data = profile_resp.json().get('data', {})
# Recent videos
videos_resp = requests.post(f'{TIKTOK_URL}/user/posts',
headers={'Authorization': f'Bearer {API_KEY}',
'Content-Type': 'application/json'},
json={'username': username, 'count': 30, 'cursor': 0})
videos = videos_resp.json().get('data', {}).get('videos', [])
stats = profile_data.get('stats', {})
return {
'username': username,
'followers': stats.get('followerCount', 0),
'following': stats.get('followingCount', 0),
'total_likes': stats.get('heartCount', 0),
'video_count': stats.get('videoCount', 0),
'videos': videos
}Step 2: Check engagement ratio anomalies
Legitimate accounts have predictable engagement-to-follower ratios. Accounts with fake followers show abnormally low engagement relative to follower count.
def check_engagement_ratio(data: dict) -> dict:
followers = max(data['followers'], 1)
videos = data['videos']
if not videos:
return {'flag': 'NO_VIDEOS', 'risk': 50}
avg_likes = sum(v.get('stats', {}).get('diggCount', 0) for v in videos) / len(videos)
avg_plays = sum(v.get('stats', {}).get('playCount', 0) for v in videos) / len(videos)
likes_to_followers = (avg_likes / followers) * 100
plays_to_followers = (avg_plays / followers) * 100
# Normal ranges for TikTok:
# Likes/followers: 1-15% is typical, <0.5% suspicious, >20% suspicious (bought likes)
# Plays/followers: 10-200% is typical, <5% suspicious
engagement_risk = 0
flags = []
if likes_to_followers < 0.5:
engagement_risk += 30
flags.append(f'Very low like ratio: {likes_to_followers:.2f}%')
elif likes_to_followers > 25:
engagement_risk += 20
flags.append(f'Abnormally high like ratio: {likes_to_followers:.2f}%')
if plays_to_followers < 5:
engagement_risk += 25
flags.append(f'Very low play ratio: {plays_to_followers:.2f}%')
return {
'likes_to_followers': round(likes_to_followers, 2),
'plays_to_followers': round(plays_to_followers, 2),
'risk_score': engagement_risk,
'flags': flags
}Step 3: Analyze engagement consistency
Real accounts show natural variation in engagement. Fake engagement often looks too consistent (bot likes) or has extreme spikes (purchased engagement).
import statistics
def check_engagement_consistency(data: dict) -> dict:
videos = data['videos']
if len(videos) < 5:
return {'risk_score': 10, 'flags': ['Too few videos to analyze']}
like_counts = [v.get('stats', {}).get('diggCount', 0) for v in videos]
play_counts = [v.get('stats', {}).get('playCount', 0) for v in videos]
flags = []
risk = 0
# Check if engagement is suspiciously uniform
if like_counts and statistics.mean(like_counts) > 0:
cv = statistics.stdev(like_counts) / statistics.mean(like_counts) # coefficient of variation
if cv < 0.1: # Less than 10% variation = suspiciously uniform
risk += 25
flags.append(f'Suspiciously uniform likes (CV={cv:.3f})')
# Check for sudden engagement spikes
if play_counts:
median_plays = statistics.median(play_counts)
spikes = sum(1 for p in play_counts if p > median_plays * 10)
spike_ratio = spikes / len(play_counts)
if spike_ratio > 0.3: # More than 30% of videos have 10x spikes
risk += 20
flags.append(f'{spikes}/{len(play_counts)} videos have 10x play spikes')
# Check likes-to-comments ratio (bots rarely comment)
for v in videos:
s = v.get('stats', {})
likes = s.get('diggCount', 0)
comments = s.get('commentCount', 0)
if likes > 1000 and comments < likes * 0.005: # Less than 0.5% comment rate
risk += 5
flags.append(f'Very low comment ratio on video with {likes} likes')
break # Only flag once
return {'risk_score': min(risk, 50), 'flags': flags}Step 4: Check following/follower ratio
Accounts that follow massive numbers of accounts relative to their followers often participated in follow-for-follow schemes or used follow bots.
def check_follow_ratio(data: dict) -> dict:
followers = max(data['followers'], 1)
following = data['following']
ratio = following / followers
flags = []
risk = 0
if followers > 10000 and ratio > 1.0:
risk += 30
flags.append(f'Following > followers ({following:,} / {followers:,})')
elif followers > 10000 and ratio > 0.5:
risk += 15
flags.append(f'High follow ratio: {ratio:.2f}')
# Check if total likes seem inflated relative to video count
if data['video_count'] > 0:
likes_per_video = data['total_likes'] / data['video_count']
expected_likes = followers * 0.05 # 5% of followers per video is generous
if likes_per_video > expected_likes * 5:
risk += 15
flags.append(f'Inflated total likes: {likes_per_video:,.0f}/video vs {expected_likes:,.0f} expected')
return {'follow_ratio': round(ratio, 3), 'risk_score': risk, 'flags': flags}Step 5: Generate the full fraud report
Combine all detection signals into a comprehensive fraud risk assessment with an overall risk score and detailed breakdown.
def fraud_report(username: str) -> dict:
data = fetch_creator_data(username)
engagement = check_engagement_ratio(data)
consistency = check_engagement_consistency(data)
follow = check_follow_ratio(data)
total_risk = engagement['risk_score'] + consistency['risk_score'] + follow['risk_score']
all_flags = engagement.get('flags', []) + consistency.get('flags', []) + follow.get('flags', [])
verdict = 'LOW RISK' if total_risk < 20 else 'MEDIUM RISK' if total_risk < 50 else 'HIGH RISK'
report = {
'username': username,
'followers': data['followers'],
'total_risk_score': min(total_risk, 100),
'verdict': verdict,
'engagement_check': engagement,
'consistency_check': consistency,
'follow_check': follow,
'all_flags': all_flags,
'credits_used': 2
}
print(f'@{username}: {verdict} ({total_risk}/100)')
print(f' Followers: {data["followers"]:,}')
for flag in all_flags:
print(f' - {flag}')
return report
# report = fraud_report('suspicious_creator')Python Example
import os, requests, statistics
API_KEY = os.environ['SCAVIO_API_KEY']
TT = 'https://api.scavio.dev/api/v1/tiktok'
def tt(endpoint, body):
return requests.post(f'{TT}/{endpoint}',
headers={'Authorization': f'Bearer {API_KEY}', 'Content-Type': 'application/json'},
json=body).json()
def detect_fake(username):
profile = tt('user/info', {'username': username}).get('data', {})
stats = profile.get('stats', {})
followers = stats.get('followerCount', 1)
posts = tt('user/posts', {'username': username, 'count': 20, 'cursor': 0})
videos = posts.get('data', {}).get('videos', [])
avg_likes = sum(v.get('stats', {}).get('diggCount', 0) for v in videos) / max(len(videos), 1)
ratio = (avg_likes / followers) * 100
risk = 'HIGH' if ratio < 0.5 else 'MEDIUM' if ratio < 1.0 else 'LOW'
print(f'@{username}: {risk} RISK (like ratio: {ratio:.2f}%, {followers:,} followers)')
detect_fake('example_creator')JavaScript Example
const API_KEY = process.env.SCAVIO_API_KEY;
const TT = 'https://api.scavio.dev/api/v1/tiktok';
async function tt(endpoint, body) {
const r = await fetch(`${TT}/${endpoint}`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
return r.json();
}
async function detectFake(username) {
const profile = await tt('user/info', { username });
const followers = profile.data?.stats?.followerCount || 1;
const posts = await tt('user/posts', { username, count: 20, cursor: 0 });
const videos = posts.data?.videos || [];
const avgLikes = videos.reduce((s, v) => s + (v.stats?.diggCount || 0), 0) / Math.max(videos.length, 1);
const ratio = (avgLikes / followers) * 100;
console.log(`@${username}: ${ratio < 0.5 ? 'HIGH' : ratio < 1 ? 'MED' : 'LOW'} RISK (${ratio.toFixed(2)}%)`);
}
detectFake('example_creator').catch(console.error);Expected Output
@suspicious_creator: HIGH RISK (72/100)
Followers: 500,000
- Very low like ratio: 0.31%
- Suspiciously uniform likes (CV=0.082)
- Following > followers (520,000 / 500,000)
@legitimate_creator: LOW RISK (8/100)
Followers: 85,000
(no flags)
Credits used: 2 per creator ($0.01)