TikTok API v1
Public TikTok data — accounts, videos, hashtags, search and comments — as JSON, from https://api.fetchlayer.dev/tiktok. Everything returned is public on TikTok: no private audience data, no analytics, and no metric TikTok does not publish. FetchLayer is not affiliated with, endorsed by, or sponsored by TikTok.
Available endpoints
/user-profile POST List an account’s videos /user-videos POST Get one video in full /video-detail POST Read a video’s comments /comments POST Get a video’s transcript /video-transcript POST Search TikTok videos /search-videos POST List videos under a hashtag /hashtag-videos POST Parse a TikTok URL (free) /resolve-url GET Download a video, image or cover /media One video shape, everywhere
Every route that returns videos returns the same record, so code written against a search result works unchanged on an account's posts or a hashtag feed: id, url, description (the caption), createdAt, durationSeconds, isPhotoPost, coverUrl, assets, stats, author, music, hashtags, mentions, mentionedUsers, hashtagDetails, roundedStats, isAd, isPaidPartnership, isShoppable, createdRegion and language.
assets is an ordered list, which is how a clip and a photo carousel are handled the same way: a clip yields one asset of kind video, a carousel one of kind image per frame. Hashtags and mentions are TikTok's own tags on the post, not parsed out of the caption, so a tag written inside a word does not produce a false positive. mentionedUsers and hashtagDetails carry each tag's ids and its position in the caption, in UTF-16 code units, so description.slice(start, end) is the tag as shown — which for an account is often its display name rather than its username. A mentioned account TikTok no longer publishes keeps its userId with username and secUid null and unresolved: "account_unavailable"; a tag with no account id at all is "not_published".
TikTok publishes a video's views, likes, comments and shares rounded once they reach 10,000: to the nearest 100 from 10,000, the nearest 100,000 from 1M, the nearest 100M from 1B. roundedStats names the counters on a record that are rounded, and an empty list means every one is exact. Saves, and every account and hashtag count, are exact. isAd means TikTok runs or ran the post as a promoted ad; isPaidPartnership is TikTok's paid-partnership label; isShoppable marks a shop or product video. createdRegion is where that one post was made, filled on /video-detail, and on the list routes when the request has include: ["createdRegion"]; accountRegion, the region the account is registered in, comes on each video with include: ["exactStats"], on /video-detail and the list routes. profile.region is the same fact on the account: the first request for an account answers it null with a note, and later requests carry it. profile.postingRegion is where its recent videos were posted, which can differ. commercialSignals carries TikTok's own raw values behind the paid-partnership and shop flags. Counts are as TikTok shows them to US viewers. TikTok keeps two US copies of an account’s counts, which can differ by about one post and a few hundred followers; successive requests can land on either, and neither is newer.
Media: video files, images and covers
TikTok's own asset URLs are signed and expire within hours, so every asset carries a downloadUrl pointing at GET /media instead, which delivers it on demand with Range support.
A video's downloadUrl is /media?video=<id> and delivers the video as an MP4 without the TikTok watermark; it is null when the creator has turned off downloads, and /media then answers 403, “The creator has turned off downloads for this video.” A photo post, or a video that is not public, is a 404. Images and covers use /media?url=…; an expired one is a 404. Each request to /media costs 1 credit, and each range request counts as one.
Adding search results to a hashtag
TikTok’s list for a hashtag and its search for the hashtag are different selections, and search leans towards recent posts. With includeSearch: true, /hashtag-videos also returns the posts a search for the hashtag finds that the list does not, after the list’s posts and marked source: "search" (the list’s are "list"). The same filters apply to them (knownIds or pollState, within, since, requireHashtag), limit counts both together, and incremental.bySource gives the counts per source.
How much it adds depends on the hashtag: in one check each search for #minecraft found about 20 posts from the last day that 8 requests for its list had not shown, while for a smaller hashtag such as #bookbinding it found none the list did not already have. It adds about 10 seconds, so use timeoutMs of 45000 or more: with less than about 12 seconds left after the list, the search is skipped and a note says so. It is not run when continuing with a cursor. Search posts are billed like list posts, as part of the posts delivered.
Paging and billing
limit caps the items returned; pages caps how many pages are fetched to get them, up to 20. TikTok serves at most 35 of an account's videos, 30 of a hashtag's, about 12 search results or 50 comments to a page, so a larger limit needs more pages. When more results exist, hasMore is true and nextCursor carries an opaque token to pass back unchanged as cursor, with the same request. A cursor continues only the route, and the account, hashtag, search term or video, it came from; anything else is a 400. Passing -1 on limit or pages means "the maximum this service allows", which is 500 videos, 500 comments and 20 pages.
Billing follows pages: one credit per page of results fetched, and every response states it as pagesFetched. A single-page request costs 1. /hashtag-videos is the exception: it bills by posts delivered, 1 credit per page of up to 30 posts delivered, minimum 1 credit per request (a poll delivering nothing new costs the base request charge), and every response states it as credits: the whole list of a hashtag (about 170 posts) is about 6, a poll with a few new posts 1–2. A repeated request and a continuation with a cursor are each billed at least 1. /resolve-url reports pagesFetched: 0 and is free.
Read the notes array
Every response carries notes, and an empty result always comes with one explaining it rather than as an unexplained empty array. The ones you will see: a list that stopped before the requested number was reached; paging that has gone as deep as it goes; a comment that has more replies than are listed; a private account; a video with comments turned off; and a genuinely empty account, hashtag or search.
A null counter means TikTok did not publish that figure, not zero — a real zero comes back as 0. The distinction is preserved everywhere, so a missing value is never reported as an absence of engagement.
Requests are strict
An unrecognised body field is a 400 naming it, not a silent no-op. A typo such as hashtagNam would otherwise be dropped and answered with the wrong data and no indication why. The same principle is why search takes no sort order and no date filter: TikTok offers a public caller neither, so they are rejected rather than accepted and ignored.
What this API does not return
- Follower and following lists. Not in this release.
- Full comment reply threads. Only the replies TikTok inlines with a comment;
replyCountreports the true total. - Sound and music pages, and the videos using a sound. Not in this release — a sound link still resolves through
/resolve-url. - Trending and discover feeds, and video transcripts or subtitles. Not in this release.
- Account analytics — audience demographics, watch time, traffic sources. TikTok publishes none of this for an account you do not own, so no service can return it for an arbitrary creator.
- Historical snapshots. Counts are as at the time of the request; there is no time series. Store what you get back if you want one.
- A date filter or sort on search. TikTok offers a public caller neither.
Advertising, separately
This API covers organic TikTok. For the paid side — the ads a brand is running, their creative and the advertisers behind them — see the TikTok Ad Library API, which is a separate platform on the same key.