Getting Started
Read public Douyin videos, accounts, comments, collections and the hot-search board as structured JSON. No Douyin account and no 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. See what is trending
The hot-search board needs no input: every entry with its rank, heat score and view and video counts.
curl -X POST https://api.fetchlayer.dev/douyin/trending \
-H "Authorization: Bearer ss-your-key" \
-H "Content-Type: application/json" \
-d '{"limit":20}'3. Read a video
By id, link, or the share text someone pasted. Likes, comments, shares and saves come back exact.
curl -X POST https://api.fetchlayer.dev/douyin/video-detail \
-H "Authorization: Bearer ss-your-key" \
-H "Content-Type: application/json" \
-d '{"video":"https://www.douyin.com/video/7665908051974704410"}'No play counts
stats.plays is always null. A collection's total plays is published, on /collection-videos. 4. Read an account
By sec_uid, profile link or share link. /user-profile gives the exact follower count; /user-videos its most recent videos.
curl -X POST https://api.fetchlayer.dev/douyin/user-videos \
-H "Authorization: Bearer ss-your-key" \
-H "Content-Type: application/json" \
-d '{"user":"https://www.douyin.com/user/MS4wLjABAAAAYOEVkUXnngwqKUvaFEGdZWxDxUD6cEbk7zYTJGMrqPA"}'About 41 recent videos
olderVideosExist is true. Videos that belong to a collection can be read in full with /collection-videos, starting from any video's collection.id. 5. Read the comments
Top-level comments in Douyin's ranking, 50 to a credit, each with exact likes, its reply count and the province it was posted from. Read the replies under a comment with /comment-replies, passing its id as commentId.
curl -X POST https://api.fetchlayer.dev/douyin/video-comments \
-H "Authorization: Bearer ss-your-key" \
-H "Content-Type: application/json" \
-d '{"video":"7665908051974704410","limit":200}'Continue with cursor
hasMore is true, pass the response's cursor back with the same request to continue. Douyin's ranking can shift slightly between calls, so a continuation may differ a little from one long read. Resolving a pasted link
/resolve-url tells you whether a link or share text is a video, an account, a hashtag or a collection, with its id. It is free.
curl -X POST https://api.fetchlayer.dev/douyin/resolve-url \
-H "Authorization: Bearer ss-your-key" \
-H "Content-Type: application/json" \
-d '{"url":"https://www.douyin.com/video/7665908051974704410"}'Errors and retries
A 400 names the field to fix. A 404 means the video, account, hashtag or collection does not exist or is not public. A 503 is worth retrying; when it is your own timeoutMs running out, it carries X-Timeout-Source: caller. A 502 means Douyin's answer could not be read; retry later.
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. |
| 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.