Endpoints
Search 2
Users 3
Videos 5
Lookup 2
Hashtag 1
Music 1
Effect 2
Place 2
Trending 2
What is the TikTok API?
Public TikTok data through one predictable REST API: profiles with exact, follower and like counts, videos, comments, hashtags, music, effects, search and trending — plus a /stream endpoint that returns the actual playable MP4. Same JSON envelope on every response, cursor pagination, stable error codes. No login, no TikTok keys, no watermark tooling required.
TikTok API is available on the JoJ API marketplace. Subscribe with a single JoJ API key, call it through one gateway, and pay from one wallet — no separate account or contract with the provider.
TikTok API endpoints
- GET
/v1/search/suggestionsSearch Suggestions
Returns TikTok's own search suggestions for a keyword. **These are suggestions, not search results.** For the actual result posts, use `/v1/search/videos`; this one is cheap enough for autocomplete.
- GET
/v1/search/videosSearch Videos
Returns the video posts TikTok serves on its results page for a keyword, across many creators, with each post's author handle. Play and like counts are rounded by TikTok to three significant digits at any size, so rows report `statsPrecision` `approximate`; comment, share and save counts arrive exact. Paged: hand back the envelope's `cursor` as `cursor`, or pass **both** `offset` and `searchId` from the body - they are the same two values. An `offset` on its own reads as the end of the results, which is why the envelope cursor carries the pair joined by a colon.
- GET
/v1/profile/{username}User Details
Returns an account's public profile: identity, avatars, bio and link, account settings, and **exact** follower, following, like and post counts. If you only need followers, `secUid` or live state, `/v1/profile/{username}/live` answers the same question far more cheaply. **`include=videos`** returns the account's posts in the same answer, for a creator screen that would otherwise take two round trips. The cover images in each row load straight from TikTok's CDN in a browser, so a grid of thumbnails costs no further calls; only playback does, through `/v1/video/{video_id}/stream`. It is opt-in because it changes what the call costs: a profile with its posts takes several times longer than the profile alone. The envelope's `cursor` and `hasMore` then describe the posts, since they are the only part that pages, and `cursor` is accepted back here to walk them without asking for the profile again.
- GET
/v1/profile/{username}/videosUser Videos
Returns a creator's posts newest first: description, cover, play address, duration, publish time, hashtags, the track, and engagement counts. **Pagination.** Pass the envelope's `cursor` back as `cursor` to continue; when it comes back `null` the catalogue is exhausted. Use `limit` to stop early. Hand the cursor back exactly as it was given - it carries its own position space, `createTimeMs:…` or `rowOffset:…`. A bare number is still accepted and read as `createTimeMs`. **About the counts.** TikTok rounds play and like counts to three significant digits on listings, at any size, so `statsPrecision` says `approximate`. Comment, share and save counts arrive exact. This is TikTok's rounding, not ours. `exact=true` is accepted here for compatibility and does nothing; `/v1/video/{video_id}?exact=true` returns unrounded counts for a single post.
- GET
/v1/profile/{username}/repostsUser Reposts
Returns the videos this account has reposted - other people's posts, not its own. Each row carries the same fields as the post listing, and an `authorUsername` naming whoever actually made the video. Play and like counts are rounded by TikTok to three significant digits at any size, so rows report `statsPrecision` `approximate`; comment, share and save counts arrive exact. Paged fifteen at a time. Pass `cursor` from `nextCursor` to continue.
- GET
/v1/video/{video_id}Video Details
Returns a single post: description, timestamp, media, music, hashtags, mentions, author, and play, like, comment, share, save and repost counts. **Play and like counts are rounded by TikTok to three significant digits** - 11,381 plays are published as 11,400; comment, share and save counts are exact. `exact=true` asks for unrounded counts; `statsPrecision` says which you got, and `exactReason` says why when they could not be had: `not_original_sound` and `not_listed` will not change, `beyond_first_page`, `counts_missing` and `lookup_failed` may on a retry. Photo posts are returned too and flagged with `isPhotoPost`; their duration is zero and they carry no play address. Only the id is needed - TikTok resolves a post without the handle. **`playAddr` and `downloadAddr` are not playable on their own.** TikTok's media host refuses requests that do not come from its own pages, so a play address pasted into a player or a download tool will fail. Use `GET /v1/video/{video_id}/stream` to play or download the file.
- GET
/v1/video/{video_id}/streamVideo Stream file
Streams the post's MP4 through this API. **Use this rather than the `playAddr` returned by `/v1/video/{video_id}`.** TikTok's media host refuses play addresses requested outside its own pages, so a `playAddr` pasted into a player or a download tool will fail; this endpoint hands back the bytes in the only shape that plays. `Range` is passed through, so seeking works and a player can start mid-file. Do not store responses: the address behind them expires.
- GET
/v1/videosMulti-video Details
Takes a comma-separated list of post ids and returns them together, which is the cheapest way to enrich a list of ids you already hold. The rows carry all six counts, the publish time, the duration, the track, the hashtags and the mentions. **Play and like counts are rounded by TikTok** to three significant digits - 527,168 plays are published as 527,200. `/v1/video/{video_id}?exact=true` returns unrounded counts, but only for a post using its own original sound and only one post at a time. Twenty ids is the ceiling, and a longer list is rejected rather than silently truncated. Repeated ids are fetched and returned once, in the order you sent them. The two ways an id can come back without a post are kept apart: `missing` is TikTok saying it holds no such post, and asking again will not change that, while `notFetched` is an id that went unread - the request ran out of time, or TikTok stopped answering part way down the list - and those are worth asking for again.
- GET
/v1/video/{video_id}/commentsVideo Comments
Returns the comments on a post - text, author, timestamp, and like and reply counts. Paged twenty at a time. Pass `cursor` from `nextCursor` to continue.
- GET
/v1/video/{video_id}/comments/{comment_id}/repliesVideo Comment Replies
Returns the replies to one comment - the thread beneath it - with the same fields as a top-level comment. Paged twenty at a time. Pass `cursor` from `nextCursor` to continue.
- GET
/v1/user/{user_id}/handleUser ID to Username handle
Turns a TikTok user id into the @handle. Useful when a dataset carries ids but every other endpoint here takes handles.
- GET
/v1/profile/{username}/liveCheck User Live Status
Reports whether the account is broadcasting at this moment, and when it is, the room id, title, cover, viewer count and start time - alongside the identity fields and exact follower counts that come with it. `isLive` follows the room's own state rather than the presence of a room id: a room id survives the broadcast that created it, so an account that streamed once years ago still carries one. Everything describing the broadcast is returned only while it is on air, so a stale title or viewer count is never published as current. Much cheaper than `/v1/profile/{username}` when all you need is followers, `secUid` or live state. Answers are never more than thirty seconds old.
- GET
/v1/hashtag/{name}Hashtag Details
Returns the hashtag's own numbers - **exact** total views and total posts - together with a page of the posts TikTok lists for it. **The posts are TikTok's ranking for the tag, not a strict membership list.** Roughly half the rows on a popular tag carry the tag itself; the rest carry adjacent tags. TikTok's own tag page shows the same rows. **A tag's figures and its posts do not always agree** on whether a tag can be read. Some tags have real, readable figures while TikTok refuses their post listing; those answer with the tag, an empty `videos` list and `videosAvailable: false`. It is not a size ceiling - `#fyp` answers normally with billions of posts. A tag nobody holds is a 404, and an outage is an error rather than an empty list. Counts on the posts are rounded by TikTok above roughly a hundred thousand. `/v1/video/{video_id}?exact=true` returns unrounded counts, but only for a post using its own original sound. `cursor` is opaque - hand back whatever the previous response gave; a page of thirty can carry a few more or fewer rows, so it is not a row count. `dropped` reports rows that arrived unreadable, so a short page is never silently short. `related=true` adds the tags TikTok itself lists as related, with their own exact counts. Slower.
- GET
/v1/music/{music_id}Music Details
Returns a track's own details together with the posts using it. Only the numeric id is needed. The slug that appears in a TikTok music URL is decoration and is ignored. Pagination is by row offset: pass the `cursor` from the previous response back to continue. `cursorSpace` on the body names the unit, because the post listings on this API count in milliseconds under the same field name. A response with `hasMore` false and no cursor means this walk reached the end. `limit` is a floor, not a ceiling: TikTok serves this listing a whole page at a time, so a response can carry more posts than it asks for. Play and like counts here are exact. A track TikTok will not list is an error rather than a partial body.
- GET
/v1/effect/{effect_id}Effect Details
Returns the effect's name, owner and icon, together with two counts nothing else on the public web reports: how many posts have used it and how many times those posts were played. Both counts are unrounded.
- GET
/v1/effect/{effect_id}/videosEffect Videos
Walks the posts that use an effect, newest first. The counts on these rows are **exact** - a post that reads 648,000 on a web listing reads 648,086 here. `cursor` is the row offset the previous response handed back; pass it to continue. `dropped` reports rows that arrived unreadable, so a short page is never silently short.
- GET
/v1/place/{place_id}Place Details
Returns TikTok's own record for a place - its name, category and address - together with how many posts it holds. Address fields arrive sparse: a city carries a country and little between, and a missing field is null rather than an empty string.
- GET
/v1/place/{place_id}/videosPlace Videos
Walks the posts filmed at a place, newest first. Every row carries that place's id, so this is a geographic query rather than a feed of whatever is nearby. Counts on these rows are **approximate**: TikTok rounds plays and likes above roughly a hundred thousand on listings. Ask `/v1/video/{video_id}` for a real number. `cursor` is opaque - hand back whatever the previous response gave. `dropped` reports rows that arrived unreadable, so a short page is never silently short.
- GET
/v1/trendingTrending Videos
Returns the posts TikTok publishes as top performing for a country and period, with their author, follower count, and engagement figures. **These metrics are not available anywhere else on this API.** Engagement rate and six-second view-through are advertising measures; ordinary post payloads do not carry them. **Limits, stated rather than discovered.** A hundred posts per country and period is the whole list. Only the United States is available; other countries are refused rather than answered with an empty list. `period` is `daily`, `weekly` or `monthly`.
- GET
/v1/trending/hashtagsTrending Hashtags
Returns the hashtags TikTok publishes as trending, with how many posts carry them, how many views they have drawn, TikTok's own daily popularity curve, and the creators driving them. **Creators come with real user ids**, so a caller can pass one straight to `/v1/profile/{username}` rather than searching by name. **Covers 27 countries**, including ones the video ranking does not - Turkey among them. Three tags per country and period is the whole list. `period` is 7, 30 or 120 days. Other values are refused.
Frequently asked questions about TikTok API
Do I need a TikTok account, cookies, or an official API key?
No. Every endpoint reads public data — you just send a plain REST request and get JSON back. Sessions, proxies and signing are handled server-side.
Are the numbers exact or TikTok-rounded?
TikTok rounds play/like counts on all web surfaces (11,381 shows as 11,400). Profile follower/like counts are always exact, and /video/{id}?exact=true reads the app surface for unrounded counts. Every response tells you which you got in the statsPrecision field.
The playAddr URL from a video response returns 403 — how do I play the video?
That's TikTok, not us: media URLs only work with the session that produced them. Use GET /video/{id}/stream — it returns the playable MP4, supports Range (seeking) and ?download=true for the download variant.
How does pagination work?
Every response carries cursor and hasMore in the same envelope. Pass the cursor back unchanged to get the next page; when it returns null, you've reached the end.
What content can I query?
Profiles (with posts and reposts), single videos and photo posts, comments and replies, hashtags, music, effects, live status, keyword search with suggestions, and trending videos/hashtags — plus a batch endpoint that returns up to 20 videos in one request.