Liveness probe — returns service name and server time.
curl -s "$BASE/api/health"
{
"status": "ok",
"app": "TikTokScope",
"time": 1760000000
}
A read-only JSON API for public TikTok and Douyin data — profiles, followers, videos, comments, hashtags, sounds, live status and keyword search. No API key, no third-party scraping vendor. Every route is served from the same origin as this page.
https://120319.xyz
— same origin, so all routes below are relative (/api/…).
Set BASE to the origin you are calling (the site that serves this page),
then hit any route. Responses are JSON unless noted otherwise; CSV exports stream as
text/csv attachments.
# All paths are relative to the serving origin.
export BASE="https://120319.xyz"
# 1. Liveness — cheapest way to verify the API is up.
curl -s "$BASE/api/health" | jq
# 2. A profile record.
curl -s "$BASE/api/profile/charlidamelio" | jq
# 3. The first page of that account's followers.
curl -s "$BASE/api/followers/charlidamelio?cursor=0&count=20" | jq '.users[0]'
# 4. A video plus its engagement stats.
curl -s "$BASE/api/video?url=https://www.tiktok.com/@charlidamelio/video/7300000000000000000" | jq
GET and read-only. Errors are returned as
{"detail": "…"} with HTTP 400 (bad input), 404 (not found),
502 (upstream scrape failure) or 500 (unexpected).
Liveness probe — returns service name and server time.
curl -s "$BASE/api/health"
{
"status": "ok",
"app": "TikTokScope",
"time": 1760000000
}
Service status with the running release and uptime.
curl -s "$BASE/api/status"
{
"status": "ok",
"app": "TikTokScope",
"version": "3.4.0",
"uptime_seconds": 128934,
"time": 1760000000
}
Readiness probe — verifies the SQLite cache and the Node signer used for signed search are usable.
curl -s "$BASE/api/ready"
{
"status": "ok",
"checks": {
"db": "ok",
"signer": "ok"
}
}
Full public profile record: identity, bio, verification and lifetime counters.
Path: username — handle with or without a leading @.
curl -s "$BASE/api/profile/charlidamelio"
{
"username": "charlidamelio",
"nickname": "charli d'amelio",
"user_id": "5831967",
"sec_uid": "MS4wLjABAAAA7Q8Yw2m9...",
"bio": "hi",
"verified": true,
"private": false,
"region": "US",
"avatar": "https://p16-sign.tiktokcdn-us.com/...",
"followers": 155400000,
"following": 1200,
"likes": 11500000000,
"videos": 2400
}
The public profile record plus derived per-video averages: likes_per_video, followers_per_video and following_ratio.
Path: username. No query parameters.
curl -s "$BASE/api/profile/charlidamelio/analytics"
{
"profile": {
"username": "charlidamelio",
"nickname": "charli d'amelio",
"user_id": "5831967",
"sec_uid": "MS4wLjABAAAA...",
"verified": true,
"private": false,
"followers": 160334731,
"following": 1449,
"likes": 12363645945,
"videos": 3293
},
"metrics": {
"username": "charlidamelio",
"followers": 160334731,
"following": 1449,
"likes": 12363645945,
"videos": 3293,
"likes_per_video": 3754523.5181,
"followers_per_video": 48689.563,
"following_ratio": 0.0
}
}
Resolve many profiles in one request; failures are reported per row rather than aborting the batch.
Query: users — comma-separated handles (e.g. a,b,c).
curl -s "$BASE/api/batch/profiles?users=charlidamelio,khaby.lame,ghost_user"
{
"count": 3,
"results": [
{
"username": "charlidamelio",
"nickname": "charli d'amelio",
"verified": true,
"followers": 155400000,
"following": 1200,
"likes": 11500000000,
"videos": 2400,
"error": null
},
{
"username": "khaby.lame",
"nickname": "Khabane lame",
"verified": true,
"followers": 162800000,
"following": 48,
"likes": 2400000000,
"videos": 1100,
"error": null
},
{
"username": "ghost_user",
"nickname": null,
"verified": false,
"followers": null,
"following": null,
"likes": null,
"videos": null,
"error": "No user data for @ghost_user"
}
]
}
TikTok's suggested/curated accounts (scene=151), resolved to live profile cards.
Query: count (1–30, default 30).
curl -s "$BASE/api/creators/top?count=20"
{
"items": [
{
"username": "khaby.lame",
"nickname": "Khabane lame",
"sec_uid": "MS4wLjABAAAA...",
"avatar": "https://p16-sign.tiktokcdn-us.com/...",
"verified": true
},
{
"username": "charlidamelio",
"nickname": "charli d'amelio",
"sec_uid": "MS4wLjABAAAA...",
"avatar": "https://p16-sign.tiktokcdn-us.com/...",
"verified": true
}
]
}
Keyword search is signed server-side with the vendored Node signer. Analytics and benchmarking are computed from cached video records using the metrics defined below.
General keyword search across videos and users. Videos are returned with derived metrics; non-video, non-user hits are counted in other_results.
Query: q (required), cursor (int, default 0), count (1–30, default 12).
curl -s "$BASE/api/search?q=cooking&count=12"
{
"query": "cooking",
"cursor": 12,
"has_more": true,
"videos": [
{
"id": "7300000000000000000",
"desc": "easy 15-minute pasta #foodtok",
"create_time": 1699000000,
"duration": 22,
"author": {
"username": "chefanna",
"nickname": "Anna Cooks",
"sec_uid": "MS4wLjABAAAA...",
"avatar": "https://p16-sign.tiktokcdn-us.com/...",
"verified": false
},
"stats": {
"plays": 820000,
"likes": 91000,
"comments": 1400,
"shares": 6200,
"collects": 12000,
"reposts": 300
},
"metrics": {
"engagement_rate": 0.1202,
"save_rate": 0.0146,
"share_rate": 0.0076,
"comment_rate": 0.0017,
"like_rate": 0.111,
"like_per_1k": 111.0,
"amplification": 0.0769,
"est_watch_proxy": 2.6444
},
"music": "original sound",
"music_id": "7000000000000000000",
"challenges": ["foodtok", "pasta"]
}
],
"users": [
{
"username": "chefanna",
"nickname": "Anna Cooks",
"sec_uid": "MS4wLjABAAAA...",
"signature": "recipes every day",
"avatar": "https://p16-sign.tiktokcdn-us.com/...",
"verified": false
}
],
"other_results": 1
}
Keyword search restricted to user accounts. Each hit is a lightweight profile card.
Query: q (required), cursor (int, default 0), count (1–30, default 12).
curl -s "$BASE/api/search/users?q=charli"
{
"query": "charli",
"cursor": 12,
"has_more": true,
"users": [
{
"username": "charlidamelio",
"nickname": "charli d'amelio",
"sec_uid": "MS4wLjABAAAA...",
"signature": "hi",
"avatar": "https://p16-sign.tiktokcdn-us.com/...",
"verified": true
}
]
}
Photo (image-post) search. Each result is a video record whose images array holds the slide URLs; derived metrics are attached as usual.
Query: q (required), cursor (int, default 0), count (1–30, default 12).
curl -s "$BASE/api/search/photos?q=sunset&count=12"
{
"query": "sunset",
"items": [
{
"id": "7300000000000000001",
"desc": "golden hour #sunset",
"create_time": 1699000000,
"duration": 0,
"author": {
"username": "skylover",
"nickname": "Sky Lover",
"sec_uid": "MS4wLjABAAAA...",
"verified": false
},
"stats": {
"plays": 410000,
"likes": 52000,
"comments": 610,
"shares": 2400,
"collects": 8800,
"reposts": 120
},
"metrics": {
"engagement_rate": 0.1342,
"save_rate": 0.0215,
"share_rate": 0.0059,
"comment_rate": 0.0015,
"like_rate": 0.1268,
"like_per_1k": 126.8,
"amplification": 0.2154,
"est_watch_proxy": null
},
"images": [
["https://p16-sign.tiktokcdn-us.com/...1.jpg"],
["https://p16-sign.tiktokcdn-us.com/...2.jpg"]
],
"challenges": ["sunset"]
}
],
"has_more": true,
"next_cursor": 12
}
Signed sound/music search. Each item describes a track and its total usage count. play_url is an object (or null) holding the audio URL.
Query: q (required), cursor (int, default 0), count (1–30, default 20).
curl -s "$BASE/api/search/sounds?q=original&count=2"
{
"query": "original",
"items": [
{
"id": "5000000001402792732",
"title": "Original",
"author": "Arcángel & Bad Bunny",
"album": "Ares",
"duration": 30,
"usage_count": 39135,
"play_url": {
"uri": "https://sf19.tiktokcdn-us.com/obj/...",
"url_list": ["https://sf19.tiktokcdn-us.com/obj/..."],
"width": 720,
"height": 720
},
"cover": "https://p16-common.tiktokcdn-us.com/..."
}
],
"cursor": 2,
"has_more": true
}
A creator's public video list, unsigned and paginated. Items carry derived metrics.
Path: username. Query: cursor (int, default 0), count (1–19, default 15).
curl -s "$BASE/api/profile/charlidamelio/videos?cursor=0&count=15"
{
"username": "charlidamelio",
"items": [
{
"id": "7300000000000000000",
"desc": "day in the life #fyp",
"create_time": 1699000000,
"duration": 15,
"author": {
"username": "charlidamelio",
"nickname": "charli d'amelio",
"sec_uid": "MS4wLjABAAAA..."
},
"stats": {
"plays": 2500000,
"likes": 340000,
"comments": 4200,
"shares": 18000,
"collects": 9600,
"reposts": 180
},
"metrics": {
"engagement_rate": 0.1449,
"save_rate": 0.0038,
"share_rate": 0.0072,
"comment_rate": 0.0017,
"like_rate": 0.136,
"like_per_1k": 136.0,
"amplification": 0.0812,
"est_watch_proxy": 2.1735
}
}
],
"has_more": true,
"next_cursor": 15
}
Videos a creator has reposted, newest first, in the same shape as the video list.
Path: username. Query: cursor (int, default 0), count (1–30, default 20).
curl -s "$BASE/api/profile/charlidamelio/reposts?count=20"
{
"username": "charlidamelio",
"items": [
{
"id": "7300000000000000009",
"desc": "this edit #fyp",
"create_time": 1698990000,
"duration": 11,
"author": {
"username": "editor.friend",
"nickname": "editor",
"sec_uid": "MS4wLjABAAAA..."
},
"stats": {
"plays": 540000,
"likes": 76000,
"comments": 900,
"shares": 4100,
"collects": 5200,
"reposts": 640
},
"metrics": {
"engagement_rate": 0.1519,
"save_rate": 0.0096,
"share_rate": 0.0076,
"comment_rate": 0.0017,
"like_rate": 0.1407,
"like_per_1k": 140.7,
"amplification": 0.1224,
"est_watch_proxy": 1.6709
}
}
],
"has_more": false,
"next_cursor": 20
}
Samples a creator's recent videos and groups their engagement rate by weekday/hour (UTC), plus a duration-bucket curve.
Path: username. Query: limit (4–20, default 12).
curl -s "$BASE/api/profile/charlidamelio/best-time?limit=12"
{
"username": "charlidamelio",
"sampled": 12,
"best_times": [
{ "weekday": 2, "hour": 18, "videos": 3, "avg_engagement_rate": 0.1421 },
{ "weekday": 5, "hour": 20, "videos": 2, "avg_engagement_rate": 0.1318 }
],
"duration_curve": [
{ "bucket": "0-15s", "videos": 4, "avg_engagement_rate": 0.1611 },
{ "bucket": "15-30s", "videos": 5, "avg_engagement_rate": 0.1204 },
{ "bucket": "30-60s", "videos": 3, "avg_engagement_rate": 0.0982 },
{ "bucket": "60s+", "videos": 0, "avg_engagement_rate": null }
]
}
Compare up to four creators side by side. Each creator's recent videos are sampled to produce comparable rows.
Query: users — comma-separated handles; videos (3–12, default 4) sampled per creator.
curl -s "$BASE/api/benchmark?users=charlidamelio,khaby.lame&videos=4"
{
"rows": [
{
"username": "khaby.lame",
"nickname": "Khabane lame",
"followers": 162800000,
"following": 48,
"likes_total": 2400000000,
"videos_total": 1100,
"sampled_videos": 4,
"avg_engagement_rate": 0.1564,
"avg_plays": 4200000,
"plays_per_follower": 0.0258,
"posts_last_30d": 2,
"avg_days_between_posts": 4.2
},
{
"username": "charlidamelio",
"nickname": "charli d'amelio",
"followers": 155400000,
"following": 1200,
"likes_total": 11500000000,
"videos_total": 2400,
"sampled_videos": 4,
"avg_engagement_rate": 0.121,
"avg_plays": 1800000,
"plays_per_follower": 0.0116,
"posts_last_30d": 3,
"avg_days_between_posts": 2.7
}
]
}
Ranked trending search terms for a region.
Query: region (two-letter code, default US).
curl -s "$BASE/api/tiktok/trending-searches?region=US"
{
"items": [
{ "rank": 1, "word": "fyp", "type": 1 },
{ "rank": 2, "word": "summer", "type": 1 },
{ "rank": 3, "word": "recipe", "type": 2 }
]
}
Native description and subtitle translation for a video.
Query: url or id (+ optional author), lang (default es).
curl -s "$BASE/api/video/transcript?url=https://www.tiktok.com/@charlidamelio/video/7300000000000000000&lang=es"
{
"id": "7300000000000000000",
"target": "es",
"description": "día en la vida #fyp",
"subtitle": "hola chicos\nbienvenidos de nuevo a mi canal"
}
One page of a user's follower list, with the profile header included.
Path: username. Query: cursor (int, default 0), count (1–50, default 20).
curl -s "$BASE/api/followers/charlidamelio?cursor=0&count=20"
{
"profile": {
"username": "charlidamelio",
"nickname": "charli d'amelio",
"sec_uid": "MS4wLjABAAAA7Q8Yw2m9...",
"followers": 155400000
},
"users": [
{
"user_id": "6901234567890123456",
"username": "somefan",
"nickname": "Some Fan",
"sec_uid": "MS4wLjABAAAA...",
"bio": "fan account",
"avatar": "https://p16-sign.tiktokcdn-us.com/...",
"verified": false,
"private": false,
"followers": 1234,
"following": 321,
"likes": 5678,
"videos": 42
}
],
"total": 155400000,
"has_more": true,
"next_cursor": 20
}
Stream the follower list as a downloadable CSV, paginating upstream automatically.
Path: username. Query: limit (1–5000, default 500).
curl -s "$BASE/api/followers/charlidamelio/export?limit=500" -o charli-followers.csv
user_id,username,nickname,sec_uid,bio,verified,private,followers,following,likes,videos
6901234567890123456,somefan,Some Fan,MS4wLjABAAAA...,fan account,False,False,1234,321,5678,42
6901234567890123457,anotherfan,Another Fan,MS4wLjABAAAA...,,False,True,88,120,301,7
...
Full video record: author, lifetime stats, media URLs, music, hashtags and subtitle tracks.
Query: url (canonical video URL) or id + author.
# By URL
curl -s "$BASE/api/video?url=https://www.tiktok.com/@charlidamelio/video/7300000000000000000"
# Or by bare id + author
curl -s "$BASE/api/video?id=7300000000000000000&author=charlidamelio"
{
"id": "7300000000000000000",
"desc": "day in the life #fyp",
"create_time": 1699000000,
"duration": 15,
"author": {
"username": "charlidamelio",
"nickname": "charli d'amelio",
"sec_uid": "MS4wLjABAAAA7Q8Yw2m9...",
"avatar": "https://p16-sign.tiktokcdn-us.com/...",
"verified": true
},
"stats": {
"plays": 2500000,
"likes": 340000,
"comments": 4200,
"shares": 18000,
"collects": 9600
},
"cover": "https://p16-sign.tiktokcdn-us.com/...",
"play_url": "https://v16-webapp.tiktokcdn.com/...mp4",
"download_url": "https://v16-webapp.tiktokcdn.com/...mp4",
"music": "original sound",
"music_id": "7000000000000000000",
"music_author": "charlidamelio",
"subtitles": [
{ "lang": "eng-US", "format": "webvtt", "url": "https://.../subtitle.vtt" }
],
"challenges": ["fyp", "dayinthelife"]
}
Clean anonymous video-detail fallback (useful when the main record is gated); includes subtitle URLs.
Query: url or id.
curl -s "$BASE/api/video/reflow?id=7300000000000000000"
{
"id": "7300000000000000000",
"desc": "day in the life #fyp",
"create_time": 1699000000,
"author": {
"username": "charlidamelio",
"nickname": "charli d'amelio",
"sec_uid": "MS4wLjABAAAA7Q8Yw2m9..."
},
"stats": {
"plays": 2500000,
"likes": 340000,
"comments": 4200,
"shares": 18000
},
"music": "original sound",
"music_id": "7000000000000000000",
"cover": null,
"play_url": null,
"download_url": null,
"subtitles": [
{ "lang": "eng-US", "format": "webvtt", "url": "https://.../subtitle.vtt" }
],
"challenges": ["fyp"]
}
Parsed caption lines for a video, as an array and as a single joined string.
Query: url or id (+ optional author), lang (default eng-US).
curl -s "$BASE/api/video/subtitles?url=https://www.tiktok.com/@charlidamelio/video/7300000000000000000&lang=eng-US"
{
"lines": [
"hey guys",
"welcome back to my channel",
"today we are doing something fun"
],
"text": "hey guys\nwelcome back to my channel\ntoday we are doing something fun"
}
Proxy a TikTok CDN media URL as an MP4 attachment. Non-TikTok hosts are refused.
Query: url — a tiktokcdn, tiktokv or tiktok.com media URL.
curl -s "$BASE/api/download?url=https://v16-webapp.tiktokcdn.com/...mp4" -o tiktok.mp4
HTTP/1.1 200 OK
content-type: video/mp4
content-disposition: attachment; filename="tiktok.mp4"
<binary mp4 bytes>
Recommended videos for a creator, served from the embed feed endpoint. Use sec_uid when you already have it, otherwise pass a username to resolve it first.
Query: username or sec_uid, cursor (int, default 0), count (1–50, default 20).
curl -s "$BASE/api/feed?username=charlidamelio&count=20"
{
"items": [
{
"id": "7300000000000000000",
"desc": "day in the life #fyp",
"author": "charlidamelio",
"plays": 2500000,
"likes": 340000,
"comments": 4200,
"cover": "https://p16-sign.tiktokcdn-us.com/..."
}
],
"has_more": true,
"next_cursor": 20
}
Enumerates the video quality ladder from the SSR page. Every gear URL is watermark-free; best_clean_url is the highest-resolution gear.
Query: url (canonical video URL) or id (with author).
curl -s "$BASE/api/video/qualities?id=7540092833810730270&author=charlidamelio"
{
"video_id": "7540092833810730270",
"gears": [
{
"gear": "adapt_lowest_1080_1",
"codec": "h265_hvc1",
"bitrate": 1558502,
"width": 1080,
"height": 1920,
"data_size": 20958736,
"url": "https://v16-webapp-prime.us.tiktok.com/video/tos/...",
"watermark": false
}
],
"best_clean_url": "https://v16-webapp-prime.us.tiktok.com/video/tos/...",
"play_addr": "https://v16-webapp-prime.us.tiktok.com/video/tos/...",
"download_addr": "https://v16-webapp-prime.us.tiktok.com/video/tos/..."
}
Playlist/mix metadata merged with a page of its videos. Find a mix_id in a video record's playlist_id field.
Path: mix_id. Query: cursor (int, default 0), count (1–30, default 10).
curl -s "$BASE/api/mix/7000000000000000000?count=12"
{
"id": "7000000000000000000",
"name": "my playlist",
"video_count": 24,
"cover": "https://p16-sign.tiktokcdn-us.com/...",
"creator": {
"username": "charlidamelio",
"nickname": "charli d'amelio",
"sec_uid": "MS4wLjABAAAA..."
},
"items": [
{
"id": "7300000000000000000",
"desc": "day in the life #fyp",
"author": { "username": "charlidamelio" },
"metrics": { "engagement_rate": 0.1635 }
}
],
"cursor": 12,
"has_more": true
}
Trending hashtags from the discover module, enriched with lifetime view/video counts and ranked by views.
Query: limit (1–20, default 10).
curl -s "$BASE/api/trending/hashtags?limit=5"
{
"items": [
{
"id": "7692240338664030239",
"title": "tiktokshopcollectiblesnycc",
"views": 2356453,
"videos": 148,
"rank": 1
}
],
"source": "discover+reflow"
}
Trending sounds from discover, enriched with usage counts from music detail and ranked. Fields such as author, duration, cover and audio_url appear only when enrichment succeeds.
Query: limit (1–20, default 10).
curl -s "$BASE/api/trending/sounds?limit=5"
{
"items": [
{
"id": "6740248251825391617",
"title": "Forever (From \"Euphoria: Season 1\" Soundtrack)",
"author": "Labrinth",
"duration": 60,
"cover": "https://p19-common.tiktokcdn-us.com/...",
"audio_url": "https://sf19.tiktokcdn-us.com/obj/...",
"usage_count": 3200000,
"rank": 1
}
],
"source": "discover+music_detail"
}
Hashtag/challenge stats: title, description and lifetime view/video counts.
Path: tag — with or without a leading #.
curl -s "$BASE/api/hashtag/fyp"
{
"id": "2284",
"title": "fyp",
"desc": "",
"views": 59800000000000,
"videos": 321000000
}
A page of top videos for a hashtag/challenge, with derived metrics.
Path: name — with or without a leading #. Query: cursor (int, default 0), count (1–50, default 20).
curl -s "$BASE/api/hashtag/fyp/videos?cursor=0&count=20"
{
"query": "fyp",
"videos": [
{
"id": "7300000000000000000",
"desc": "wait for it… #fyp",
"create_time": 1699000000,
"duration": 18,
"author": {
"username": "viraluser",
"nickname": "Viral User",
"sec_uid": "MS4wLjABAAAA..."
},
"stats": {
"plays": 3100000,
"likes": 480000,
"comments": 5200,
"shares": 26000,
"collects": 14000,
"reposts": 900
},
"metrics": {
"engagement_rate": 0.1649,
"save_rate": 0.0045,
"share_rate": 0.0084,
"comment_rate": 0.0017,
"like_rate": 0.1548,
"like_per_1k": 154.8,
"amplification": 0.0833,
"est_watch_proxy": 2.9682
}
}
],
"has_more": true,
"next_cursor": 20
}
Hashtags related to a given tag, each with an approximate video count.
Path: name — with or without a leading #.
curl -s "$BASE/api/hashtag/fyp/related"
{
"name": "fyp",
"related": [
{ "tag": "foryou", "count": 214000000 },
{ "tag": "viral", "count": 98000000 },
{ "tag": "foryoupage", "count": 98000000 }
]
}
Videos using a sound, paginated. Each item is a full video record.
Path: music_id. Query: cursor (int, default 0), count (1–50, default 20).
curl -s "$BASE/api/sound/7000000000000000000?cursor=0&count=20"
{
"items": [
{
"id": "7300000000000000000",
"desc": "day in the life #fyp",
"author": {
"username": "charlidamelio",
"nickname": "charli d'amelio"
},
"stats": {
"plays": 2500000,
"likes": 340000,
"comments": 4200,
"shares": 18000,
"collects": 9600
},
"music_id": "7000000000000000000"
}
],
"has_more": true,
"next_cursor": 20,
"total": 125000
}
Trending topics and hashtags surfaced by TikTok's discover module.
curl -s "$BASE/api/discover"
{
"items": [
{ "title": "#fyp", "link": "https://www.tiktok.com/tag/fyp" },
{ "title": "#summer", "link": "https://www.tiktok.com/tag/summer" },
{ "title": "#foodtok", "link": "https://www.tiktok.com/tag/foodtok" }
]
}
Douyin (抖音) hot-search leaderboard, ranked with hot values and labels.
curl -s "$BASE/api/douyin/trending"
{
"items": [
{
"rank": 1,
"word": "热歌推荐",
"hot_value": 12345678,
"label": 1,
"cover": "https://p3-sign.douyinpic.com/..."
},
{
"rank": 2,
"word": "夏日穿搭",
"hot_value": 9876543,
"label": null,
"cover": null
}
]
}
Douyin challenge (话题) detail with stats and a page of associated videos.
Path: ch_id. Query: cursor (int, default 0), count (1–50, default 20).
curl -s "$BASE/api/douyin/challenge/1234567890"
{
"id": "1234567890",
"title": "夏日穿搭",
"views": 42000000000,
"videos": 8600000,
"is_commerce": false,
"items": [
{
"id": "7300000000000000000",
"desc": "#夏日穿搭 今天穿这套",
"author": { "username": "dy_user", "nickname": "抖音用户" },
"stats": { "plays": 1200000, "likes": 98000, "comments": 1200, "shares": 5400 }
}
],
"has_more": true,
"next_cursor": 20
}
Douyin (抖音) creator profile by sec_uid, via the public iesdouyin web API.
Path: sec_uid — the Douyin security id.
curl -s "$BASE/api/douyin/profile/MS4wLjABAAAA..."
{
"sec_uid": "MS4wLjABAAAA...",
"username": "dy_user",
"nickname": "抖音用户",
"bio": "记录美好生活",
"avatar": "https://p3-sign.douyinpic.com/...",
"followers": 1234000,
"following": 321,
"likes": 8900000,
"videos": 210,
"verified": false
}
Live-status check for an account, with room metadata and stream URLs when live.
Path: username.
curl -s "$BASE/api/live/charlidamelio"
{
"username": "charlidamelio",
"is_live": true,
"room_id": "7300000000000000000",
"title": "come hang out 💕",
"viewer_count": 48210,
"like_count": 1204500,
"started_at": 1760000000,
"cover": "https://p16-sign.tiktokcdn-us.com/...",
"stream_urls": {
"flv": "https://pull-flv-l1.tiktokcdn.com/...",
"hls": "https://pull-hls-l1.tiktokcdn.com/....m3u8"
}
}
Keyword autocomplete suggestions for search/typeahead.
Query: q — the partial term.
curl -s "$BASE/api/suggest?q=charl"
{
"query": "charl",
"suggestions": [
"charlidamelio",
"charlie puth",
"charlotte tilbury",
"charlie brown"
]
}
Standard oEmbed metadata for a video URL, suitable for link unfurling.
Query: url — the canonical video URL.
curl -s "$BASE/api/oembed?url=https://www.tiktok.com/@charlidamelio/video/7300000000000000000"
{
"version": "1.0",
"type": "video",
"title": "day in the life #fyp",
"author_name": "charli d'amelio",
"author_url": "https://www.tiktok.com/@charlidamelio",
"width": 325,
"height": 570,
"html": "<blockquote class=\"tiktok-embed\" data-video-id=\"7300000000000000000\">…</blockquote>",
"thumbnail_width": 720,
"thumbnail_height": 1280,
"thumbnail_url": "https://p16-sign.tiktokcdn-us.com/..."
}
API keys are optional: anonymous requests work on every endpoint.
These management routes are disabled unless the server sets ADMIN_TOKEN;
while it is unset they return 503 {"detail":"Admin API disabled"}. When enabled,
send the token in the X-Admin-Token header. A new key is shown in full once at
creation — only a hash and a display prefix are stored.
Mint a new API key. The plaintext key is returned once and cannot be retrieved again.
Header: X-Admin-Token. JSON body: label, tier (default free), daily_quota (default 2000).
curl -s -X POST "$BASE/api/keys" \
-H "X-Admin-Token: $ADMIN_TOKEN" -H "Content-Type: application/json" \
-d '{"label":"my app","tier":"free","daily_quota":2000}'
{
"id": 1,
"key": "tks_live_...",
"prefix": "tks_live_...",
"label": "my app",
"tier": "free",
"daily_quota": 2000,
"created_at": 1760000000.12
}
List all keys (never the secret) with today's usage and revocation state.
Header: X-Admin-Token.
curl -s "$BASE/api/keys" -H "X-Admin-Token: $ADMIN_TOKEN"
{
"keys": [
{
"id": 1,
"prefix": "tks_live_...",
"label": "my app",
"tier": "free",
"daily_quota": 2000,
"used_today": 12,
"remaining_today": 1988,
"revoked": false
}
]
}
Revoke a key by id. Revocation is a timestamp, not a delete, and is idempotent.
Header: X-Admin-Token. Path: key_id.
curl -s -X DELETE "$BASE/api/keys/1" -H "X-Admin-Token: $ADMIN_TOKEN"
{ "revoked": 1 }
Per-day usage rows across keys, optionally scoped to a single key.
Header: X-Admin-Token. Query: key_id (optional int).
curl -s "$BASE/api/keys/usage?key_id=1" -H "X-Admin-Token: $ADMIN_TOKEN"
{
"usage": [
{ "key_id": 1, "prefix": "tks_live_...", "label": "my app", "day": "2026-10-13", "count": 12 }
]
}
Added in the current release: direct media/audio, PDF+CSV reports, embeddable cards, Douyin topic videos and deep live rooms.
Direct media URLs from the reflow endpoint: video play/download and the sound audio track.
Query: url or id.
curl -s "$BASE/api/video/media?id=7540092833810730270"
{
"id": "7540092833810730270",
"play_url": "https://v16-webapp.../video/tos/...",
"download_url": "https://v16-webapp.../video/tos/...",
"music": "original sound",
"music_url": "https://...",
"music_duration": 24,
"subtitles": [{ "lang": "eng-US", "format": "webvtt", "url": "..." }]
}
Download a one-page report for a creator. format=csv (default) or format=pdf.
curl -s -o alice.pdf "$BASE/api/report/profile/alice?format=pdf"
Download a report for a video (stats + engagement metrics).
curl -s -o video.csv "$BASE/api/report/video?url=https://www.tiktok.com/@user/video/123&format=csv"
oEmbed-style profile card for embedding. A ready-made widget also lives at /embed?user=username.
{ "type": "rich", "provider_name": "TikTokScope", "title": "@alice on TikTok", "thumbnail_url": "...", "html": "<blockquote…>" }
Videos for a Douyin hot topic. Query: word (use a hot-search word), offset, count.
curl -s "$BASE/api/douyin/topic-videos?word=%E7%83%AD%E9%97%A8&count=12"
Deep live-room info with FLV / HLS / RTMP pull URLs, viewer count and cover.
{ "room_id": "...", "is_live": true, "viewers": 1617, "streams": { "flv": {...}, "hls": "...m3u8", "rtmp": "rtmp://..." } }
These derived values are computed from a video's stats (and its
duration), and the comment sentiment from the text returned by
/api/video/comments. They appear in the analytics payload and
are useful for comparing videos of different sizes.
| Metric | Formula | Meaning | Range |
|---|---|---|---|
engagement_rate |
(likes + comments + shares) / plays |
Share of views that produced an interaction. | 0–1+ (typ. 0.01–0.20) |
save_rate |
collects / plays |
How often a view leads to a bookmark — intent to revisit. | 0–1 |
share_rate |
shares / plays |
How often a view leads to a share — viral pull. | 0–1 |
comment_rate |
comments / plays |
Conversation intensity per view. | 0–1 |
like_per_1k |
(likes / plays) × 1000 |
Likes per 1,000 views — size-independent comparison. | 0–1000 |
amplification |
(shares + collects) / likes |
Extra distribution per like — how much the audience spreads and saves it. | 0–∞ (typ. 0.01–0.2) |
est_watch_proxy |
engagement_rate × duration |
Engagement rate scaled by video length (seconds) — a relative watch-time signal. | seconds (relative; null without duration) |
sentiment |
lexicon polarity over comment text | Tone of the comment section; returns a label and a score. |
score −1 to 1 |
null — never an invented number — while a missing
numerator counts as zero. Rounded to 4 decimals.
600s, followers 300s, video/reflow 1800s, comments 600s, feed/sound/trending/discover 600s, suggestions 3600s.4 concurrent requests, with a random 0.8–2.0s delay between calls. Exports page at 50 rows per request and sleep 1s between pages.count from 1 to 50 (default 20). Exports accept limit up to 5000 (default 500).400; an upstream scrape failure returns 502 with {"detail": "…"}. Empty upstream payloads usually mean throttling — back off and retry.
Comments
One page of top-level comments on a video, newest first, with cursor paging.
Query:
urlorid,cursor(int, default 0),count(1–50, default 20).Stream all comments for a video as CSV, paging upstream until the limit is reached.
Query:
urlorid,limit(1–5000, default 500).Replies to a specific comment, in the same shape as the top-level comment list.
Query:
item_id(video id),comment_id,cursor,count(1–50).Fetch the top comments on a video and batch-translate them. Each comment gains a
text_translatedfield; the originaltextis preserved.Query:
urlorid(+ optionalauthor),target(language code, defaulten),limit(1–40, default 20).