A TikTok UGC campaign tracker searches for videos using your campaign hashtag via the TikTok search API, collects creator handles and engagement stats, and stores daily snapshots to measure campaign reach over time.
Prerequisites
- Python 3.9+
- Scavio API key with TikTok access
- SQLite (stdlib)
Walkthrough
Step 1: Search TikTok videos by campaign hashtag
Use search/videos with your hashtag as the query to find all UGC posts.
import requests
API_KEY = "your-scavio-api-key"
CAMPAIGN_HASHTAG = "#YourCampaign2026"
def get_campaign_videos(hashtag: str, max_pages: int = 5) -> list:
tag = hashtag.lstrip("#")
all_videos = []
cursor = 0
for _ in range(max_pages):
r = requests.post(
"https://api.scavio.dev/api/v1/tiktok/search/videos",
json={"query": f"#{tag}", "cursor": cursor, "count": 30},
headers={"Authorization": f"Bearer {API_KEY}"},
timeout=20
)
r.raise_for_status()
data = r.json()
batch = data.get("videos", [])
if not batch:
break
all_videos.extend(batch)
if not data.get("has_more"):
break
cursor = data.get("cursor", 0)
return all_videosStep 2: Extract creator and engagement data
Parse each video for creator handle, view count, like count, and share count.
def extract_video_stats(videos: list) -> list:
records = []
for v in videos:
stats = v.get("stats", {})
records.append({
"video_id": v.get("id"),
"creator": v.get("author", {}).get("uniqueId"),
"description": v.get("desc", "")[:100],
"plays": stats.get("playCount", 0),
"likes": stats.get("diggCount", 0),
"comments": stats.get("commentCount", 0),
"shares": stats.get("shareCount", 0)
})
return recordsStep 3: Store in SQLite and compute totals
Save daily snapshots and sum campaign-wide engagement.
import sqlite3
from datetime import date
def init_db(path="ugc_tracker.db"):
conn = sqlite3.connect(path)
conn.execute("""
CREATE TABLE IF NOT EXISTS ugc_posts (
date TEXT, video_id TEXT, creator TEXT,
plays INTEGER, likes INTEGER, comments INTEGER, shares INTEGER,
PRIMARY KEY (date, video_id)
)
""")
conn.commit()
return conn
def save_and_summarize(conn, records: list) -> dict:
today = str(date.today())
for r in records:
conn.execute("INSERT OR REPLACE INTO ugc_posts VALUES (?,?,?,?,?,?,?)",
(today, r["video_id"], r["creator"], r["plays"], r["likes"], r["comments"], r["shares"]))
conn.commit()
totals = {"posts": len(records), "total_plays": sum(r["plays"] for r in records),
"total_likes": sum(r["likes"] for r in records),
"unique_creators": len(set(r["creator"] for r in records))}
return totalsPython Example
import requests
import sqlite3
from datetime import date
API_KEY = "your-scavio-api-key"
CAMPAIGN_HASHTAG = "YourCampaign2026"
def get_campaign_videos(hashtag, max_pages=5):
all_videos, cursor = [], 0
for _ in range(max_pages):
r = requests.post("https://api.scavio.dev/api/v1/tiktok/search/videos",
json={"query": f"#{hashtag}", "cursor": cursor, "count": 30},
headers={"Authorization": f"Bearer {API_KEY}"}, timeout=20)
r.raise_for_status()
data = r.json()
batch = data.get("videos", [])
if not batch: break
all_videos.extend(batch)
if not data.get("has_more"): break
cursor = data.get("cursor", 0)
return all_videos
def extract_stats(videos):
return [{"video_id": v.get("id"), "creator": v.get("author",{}).get("uniqueId"),
"plays": v.get("stats",{}).get("playCount",0),
"likes": v.get("stats",{}).get("diggCount",0),
"shares": v.get("stats",{}).get("shareCount",0)} for v in videos]
def init_db(path="ugc_tracker.db"):
conn = sqlite3.connect(path)
conn.execute("CREATE TABLE IF NOT EXISTS ugc (date TEXT, video_id TEXT, creator TEXT, plays INTEGER, likes INTEGER, shares INTEGER, PRIMARY KEY (date, video_id))")
conn.commit()
return conn
def run_tracker():
conn = init_db()
videos = get_campaign_videos(CAMPAIGN_HASHTAG)
records = extract_stats(videos)
today = str(date.today())
for r in records:
conn.execute("INSERT OR REPLACE INTO ugc VALUES (?,?,?,?,?,?)",
(today, r["video_id"], r["creator"], r["plays"], r["likes"], r["shares"]))
conn.commit()
total_plays = sum(r["plays"] for r in records)
unique_creators = len(set(r["creator"] for r in records))
top = sorted(records, key=lambda x: x["plays"], reverse=True)[:3]
print(f"Campaign #{CAMPAIGN_HASHTAG} — {today}")
print(f"Posts: {len(records)} | Creators: {unique_creators} | Total plays: {total_plays:,}")
print("\nTop posts:")
for r in top:
print(f" @{r['creator']}: {r['plays']:,} plays")
if __name__ == "__main__":
run_tracker()JavaScript Example
const API_KEY = 'your-scavio-api-key';
const HASHTAG = 'YourCampaign2026';
async function getCampaignVideos(hashtag, maxPages = 5) {
const videos = [];
let cursor = 0;
for (let i = 0; i < maxPages; i++) {
const res = await fetch('https://api.scavio.dev/api/v1/tiktok/search/videos', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${API_KEY}` },
body: JSON.stringify({ query: `#${hashtag}`, cursor, count: 30 })
});
const data = await res.json();
const batch = data.videos ?? [];
if (!batch.length) break;
videos.push(...batch);
if (!data.has_more) break;
cursor = data.cursor ?? 0;
}
return videos;
}
const videos = await getCampaignVideos(HASHTAG);
const totalPlays = videos.reduce((s, v) => s + (v.stats?.playCount ?? 0), 0);
const creators = new Set(videos.map(v => v.author?.uniqueId)).size;
console.log(`Posts: ${videos.length} | Creators: ${creators} | Total plays: ${totalPlays.toLocaleString()}`);Expected Output
Campaign #YourCampaign2026 - 2026-05-22
Posts: 147 | Creators: 89 | Total plays: 4,821,032
Top posts:
@topcreatora: 892,441 plays
@creatorb: 441,209 plays
@creatorc: 312,887 plays