A TikTok sound is a first-class object on the platform. It has its own ID, its own page and its own catalogue of videos filmed over it. Most tooling ignores that and treats audio as a caption-level string, which is why so many trending-sound dashboards cannot tell you how long a track runs, who it is attributed to, or whether it is a licensed release or something a creator hummed into a phone. This guide covers the two calls that fix that: music-info, which resolves a numeric music ID into the full sound record, and music-posts, which lists the videos built on it. Every field named below comes from live responses captured against the production API.
Where a musicId comes from
Neither endpoint accepts a sound name, a slug or a search term. Both require musicId, the numeric identifier TikTok assigns to a sound - something like 7224128604890990593. There are two ways to obtain one, and only one of them is worth building a pipeline on.
Out of any video record
This is the reliable route. Every video object the API returns carries a nested music object, and that object's id is the musicId. In post-detail it sits at itemInfo.itemStruct.music.id. On the feed-shaped endpoints - user-posts, user-liked-posts, user-repost, challenge-posts, place-posts - every entry in itemList exposes the same music.id. So any crawl you are already running is quietly collecting sound IDs, and the only work left is to deduplicate them.
That nested block is not empty either. On the feed endpoints it carries id, title, authorName, playUrl, the three cover sizes, duration, shoot_duration, original, private, isCopyrighted, is_commerce_music, is_unlimited_music and tt2dsp. The copy inside post-detail adds album, preciseDuration, collected and scheduleSearchTime. If all you need is a label for a video, use what you already have and skip the extra call. Two things send you to music-info anyway: the artist profile, and the platform-wide video count for the sound - neither of which appears in the nested copy. The walkthrough on reading TikTok video data via API covers that item structure in full.
Out of a sound page URL
Public TikTok sound pages end in the numeric music ID, so pasting a link and taking the trailing digits works for one-off lookups and for accepting user input in a tool. Treat it as a convenience, not infrastructure: URL formats change, slugs get rewritten, and a video record hands you the same number without any parsing.
Calling music-info
The base URL is https://api.primeapi.co/ and authentication is one header, X-PrimeAPI-Key. music-info takes a single required parameter, musicId, and has no optional ones - there is nothing to page, because a sound record is one object.
curl
curl -s "https://api.primeapi.co/music-info?musicId=7224128604890990593" \
-H "X-PrimeAPI-Key: YOUR_API_KEY"
Node.js (Axios)
const axios = require("axios");
const KEY = "YOUR_API_KEY";
async function getSound(musicId) {
const res = await axios.get("https://api.primeapi.co/music-info", {
params: { musicId },
headers: { "X-PrimeAPI-Key": KEY }
});
const info = res.data.musicInfo || {};
const m = info.music || {};
console.log("balance:", res.headers["x-primeapi-balance"]);
return {
id: m.id,
title: m.title,
author: m.authorName,
album: m.album,
seconds: m.duration,
play: m.playUrl,
cover: m.coverLarge,
isOriginalSound: m.original === true,
licensed: m.isCopyrighted === true,
artistHandle: info.artist ? info.artist.uniqueId : null,
videoCount: info.stats ? info.stats.videoCount : null
};
}
getSound("7224128604890990593")
.then(s => console.log(s))
.catch(err => console.error(err.response ? err.response.data : err.message));
Python (Requests)
import requests
BASE = "https://api.primeapi.co"
HEADERS = {"X-PrimeAPI-Key": "YOUR_API_KEY"}
def get_sound(music_id):
r = requests.get(
f"{BASE}/music-info",
params={"musicId": music_id},
headers=HEADERS,
timeout=15,
)
r.raise_for_status()
info = r.json().get("musicInfo", {})
music = info.get("music", {})
artist = info.get("artist") or {}
return {
"id": music.get("id"),
"title": music.get("title"),
"author": music.get("authorName"),
"album": music.get("album"),
"seconds": music.get("duration"),
"shoot_seconds": music.get("shoot_duration"),
"play_url": music.get("playUrl"),
"original": music.get("original"),
"copyrighted": music.get("isCopyrighted"),
"commerce": music.get("is_commerce_music"),
"artist_handle": artist.get("uniqueId"),
"artist_name": artist.get("nickname"),
"video_count": info.get("stats", {}).get("videoCount"),
"dsp": bool(music.get("tt2dsp", {}).get("tt_to_dsp_song_infos")),
}
if __name__ == "__main__":
print(get_sound("7224128604890990593"))
The sound record, field by field
Everything useful lives under musicInfo, which holds four children: music, artist, artists and stats. The track itself is musicInfo.music.
| Field | Type | What it holds |
|---|---|---|
id | string | The musicId you passed in. Your primary key. |
title | string | The sound name as TikTok displays it. |
authorName | string | The credited author string on the sound. |
album | string | Album name for catalogue releases. |
duration | int | Length of the sound in seconds. |
shoot_duration | int | The second duration value TikTok exposes, for the recordable segment. |
playUrl | string | Direct CDN link to the audio. |
coverThumb / coverMedium / coverLarge | string | Artwork at three sizes. |
original | bool | True for a creator-recorded original sound. |
private | bool | True when the sound is not openly available. |
isCopyrighted | bool | Rights flag for catalogue material. |
is_commerce_music | bool | Whether the track sits in the commercial-use library. |
is_unlimited_music | bool | Companion usage flag on the same record. |
tt2dsp | object | Holds tt_to_dsp_song_infos, the streaming-service mapping. |
The artist block is an account, not a label
musicInfo.artist is a compact profile: id, uniqueId, nickname, secUid, signature, the three avatar sizes, and the flags privateAccount, secret, ftc, openFavorite and relation. musicInfo.artists is an array of the same shape, so read the array when you need every credited account and keep artist as the singular fallback.
Be careful what you infer from it. This is the TikTok account attached to the sound record, which is not always the official artist account - in one response captured for this guide the nickname read as the recording artist while uniqueId was an unrelated personal handle. Display nickname if you like, but do not build attribution logic on uniqueId without checking it. Note also that the artist block carries no verified field; if you need that, resolve the handle separately with userinfo-by-username, which returns the full profile including the verification flag.
Reach, share metadata and the envelope
musicInfo.stats contains exactly one number: videoCount, the platform-wide count of videos using this sound. That single integer is the cheapest audio-trend metric there is - one credit, one field, and sampled daily it gives you a growth curve per sound without touching a single video. Outside musicInfo the response carries shareMeta with title and desc - the pre-formatted share strings - plus statusCode, status_code, status_msg, an extra object with logid and now, and log_pb.impr_id. Log those two identifiers; they are the fastest way to point support at one specific upstream response.
Original sounds versus licensed tracks
Four booleans decide how you should treat a sound. original true means a creator made it - a voiceover, a skit, a remix recorded in the app. Those sounds have no album, usually nothing under tt2dsp, and an artist block that is simply the creator's own account. isCopyrighted, is_commerce_music and is_unlimited_music describe the rights side of catalogue material, which matters if you are advising brands on what they can legally use. The distinction also shapes analysis: an original sound spreading across thousands of videos is an organic meme, while a licensed track doing the same is usually a label campaign.
playUrl, and when to use download-music instead
playUrl is a link, not a file. Fetch it yourself if you want the audio, and expect CDN links to expire, so store the bytes rather than the URL if you are archiving. When your input is a video URL rather than a music ID, download-music is the shorter path - it returns a single play field and nothing else, which makes it cheap to run in bulk. The post on downloading TikTok audio as MP3 covers the difference in detail.
From the sound to its videos: music-posts
music-posts takes the same musicId plus optional count and cursor. Before you write the parser, note that its envelope is nothing like music-info. The payload is wrapped in code, msg, processed_time and data; the items live in data.videos; and cursor and hasMore both come back as integers, so hasMore is 1 or 0 rather than true or false.
def get_sound_videos(music_id, pages=4, page_size=20):
videos, cursor = [], 0
for _ in range(pages):
r = requests.get(
f"{BASE}/music-posts",
params={"musicId": music_id, "count": page_size, "cursor": cursor},
headers=HEADERS,
timeout=20,
)
r.raise_for_status()
data = r.json().get("data", {})
videos.extend(data.get("videos", []))
cursor = data.get("cursor", 0)
if not data.get("hasMore"):
break
return videos
for v in get_sound_videos("7224128604890990593"):
print(v["aweme_id"], v["play_count"], v["title"][:60])
Each video in data.videos is flat rather than nested. Identity comes from aweme_id and video_id, with region, title, content_desc, duration and create_time describing the clip. Imagery is cover, origin_cover and ai_dynamic_cover. The playable files are play and the watermarked wmplay, with size and wm_size for each. Engagement arrives as play_count, digg_count, comment_count, share_count, download_count and collect_count, and each item repeats its own music object. The remaining fields - anchors, anchors_extras, is_ad, commerce_info, commercial_video_info, item_comment_settings and mentioned_users - are mostly commerce and moderation metadata, and is_ad is the one worth filtering on before you compute averages.
Two guides go further with this endpoint: one on finding every video using a TikTok sound, and one on tracking trending TikTok sounds, which builds on the videoCount sampling described above.
Credits, limits and errors
Each request costs 1 credit whatever it returns, and the balance left comes back in X-PrimeAPI-Balance. The default rate limit is 100 requests per minute, and responses are live with no caching, averaging about a second. A sound profile is one call, so a catalogue of five thousand tracks costs five thousand credits and under an hour of wall clock; the video pages are the expensive half, so budget by pages of music-posts rather than by sounds. New accounts get 50 free credits, which is enough to test both endpoints against a real track before writing anything. Paid tiers on the pricing page run from 2,500 credits ($9.90) to 500,000 ($279.00).
Four failures arrive as a JSON message rather than a bare status code: "Please sign up to primeapi.co" when the key header is missing, "PrimeAPI-Key is not available" for a wrong or unactivated key, the per-minute limit message when you cross 100 requests in a window, and "Your balance has been exhausted..." when credits run out. Branch on that body. Separately, a deleted or restricted sound will answer without the data you expected rather than with an error, so check that musicInfo.music.id exists before you read from it. Full parameter tables live in the API documentation, and both calls can be run against a live sound in the playground before you write any code.
Putting it together
The working pattern is short. Harvest music.id from the video records you are already collecting, deduplicate, then call music-info once per sound and store title, authorName, duration, original, the rights flags and stats.videoCount. Re-read videoCount on a schedule and the delta tells you which sounds are accelerating; only then spend credits on music-posts to see who is actually using them. That order matters, because it puts one cheap call in front of the expensive paging and keeps a sound catalogue affordable at scale. PrimeApi is an independent service and is not affiliated with or endorsed by TikTok or ByteDance; create an account and the free credits will cover a first pass over a few dozen tracks.
Frequently asked questions
Where do I get a TikTok musicId?
From any video record. Every item the API returns carries a nested music object, and its id is the musicId - in post-detail it sits at itemInfo.itemStruct.music.id, and on feed endpoints such as user-posts, challenge-posts or place-posts it is music.id on each entry of itemList. A public sound page URL also ends in that same number, but reading it out of a video record is the version you can automate.
Does music-info return the audio file itself?
It returns playUrl, a direct CDN link to the sound, plus coverThumb, coverMedium and coverLarge for the artwork. That is a link, not a file - you still fetch it yourself. If you are starting from a video URL rather than a music ID, download-music is the shorter path, since it answers with a single play field.
What does the original flag mean?
original is a boolean on the music object that separates a sound a creator recorded themselves from a track that came out of TikTok's licensed catalogue. Read it together with isCopyrighted, is_commerce_music and is_unlimited_music, which describe the rights side, and with private, which tells you the sound is not openly available.
Where are the streaming service links?
Inside tt2dsp, which holds a single key, tt_to_dsp_song_infos. That is where the mapping from a TikTok sound to catalogue entries on streaming services lives. It only makes sense for released music, so for creator-recorded original sounds there is nothing to map - check it exists before you index into it.
Why does music-posts look nothing like music-info?
They come from different upstream shapes and you should not share a parser. music-info nests everything under musicInfo and pages nothing. music-posts wraps its payload in code, msg, processed_time and data, puts the items in data.videos, and returns cursor and hasMore as integers rather than a string and a boolean.
How many credits does a sound lookup cost?
One credit per request, the same as every endpoint, with the remaining balance in the X-PrimeAPI-Balance response header. A sound record is a single call, so profiling a thousand tracks costs a thousand credits; the video pages you pull afterwards with music-posts are what actually drive spend. New accounts start with 50 free credits.