Douyin API — Getting Started — FetchLayer Docs
Documentation menu
FetchLayer FetchLayer API

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.

bash
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.

bash
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

Douyin does not publish how many times a video was played, so 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.

bash
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

An account's list holds up to about 41 of its most recent videos, in one response with no cursor. When older ones exist, 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.

bash
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

When 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.

bash
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.

json
{
  "error": "Missing required parameter: url",
  "status": 400,
  "missing": ["url"],
  "code": "invalid_input",
  "retryable": false,
  "requestId": "922874e3-c0d0-40d8-8866-d198af132420"
}
codeStatusRetryableMeaning
invalid_input400noThe request needs fixing; error names the field. A missing required parameter also lists it in missing.
unauthorized401noThe API key is missing or not valid.
insufficient_credits402noNothing 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.
forbidden403noThis key or account is not allowed this request.
not_found404noWhat the request names does not exist or is not public.
expired410noWhat the request refers to is no longer available; ask for it afresh.
quota_exceeded429noA request-rate limit was reached; error says which. Slow down before sending more.
cancelled499noThe caller closed or cancelled the request before it finished. The work was stopped and nothing was charged.
time_limit503yesYour timeoutMs ran out before there was anything to return; ask again, or allow more time.
temporarily_unavailable503yesNot available right now; ask again shortly.
store_unreachable503yesStore 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_error502yesThe source answered in a way that could not be used; ask again later.
internal_error500yesSomething failed on our side; ask again later, and quote requestId if it persists.
tool_error—noMCP 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.

Next steps