Home > Blog > How to Get TikTok Video Data via API
Post Data

How to Get TikTok Video Data via API

By PrimeApi·August 15, 2026·6 min read·Updated August 15, 2026

A TikTok video page shows you a caption, four counters and a sound name. The record behind that page is far bigger: encoding details and multiple play addresses, subtitle tracks, the full sound object, the author profile with their own statistics, every hashtag attached to the caption, and five separate engagement counters. This guide covers how to read that record with the post-detail endpoint - how to extract a post ID from a TikTok URL, what each block of the response holds, and how to collect IDs in bulk with user-posts when you need a whole catalogue instead of a single clip.

How do you get a post ID from a TikTok URL?

Everything starts with the numeric post ID, and almost every input you receive from a user will be a URL rather than an ID. There are three cases worth handling.

Standard web URLs

A canonical video URL looks like https://www.tiktok.com/@taylorswift/video/7330315169584778539. The post ID is the trailing numeric segment. Image slideshow posts use /photo/ in place of /video/ but follow the same shape, so one pattern covers both. Strip any query string first - share links routinely arrive with tracking parameters appended.

import re

def post_id_from_url(url: str):
    m = re.search(r"/(?:video|photo)/(\d+)", url.split("?")[0])
    return m.group(1) if m else None

print(post_id_from_url("https://www.tiktok.com/@taylorswift/video/7330315169584778539?is_from_webapp=1"))
# 7330315169584778539

Short links

Links shared from the mobile app look like https://vm.tiktok.com/ZMxxxxxxx/ or https://vt.tiktok.com/ZSxxxxxxx/. These carry no post ID at all - the ID only exists after the redirect resolves. Follow the redirect yourself with an ordinary HTTP client and read the Location header, then run the resulting canonical URL through the pattern above. This step costs no credits because it never touches the API.

IDs in bulk

If your input is a creator rather than a link, skip URL parsing entirely. Resolve the handle to a secUid once, then call user-posts: every item in the returned feed already exposes its post ID in the id field, ready to hand straight to post-detail. You get that secUid from a one-off handle lookup with userinfo-by-username; the identifier itself is explained in what a TikTok secUid is and how to get one.

Calling post-detail

The base URL is https://api.primeapi.co/, authentication is the single header X-PrimeAPI-Key, and post-detail takes one required query parameter: postId. There are no optional parameters, which makes it one of the simplest calls in the catalogue.

curl

curl -s "https://api.primeapi.co/post-detail?postId=7330315169584778539" \
  -H "X-PrimeAPI-Key: YOUR_API_KEY"

Node.js (Axios)

const axios = require("axios");

async function getPost(postId) {
  const res = await axios.get("https://api.primeapi.co/post-detail", {
    params: { postId },
    headers: { "X-PrimeAPI-Key": "YOUR_API_KEY" }
  });
  console.log("Remaining credits:", res.headers["x-primeapi-balance"]);

  const item = res.data.itemInfo && res.data.itemInfo.itemStruct;
  if (!item) throw new Error("No item struct - video unavailable");
  return item;
}

getPost("7330315169584778539")
  .then(item => console.log(item.desc, item.stats.playCount))
  .catch(err => console.error(err.response ? err.response.data : err.message));

Python (Requests)

import requests

def get_post(post_id):
    resp = requests.get(
        "https://api.primeapi.co/post-detail",
        params={"postId": post_id},
        headers={"X-PrimeAPI-Key": "YOUR_API_KEY"},
        timeout=15,
    )
    print("Remaining credits:", resp.headers.get("X-PrimeAPI-Balance"))
    resp.raise_for_status()
    return resp.json().get("itemInfo", {}).get("itemStruct")

item = get_post("7330315169584778539")
if item:
    print(item["desc"])
    print(item["stats"]["playCount"], "plays")
    print(item["music"]["title"], "by", item["music"]["authorName"])

What the response contains

Everything sits under itemInfo.itemStruct. Alongside it the payload carries a small shareMeta object with title and desc - the text TikTok uses for link previews - plus statusCode and statusMsg. The item struct itself groups into five blocks: the post, the video, the music, the author, and the counters.

FieldHolds
idThe post ID, echoed back as a string.
descThe caption, hashtags and mentions included as written.
createTimeUpload time as a Unix timestamp in seconds. Returned as a string here.
video.durationClip length in seconds.
video.width, video.height, video.ratioPixel dimensions plus the quality label TikTok assigns.
video.cover, originCover, dynamicCoverStill thumbnail, unprocessed still, and the animated preview. shareCover, reflowCover and zoomCover cover other placements.
video.playAddr, downloadAddrSigned CDN addresses for streaming and for the download variant.
video.bitrate, bitrateInfo, sizeDefault bitrate, the per-variant list, and file size in bytes.
video.format, codecType, encodedType, definition, videoQualityContainer, codec and quality descriptors for the default rendition.
video.subtitleInfosAvailable caption tracks for the clip.
video.volumeInfo, VQScoreLoudness normalisation data and TikTok's internal quality score.
musicThe sound: id, title, authorName, album, duration, preciseDuration, playUrl, cover art at three sizes, and the original, isCopyrighted, is_commerce_music flags.
authorThe creator: id, uniqueId, nickname, secUid, signature, verified, avatars, and interaction settings such as duetSetting, stitchSetting and downloadSetting.
authorStatsThe creator's own totals: followerCount, followingCount, heartCount, videoCount, diggCount, friendCount.
stats / statsV2playCount, diggCount, commentCount, shareCount, collectCount; statsV2 adds repostCount.
challengesOne record per hashtag on the post, each with its own ID and title.
textExtraPositioned entities inside the caption - hashtags and mentions with their offsets.
FlagsisAd, originalItem, officalItem, privateItem, secret, duetEnabled, stitchEnabled, shareEnabled, itemCommentStatus, takeDown.

Play addresses, bitrate variants and subtitles

The video block is where this endpoint earns its keep. bitrate gives you the default rendition, while bitrateInfo enumerates the variants TikTok encoded, each with its own play address - useful if you are picking a rendition to match a bandwidth budget rather than always taking the default. subtitleInfos lists the caption tracks, which is the practical route into transcript work: you get the machine-generated captions without running speech recognition yourself.

Treat playAddr and downloadAddr as short-lived. They are signed URLs, they expire, and fetching them usually needs the right request headers. If your goal is a file you can store or serve, the watermark-free download walkthrough covers the endpoint built for that, which takes the post URL and hands back a plain address you can stream or store.

Music, hashtags and the author

Two nested blocks are useful as pivots rather than as display data. The music.id value identifies the sound, and feeding it to music-info or music-posts turns a single video into a trend question: is this track still spreading, and who else is filming over it? The challenges array does the same job for hashtags, and author.secUid lets you jump straight from a video to that creator's whole feed without a separate profile lookup.

From one video to a whole catalogue

For anything beyond a single clip, user-posts is the companion call. It takes a required secUid plus optional count and cursor, and returns data.itemList with data.cursor and data.hasMore for paging. Each feed item is a trimmed version of the same structure - id, desc, createTime, the author and authorStats blocks, music, and flags such as IsHDBitrate, collected, digged and isAd. When you need the full video block, subtitle tracks or the complete counter set, take the item's id and call post-detail on it.

import requests

HEADERS = {"X-PrimeAPI-Key": "YOUR_API_KEY"}

def all_post_ids(sec_uid, max_pages=10):
    ids, cursor = [], "0"
    for _ in range(max_pages):
        r = requests.get(
            "https://api.primeapi.co/user-posts",
            params={"secUid": sec_uid, "count": 35, "cursor": cursor},
            headers=HEADERS, timeout=20,
        )
        data = r.json().get("data", {})
        ids += [i["id"] for i in data.get("itemList", [])]
        if not data.get("hasMore"):
            break
        cursor = data.get("cursor")
    return ids

Budget this properly: one page of the feed is 1 credit, and every enrichment call is another. Fetching a 200-video catalogue at 35 per page costs about 6 credits for the feed and 200 for the details. The full-catalogue guide works through the paging edge cases. Once you have the counters, engagement rate is straightforward arithmetic on stats and authorStats.

Credits, limits and errors

Every request costs 1 credit, including one that finds nothing, and the default rate limit is 100 requests per minute. Data is fetched live with no caching, so a play count reflects the moment you asked, and responses average around a second. Watch the X-PrimeAPI-Balance header rather than polling your dashboard.

Four failures return a JSON message and are worth branching on explicitly: "Please sign up to primeapi.co" means no key header was sent; "PrimeAPI-Key is not available" means the key is wrong or the account is not activated; the per-minute message means you crossed 100 requests in a window; and "Your balance has been exhausted..." means you are out of credits. Separately from those, a valid request for an unavailable video returns without a populated itemStruct - always check for it before reading fields. Full error details live in the API documentation, and you can try any endpoint against your own key in the interactive playground before writing code.

Putting it together

The pattern that holds up in production is short: normalise whatever arrives - short link, canonical URL or raw ID - into a post ID, call post-detail once per video, store the item struct as delivered rather than a handful of extracted fields, and re-fetch on a schedule if you are tracking growth. When you need more than one clip, drive it from user-posts and enrich each ID. Comment threads sit one call away too, via the comment scraping guide. Fifty free credits on a new account are enough to test the whole flow end to end before you commit to a plan. PrimeApi is an independent service and is not affiliated with or endorsed by TikTok or ByteDance.

Frequently asked questions

How do I find the post ID in a TikTok URL?

It is the numeric segment at the end of a standard video URL. In https://www.tiktok.com/@taylorswift/video/7330315169584778539 the post ID is 7330315169584778539. A regular expression such as /(?:video|photo)/(\d+)/ pulls it out reliably, and you should strip any ? query string first. Short vm.tiktok.com links do not contain the ID at all - follow the redirect to the full URL, then parse that.

Do I need a secUid or a login to call post-detail?

No. post-detail takes one required parameter, postId, and your X-PrimeAPI-Key header. The secUid identifier is only needed by profile-feed endpoints such as user-posts, and there is no TikTok login or OAuth step anywhere in the flow.

How many credits does one video lookup cost?

One credit per request, the same as every other endpoint, whether the video is a viral clip or an obscure one. New accounts start with 50 free credits and the remaining balance comes back in the X-PrimeAPI-Balance response header, so you can track spend without a second call.

What is the difference between stats and statsV2?

They carry the same five engagement counters - playCount, diggCount, commentCount, shareCount and collectCount - but stats returns them as numbers while statsV2 returns them as strings and adds repostCount. Read statsV2 when you want repost data, and cast its values before doing arithmetic.

Can I download the video file from the post-detail response?

The video block contains playAddr and downloadAddr, but those are signed CDN addresses that expire and normally require the right request headers to fetch. For a stable, watermark-free file link use the download-video endpoint instead, which takes the post URL and returns a plain playable address.

Why does a post return no item data?

Deleted, private, region-blocked and age-restricted videos will not come back with a populated itemInfo.itemStruct, even though the request still succeeds and still costs a credit. Check that itemStruct exists before reading fields from it, and log statusCode and statusMsg so you can tell an unavailable video apart from a malformed post ID.

Start building with the PrimeApi TikTok API
50 free credits when you sign up. No card required - you only pay when you need more.

← Back to the PrimeApi Blog