Music supervisors, A&R analysts, label data teams, and rights-management platforms all share one problem: TikTok is now the dominant discovery engine for music, but its data is locked behind an opaque app. A track can go from 200 plays to two million in 72 hours, a sync licensing opportunity can appear and disappear in a weekend, and a regional breakout can quietly dictate next quarter's tour routing - all without anyone at a label noticing until the chart shows up.
This cookbook gives you 12 production-ready recipes for extracting music intelligence from TikTok using the TikLiveAPI. Each recipe is a copy-paste Python snippet built around three endpoints: /music-info/ for sound metadata, /music-posts/ for the videos using a sound, and /search-video/ for keyword and lyric discovery. Authentication is the same everywhere - send your key in the X-Api-Key header. Grab one from the register page (100 free credits on email verification) and try requests live in the playground before wiring anything up.
One credit equals one request. No subscriptions, no expiry, no negotiation. The pricing page has the full breakdown, and the documentation covers all 37 endpoints. The two you will live in for music work are music endpoints and posts endpoints.
Every recipe below assumes this minimal client. Drop it in a file called tla.py and import it.
import os, time, requests
BASE = "https://api.tikliveapi.com"
KEY = os.environ["TLA_KEY"]
HEADERS = {"X-Api-Key": KEY}
def get(path, **params):
r = requests.get(f"{BASE}{path}", headers=HEADERS, params=params, timeout=30)
r.raise_for_status()
return r.json()
That is the entire SDK. No retries, no caching, no abstraction - you can add those once you know what you actually need.
You almost never start with a raw music_id. You start with a TikTok video URL someone slacked you. Resolve it first via /post-detail/, then pivot to the music object.
from tla import get
def resolve_music(video_url):
post = get("/post-detail/", url=video_url)
music = post.get("music_info") or post.get("music") or {}
music_id = post.get("music_info", {}).get("id")
return music_id, music
music_id, meta = resolve_music("https://www.tiktok.com/@user/video/7300000000000000000")
print(music_id)
Cost: 1 credit per video resolved. Cache the mapping - URLs are stable.
Once you have a music_id, /music-info/ returns the canonical record: title, author (the credited artist or the username for originals), cover image, duration, and the original-sound flag.
def music_meta(music_id):
data = get("/music-info/", music_id=music_id)
return {
"id": music_id,
"title": data.get("title"),
"author": data.get("author"),
"duration": data.get("duration"),
"is_original": bool(data.get("original")),
"cover": data.get("cover"),
}
print(music_meta("7012345678901234567"))
This is your normalization layer. Always hit /music-info/ once per new sound and write it to your warehouse - downstream joins all key off music_id.
The growth story lives in /music-posts/. The first page gives you the most recent uses - perfect for "is this still moving?" checks.
def music_posts_page(music_id, count=30, cursor=0):
return get("/music-posts/", music_id=music_id, count=count, cursor=cursor)
page = music_posts_page("7012345678901234567", count=30)
for v in page.get("videos", []) or page.get("videos", []):
print(v.get("aweme_id"), v.get("play_count"), v.get("digg_count"))
Field names in post lists are snake_case (aweme_id, play_count, digg_count). Memorize that - the user endpoints mix camelCase and it trips everyone up once.
To compute velocity or share of voice you need more than the first page. /music-posts/ paginates via cursor + hasMore. Keep a sleep in there - you are not in a race.
def all_music_posts(music_id, max_pages=20, page_size=30):
cursor, out = 0, []
for _ in range(max_pages):
page = get("/music-posts/", music_id=music_id, count=page_size, cursor=cursor)
items = page.get("videos") or page.get("videos") or []
out.extend(items)
if not page.get("hasMore"):
break
cursor = page.get("cursor", cursor + page_size)
time.sleep(0.25)
return out
posts = all_music_posts("7012345678901234567", max_pages=10)
print(len(posts), "posts collected")
Cost: max_pages credits. Cap it. A track with 800k uses will happily drain your balance if you let it.
Velocity is the gradient that matters. Bucket the posts by week using create_time (Unix seconds) and you instantly see whether a sound is accelerating, plateauing, or dying.
from collections import Counter
from datetime import datetime, timezone
def weekly_velocity(music_id, pages=10):
posts = all_music_posts(music_id, max_pages=pages)
weeks = Counter()
for p in posts:
ts = p.get("create_time") or 0
if not ts: continue
wk = datetime.fromtimestamp(ts, tz=timezone.utc).strftime("%G-W%V")
weeks[wk] += 1
return dict(sorted(weeks.items()))
print(weekly_velocity("7012345678901234567"))
Run this nightly across your watchlist. A jump from 40 posts/week to 400 posts/week is the signal that gets a track into Monday's pitch meeting.
Sounds do not go viral linearly - they bend. Compare the most recent 7 days against the prior 7. A 3x ratio with a meaningful floor (say, >= 100 posts in the recent window) is the classic breakout signature.
def rising_score(music_id):
posts = all_music_posts(music_id, max_pages=15)
now = int(time.time())
recent = sum(1 for p in posts if now - (p.get("create_time") or 0) < 7*86400)
prior = sum(1 for p in posts if 7*86400 <= now - (p.get("create_time") or 0) < 14*86400)
if prior == 0:
return recent, prior, float("inf") if recent else 0
return recent, prior, round(recent / prior, 2)
print(rising_score("7012345678901234567"))
Wrap this in a loop over your watchlist and flag anything with a ratio above 3 and recent count above 100. That is your daily "what is breaking" report.
Knowing a track is hot is not enough - you need to know where. Each post item carries author region data; the modal region is your best cheap proxy until you spend credits on dedicated region endpoints.
def top_region(music_id):
posts = all_music_posts(music_id, max_pages=10)
regions = Counter()
for p in posts:
region = p.get("region") or p.get("region")
if region:
regions[region] += 1
return regions.most_common(5)
print(top_region("7012345678901234567"))
If a US-signed artist's track is 70% Indonesian, that is a tour-routing conversation. If it is 90% Brazilian and the artist has no PT-BR promo, that is a marketing brief.
For royalty work the difference between an original upload and a labelled cover is everything. The original flag on /music-info/ is your first filter; the author field tells you who TikTok credits.
def attribution(music_id):
meta = get("/music-info/", music_id=music_id)
return {
"music_id": music_id,
"title": meta.get("title"),
"credited_author": meta.get("author"),
"is_original_upload": bool(meta.get("original")),
"needs_manual_review": bool(meta.get("original")) and not meta.get("album"),
}
print(attribution("7012345678901234567"))
The needs_manual_review flag is the one your rights team will care about - an "original" upload with no album metadata is exactly the kind of orphaned IP that ends up in disputes.
You manage 40 tracks. Which one is actually generating TikTok activity this week? Run the velocity recipe over your catalog and rank.
def catalog_share_of_voice(music_ids):
rows = []
for mid in music_ids:
recent, prior, ratio = rising_score(mid)
rows.append({"music_id": mid, "recent7d": recent, "prior7d": prior, "ratio": ratio})
total = sum(r["recent7d"] for r in rows) or 1
for r in rows:
r["share"] = round(r["recent7d"] / total * 100, 1)
return sorted(rows, key=lambda r: -r["share"])
catalog = ["7012345678901234567", "7012345678901234568", "7012345678901234569"]
for row in catalog_share_of_voice(catalog):
print(row)
Cost: roughly 15 credits per track. Forty tracks = 600 credits weekly. Cheap insurance against missing your own breakout.
Brand sync teams want videos using a sound that already have brand momentum. Combine /search-video/ with a sound filter check - find videos mentioning a brand keyword, then keep only those using a target sound.
def sync_leads(keyword, target_music_id, pages=3):
leads, cursor = [], 0
for _ in range(pages):
page = get("/search-video/", keyword=keyword, count=20, cursor=cursor)
items = page.get("videos") or page.get("videos") or []
for v in items:
mid = (v.get("music_info") or {}).get("id") or v.get("music_id")
if str(mid) == str(target_music_id):
leads.append({
"aweme_id": v.get("aweme_id"),
"plays": v.get("play_count"),
"author": (v.get("author") or {}).get("unique_id"),
})
if not page.get("hasMore"): break
cursor = page.get("cursor", cursor + 20)
time.sleep(0.25)
return leads
print(sync_leads("nike", "7012345678901234567"))
Output is a ranked list of creators already pairing the brand and the sound - that is your outbound sheet for the licensing conversation.
Insight compounds when you snapshot daily. Persist the watchlist metrics to a single JSONL file and you have a longitudinal dataset within a month.
import json
from datetime import date
def daily_snapshot(music_ids, path="snapshots.jsonl"):
today = date.today().isoformat()
with open(path, "a") as f:
for mid in music_ids:
meta = music_meta(mid)
recent, prior, ratio = rising_score(mid)
f.write(json.dumps({
"date": today, "music_id": mid, "title": meta["title"],
"author": meta["author"], "recent7d": recent,
"prior7d": prior, "ratio": ratio,
}) + "\n")
daily_snapshot(["7012345678901234567", "7012345678901234568"])
Schedule via cron at 04:00 local. Within four weeks you can backfit weekly seasonality and stop chasing weekend noise as if it were a trend.
Creators who use sound A often use sound B. That co-occurrence graph is how you find adjacent breakout candidates - tracks that share an audience but have not popped yet.
from collections import defaultdict
def cooccurrence(seed_music_id, depth_pages=5):
posts = all_music_posts(seed_music_id, max_pages=depth_pages)
authors = {(p.get("author") or {}).get("unique_id") for p in posts}
authors.discard(None)
graph = defaultdict(int)
for handle in list(authors)[:25]:
page = get("/search-video/", keyword=handle, count=10)
for v in page.get("videos", []) or page.get("videos", []):
mid = (v.get("music_info") or {}).get("id") or v.get("music_id")
if mid and str(mid) != str(seed_music_id):
graph[mid] += 1
time.sleep(0.25)
return sorted(graph.items(), key=lambda x: -x[1])[:20]
print(cooccurrence("7012345678901234567"))
The top-ranked co-occurring sounds are your next watchlist entries. Loop in the music endpoints to enrich each one with metadata.
Plan credits like you plan compute. Rough numbers for a label running a 50-track watchlist:
Total: roughly 31,000 credits/month for a serious A&R + sync operation. Credits never expire, so under-using one month banks budget for a release window the next. Check current bundles on the pricing page.
No. Every endpoint authenticates with a single X-Api-Key header. No OAuth, no signing, no rotating tokens.
The response shape mirrors TikTok's internal schemas. /music-info/ and the post lists use snake_case; user info endpoints use camelCase. Normalize on ingest and you will never think about it again.
music_id from a TikTok URL I have?Resolve via /post-detail/ as in Recipe 1. The music_info.id field is your music_id for every other call.
Yes. /post-detail/ returns a direct music mp3 URL alongside watermark-free video links (play, hdplay). Respect rights holders - downloads are for analysis, not redistribution.
/music-posts/ and /search-video/?/music-posts/ is sound-anchored - give it a music_id and get every video using that exact sound. /search-video/ is keyword-anchored - give it text and get videos whose captions, hashtags, or sound titles match. Use the former for tracking, the latter for discovery.
Responses are pulled in real time from TikTok with millisecond-level latency on cached metadata. Treat counts as accurate to the minute, not the second.
The playground runs every endpoint in the browser with your key, and the documentation shows request and response schemas side by side. Start there before you wire anything into your warehouse.
That is the cookbook. Twelve recipes, three endpoints, one header. Ship the daily snapshot job first - it will pay for itself the first week you catch a breakout your competitors miss.
Ready to put what you read into code? Try our endpoints live or grab the full reference.