Overview
This pipeline runs a real hook audit on any TikTok handle. It resolves the username to a sec_uid, pulls the last 20 posts, and computes each video's play_count against the creator's own median, so you see which posts beat their baseline and which sank below it. It then reads the opening caption line on every post and fetches top comments on the single best performer, so you can connect a specific hook to the reach it earned. One honest caveat up front: the public API does not expose per-second retention, so you cannot draw a true 3-second drop-off curve. What you get instead are solid reach proxies, play_count versus median plus comment and share ratios, which is exactly what most free hook audits are actually eyeballing anyway.
Trigger
On demand per @handle, or weekly for handles you track.
Schedule
Weekly, or on demand per handle
Workflow Steps
Resolve the handle to a sec_uid
POST /api/v1/tiktok/profile with the username. Read data.user.sec_uid, follower_count, and video_count. The sec_uid is the stable id every later call needs.
Pull the last 20 posts
POST /api/v1/tiktok/user/posts with sec_user_id and count 20. Each post returns play_count, digg_count, comment_count, share_count, plus the description that holds the opening hook line.
Compute the creator's median play_count
Take the median of play_count across the pulled posts. Median, not mean, so one viral spike does not drag the baseline up and mislabel normal posts as flops.
Flag over and under-performers
Mark each post over or under its median. Over-performers are your winning hooks, under-performers are the ones to rewrite. Sort by play_count to find the single best and worst.
Read comments on the best post
POST /api/v1/tiktok/video/comments for the top post's video_id with count 30. Top comments by digg_count tell you what the winning hook actually triggered in viewers.
Output the hook report
Print each post's opening caption line next to its play_count and over/under flag, then the median and the winner's top comments. That is the audit, reach proxies and the hooks that earned them.
Python Implementation
import requests
import statistics
API_KEY = "your_scavio_api_key"
BASE = "https://api.scavio.dev"
HEADERS = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
def post(path, body):
r = requests.post(f"{BASE}{path}", json=body, headers=HEADERS, timeout=60)
r.raise_for_status()
return r.json()
def hook_line(description):
text = (description or "").strip().replace("\n", " ")
return text[:80] if text else "(no caption)"
def audit(username):
profile = post("/api/v1/tiktok/profile", {"username": username})
user = profile["data"]["user"]
sec_uid = user["sec_uid"]
print(f"@{username} followers={user.get('follower_count')} videos={user.get('video_count')}")
posts = post("/api/v1/tiktok/user/posts", {"sec_user_id": sec_uid, "count": 20})
items = posts.get("posts", [])
if not items:
print("no posts found")
return
plays = [p["statistics"]["play_count"] for p in items]
median = statistics.median(plays)
print(f"median play_count over {len(items)} posts = {median:.0f}\n")
ranked = sorted(items, key=lambda p: p["statistics"]["play_count"], reverse=True)
for p in ranked:
plays_n = p["statistics"]["play_count"]
flag = "OVER " if plays_n >= median else "under"
print(f"[{flag}] {plays_n:>9} {hook_line(p.get('description'))}")
best = ranked[0]
print(f"\nbest post: {best['statistics']['play_count']} plays")
print(f"hook: {hook_line(best.get('description'))}")
comments = post("/api/v1/tiktok/video/comments", {"video_id": best["video_id"], "count": 30})
top = sorted(comments.get("comments", []), key=lambda c: c.get("digg_count", 0), reverse=True)[:5]
print("top comments on the winning hook:")
for c in top:
print(f" {c.get('digg_count', 0):>5} likes {c.get('text', '')[:80]}")
if __name__ == "__main__":
audit("somecreator")
JavaScript Implementation
const API_KEY = "your_scavio_api_key";
const BASE = "https://api.scavio.dev";
const HEADERS = { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" };
async function post(path, body) {
const res = await fetch(`${BASE}${path}`, { method: "POST", headers: HEADERS, body: JSON.stringify(body) });
if (!res.ok) throw new Error(`${path} -> ${res.status}`);
return res.json();
}
function median(nums) {
const s = [...nums].sort((a, b) => a - b);
const m = Math.floor(s.length / 2);
return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2;
}
function hookLine(description) {
const text = (description || "").trim().replace(/\n/g, " ");
return text ? text.slice(0, 80) : "(no caption)";
}
async function audit(username) {
const profile = await post("/api/v1/tiktok/profile", { username });
const user = profile.data.user;
const secUid = user.sec_uid;
console.log(`@${username} followers=${user.follower_count} videos=${user.video_count}`);
const posts = await post("/api/v1/tiktok/user/posts", { sec_user_id: secUid, count: 20 });
const items = posts.posts || [];
if (items.length === 0) {
console.log("no posts found");
return;
}
const med = median(items.map((p) => p.statistics.play_count));
console.log(`median play_count over ${items.length} posts = ${med}\n`);
const ranked = [...items].sort((a, b) => b.statistics.play_count - a.statistics.play_count);
for (const p of ranked) {
const plays = p.statistics.play_count;
const flag = plays >= med ? "OVER " : "under";
console.log(`[${flag}] ${String(plays).padStart(9)} ${hookLine(p.description)}`);
}
const best = ranked[0];
console.log(`\nbest post: ${best.statistics.play_count} plays`);
console.log(`hook: ${hookLine(best.description)}`);
const comments = await post("/api/v1/tiktok/video/comments", { video_id: best.video_id, count: 30 });
const top = (comments.comments || []).sort((a, b) => (b.digg_count || 0) - (a.digg_count || 0)).slice(0, 5);
console.log("top comments on the winning hook:");
for (const c of top) {
console.log(` ${String(c.digg_count || 0).padStart(5)} likes ${(c.text || "").slice(0, 80)}`);
}
}
audit("somecreator").catch(console.error);