Getting Started
Read public TikTok accounts, videos, hashtags, search results and comments as structured JSON. No TikTok account and no TikTok developer application — just your FetchLayer key.
1. Get your API key
Create an account and generate a FetchLayer API key from your dashboard. The same key works on every platform.
Create free account →2. Read an account
Pass a handle with or without the @, or a profile URL. The counts come back exact rather than rounded.
curl -X POST https://api.fetchlayer.dev/tiktok/user-profile \
-H "Authorization: Bearer ss-your-key" \
-H "Content-Type: application/json" \
-d '{"username":"@nasa"}'Exact counts, not rounded ones
1719888 rather than 1.7M — which is the difference between being able to track week-to-week movement and not. 3. List what they posted
The account's profile comes back with the videos, so listing an account's posts does not also need a profile call. Sort by latest, popular or oldest.
curl -X POST https://api.fetchlayer.dev/tiktok/user-videos \
-H "Authorization: Bearer ss-your-key" \
-H "Content-Type: application/json" \
-d '{"username":"@nasa","limit":70,"pages":2,"sort":"popular"}'Page with the cursor, not the offset
hasMore is true and nextCursor carries an opaque value. Pass it back as cursor to continue. pages collects several pages inside one request instead, up to 20 — each page fetched is one credit, and the response reports how many as pagesFetched. An account's videos come at most 35 to a page, so the call above asks for two. A cursor only continues the route and account it came from; anything else is a 400. A 503 on this route comes only after repeated attempts, so wait before trying again rather than retrying immediately. A private account returns no videos, with the note “This account is private, so its posts are not public.”
4. Search, or follow a hashtag
Search returns TikTok's own ranking for the term. A hashtag listing also returns the tag itself, with its total view count across TikTok — useful for sizing a trend before deciding how much of it to read.
curl -X POST https://api.fetchlayer.dev/tiktok/search-videos \
-H "Authorization: Bearer ss-your-key" \
-H "Content-Type: application/json" \
-d '{"query":"space launch","limit":24,"pages":2}'
curl -X POST https://api.fetchlayer.dev/tiktok/hashtag-videos \
-H "Authorization: Bearer ss-your-key" \
-H "Content-Type: application/json" \
-d '{"hashtag":"nasa","limit":30}'Search takes no sort and no date filter
5. Read the comments
By video id or URL. Each comment carries its text, timestamp, like and reply counts, whether it is pinned, and whether the video's author wrote or liked it.
curl -X POST https://api.fetchlayer.dev/tiktok/comments \
-H "Authorization: Bearer ss-your-key" \
-H "Content-Type: application/json" \
-d '{"video":"https://www.tiktok.com/@nasa/video/7686148096895569165","limit":100,"pages":2}'replies is not the whole thread
replies holds only the replies TikTok shows alongside a comment — usually none or a handful. replyCount reports how many exist in total, and a response where the two differ says so in notes. Separately, totalCount is the figure TikTok publishes for the video, and it can exceed what paging can reach. 6. Video files, images and covers
TikTok's asset URLs are signed and expire within hours, so every asset carries a downloadUrl pointing at GET /tiktok/media, which delivers it on demand with Range support. A video's downloadUrl gives the MP4 without the TikTok watermark; an image's gives the image.
curl -L "https://api.fetchlayer.dev/tiktok/media?video=7686148096895569165" \
-H "Authorization: Bearer ss-your-key" \
--output 7686148096895569165.mp4Downloads, and what they cost
downloadUrl is null and /media answers 403, “The creator has turned off downloads for this video.” A photo post or a video that is not public is a 404, and an expired image is a 404, “that media is no longer available on TikTok; read the record again for a fresh link”. Each request to /media costs 1 credit, and each range request counts as one. Resolving a pasted link
When a user hands you a TikTok URL and you do not know what it points at, /resolve-url tells you — video, account, hashtag, sound or search — plus the id, handle, tag or query it encodes. It is free.
curl -X POST https://api.fetchlayer.dev/tiktok/resolve-url \
-H "Authorization: Bearer ss-your-key" \
-H "Content-Type: application/json" \
-d '{"url":"https://www.tiktok.com/@nasa/video/7686148096895569165"}'A short share link (vm.tiktok.com, vt.tiktok.com, tiktok.com/t/…) does not say what it points to, so it comes back as unknown with a note saying to open it and pass the full URL. The routes that take a video reject a short link for the same reason.
Errors and retries
A 400 names the field to fix; unrecognised fields are rejected rather than ignored. A 404 means there is no such public account, video or hashtag. A 503 comes only after repeated attempts, so retrying immediately is unlikely to help — wait first. A 5xx carries a generic message, over the REST API and MCP alike — "The request could not be completed. Retry shortly.", or "Something went wrong on our side. Retry shortly." for internal_error — except the 503 for your own timeoutMs, which names it. Branch on code and retryable, not on the text. Read notes on successful responses too: a private account, comments turned off, and a list that ran out early all announce themselves there rather than as an error.
Error responses
Every error, on every route, is JSON in one shape: error (what went wrong, in words), status, code, retryable and requestId, which also comes as the X-Request-Id header. Branch on code and retryable rather than on the text; on a 5xx the text is generic. Quote requestId when reporting a problem.
{
"error": "Missing required parameter: url",
"status": 400,
"missing": ["url"],
"code": "invalid_input",
"retryable": false,
"requestId": "922874e3-c0d0-40d8-8866-d198af132420"
}| code | Status | Retryable | Meaning |
|---|---|---|---|
| invalid_input | 400 | no | The request needs fixing; error names the field. A missing required parameter also lists it in missing. |
| unauthorized | 401 | no | The API key is missing or not valid. |
| insufficient_credits | 402 | no | Nothing left to spend: no credits, a used-up free trial, or a monthly allowance and credit balance both spent. error says which. Waiting does not help; topping up does. |
| forbidden | 403 | no | This key or account is not allowed this request. |
| not_found | 404 | no | What the request names does not exist or is not public. |
| expired | 410 | no | What the request refers to is no longer available; ask for it afresh. |
| quota_exceeded | 429 | no | A request-rate limit was reached; error says which. Slow down before sending more. |
| cancelled | 499 | no | The caller closed or cancelled the request before it finished. The work was stopped and nothing was charged. |
| time_limit | 503 | yes | Your timeoutMs ran out before there was anything to return; ask again, or allow more time. |
| temporarily_unavailable | 503 | yes | Not available right now; ask again shortly. |
| posts_unavailable | 403 | no | TikTok user videos: TikTok is not showing this account’s posts publicly right now. Asking again at once returns the same answer; nothing is charged. |
| store_unreachable | 503 | yes | Store routes (Shopify, WooCommerce): the store itself can’t be reached right now — it may be offline. Ask again later. Where a store route doesn’t say so yet, an offline store is temporarily_unavailable. |
| upstream_error | 502 | yes | The source answered in a way that could not be used; ask again later. |
| internal_error | 500 | yes | Something failed on our side; ask again later, and quote requestId if it persists. |
| tool_error | — | no | MCP only: a tool call failed in a way no other code describes; error says what. |
More codes may be added; treat one you do not recognise by its retryable flag. Over MCP, a tool error carries the same fields in structuredContent, and as a JSON text block beside the message.