Developer Documentation

Welcome to TikTool Docs

Everything you need to connect to any TikTok LIVE stream - real-time WebSocket events, REST endpoints, and SDKs for Node.js, Python, Go, Java, C# and Rust. Start on the free Sandbox tier, no credit card.

Quick Start

Connect to any TikTok LIVE stream and receive real-time events in under 5 lines of code. Demo key your_api_key works without signup.

Get running in 30sPick a language. Copy. Paste. Run.
One-Click Install
npm install @tiktool/live
Quick Start - Node.js
Node.js
Python
cURL
Java
Go
C#
import WebSocket from 'ws';

const ws = new WebSocket('wss://api.tik.tools?uniqueId=streamer_name&apiKey=YOUR_KEY');

ws.on('message', (data) => {
  const event = JSON.parse(data);
  if (event.event === 'chat') {
    const lvl = event.data.user.level ?? event.data.user.payGrade ?? 0;
    console.log(`${event.data.user.uniqueId} lv${lvl}: ${event.data.comment}`);
  }
});
// Raw WS exposes the gifter/donator level as user.level.
// The @tiktool/live npm SDK exposes the same value as user.payGrade.
// Either present on every chat / gift / like / follow / member / share.
Get your API key

Sign up at tik.tools/pricing to get a free API key. No credit card required.

Try It Now - Live Demo

Copy-paste this script, run it, and see real-time TikTok LIVE events in your terminal. Works on the free Sandbox tier - no credit card needed.

Zero setup required

Replace YOUR_API_KEY with your free key from tik.tools and LIVE_USERNAME with any currently live TikTok user. Runs on the free Sandbox tier - no time limit.

Try It Now - Node.js
Node.js
Python
cURL
Java
Go
C#
// demo.mjs - TikTok LIVE in 5 minutes
// npm install ws
import WebSocket from 'ws';

const API_KEY       = 'YOUR_API_KEY';        // Get free key → https://tik.tools
const LIVE_USERNAME = 'tv_asahi_news';       // Any live TikTok username

const ws = new WebSocket(wss://api.tik.tools?uniqueId=${LIVE_USERNAME}&apiKey=${API_KEY}`);
let events = 0;

ws.on('open', () => console.log(`\n✅ Connected to @${LIVE_USERNAME} - listening for 5 min...\n`));
ws.on('message', (raw) => {
  const msg = JSON.parse(raw);
  events++;
  const d = msg.data || {};
  const user = d.user?.uniqueId || '';
  // d.user.payGrade = sender's TikTok gifter/donator level (1-50).
  // Exposed verbatim from proto on every user-bearing event.
  const lvl = (d.user?.payGrade ?? d.user?.level) ? ` lv${d.user.payGrade ?? d.user.level}` : '';
  switch (msg.event) {
    case 'chat':        console.log(`💬 ${user}${lvl}: ${d.comment}`); break;
    case 'gift':        console.log(`🎁 ${user}${lvl} sent ${d.giftName} (${d.diamondCount}💎)`); break;
    case 'like':        console.log(`❤️  ${user}${lvl} liked × ${d.likeCount}`); break;
    case 'member':      console.log(`👋 ${user}${lvl} joined`); break;
    case 'roomUserSeq': console.log(`👀 Viewers: ${d.viewerCount}`); break;
    case 'roomInfo':    console.log(`📡 Room: ${msg.roomId}`); break;
    default:            console.log(`📦 ${msg.event}`); break;
  }
});
ws.on('close', () => console.log(`\n📊 Done! Received ${events} events.\n`));

setTimeout(() => ws.close(), );

Node.js SDK

The @tiktool/live npm package provides a high-level client and utility functions for working with TikTok LIVE data.

Install
npm install @tiktool/live
Live example

Stream chat + gifts in 20 lines

Copy a runnable file in Node, Python, HTML, or as an LLM prompt - all wired to the same public ticker + JWT mint.

live-events.htmlRUNNING
Preview loads once visible.

Connection Modes

Two ways to receive TikTok LIVE events. Pick based on scale and whether you want TikTok to see your IP.

Mode 1 - Direct (default, via @tiktool/live SDK)

Your app handshakes with the TikTools sign server, then opens a WebSocket from your runtime to TikTok using the signed URL. The event stream never traverses our infrastructure.

  • Best for low- to mid-volume workloads from a single host.
  • Costws_credentials request per (re)connect on your API key.
  • Egress uses your host's default network interface. If your environment already routes outbound traffic through a corporate gateway, set the optional proxy option.
direct-mode.ts
import { TikTokLive } from '@tiktool/live';

const live = new TikTokLive({
    uniqueId: 'creator_username',
    apiKey: 'YOUR_TIKTOOLS_KEY',
    // OPTIONAL: route outbound WS + HTTP through your corporate / egress gateway.
    // Requires: npm install https-proxy-agent
    // proxy: 'http://USER:PASS@gateway.your-company.com:port',
});

live.on('chat', e => console.log(`${e.user.uniqueId} [lv${e.user.payGrade ?? 0}]: ${e.comment}`));
live.on('gift', e => console.log(`${e.user.uniqueId} [lv${e.user.payGrade ?? 0}] sent ${e.giftName} (${e.diamondCount} diamonds)`));

// e.user.payGrade exposes the sender's TikTok gifter / donator level
// (1-50, higher = more coins spent platform-wide). Present on every
// user-bearing event: chat, gift, like, follow, member, share.

await live.connect();

Mode 2 - Relayed (via TikTools managed edge)

The SDK connects to wss://api.tik.tools/.... The TikTools service handles the upstream TikTok session and forwards decoded events to your client over a single WebSocket. Your application talks only to api.tik.tools.

  • Best for production at scale, multi-tenant deployments, environments that centralize outbound traffic.
  • Cost 1× request per event forwarded (chat / gift / battle / etc.).
  • Egress single, stable endpoint (api.tik.tools).

Easiest - same SDK, one option flip (since v2.10.0):

relayed-mode.ts
import { TikTokLive } from '@tiktool/live';

const live = new TikTokLive({
    uniqueId: 'creator_username',
    apiKey: 'YOUR_KEY',
    mode: 'relayed',   // ← only change vs Direct mode
});

live.on('chat', e => console.log(`${e.user.uniqueId}: ${e.comment}`));
live.on('gift', e => console.log(`${e.user.uniqueId} sent ${e.giftName}`));
live.on('battleArmies', e => console.log(e));

await live.connect();

Advanced - raw WebSocket (no SDK) for non-Node runtimes (Python, Go, Bun, browser):

relayed-raw.ts
import WebSocket from 'ws';

const ws = new WebSocket(
    `wss://api.tik.tools/?uniqueId=creator_username&apiKey=YOUR_KEY`
);

ws.on('message', raw => {
    const msg = JSON.parse(raw.toString());
    // user.level is the raw-WS name for the same value the SDK exposes as
    // user.payGrade. Both come from proto badge {category=20, sub=8}.
    const lvl = msg.data.user.level ?? msg.data.user.payGrade ?? 0;
    if (msg.event === 'chat')         console.log(msg.data.user.uniqueId, `lv${lvl}`, msg.data.comment);
    if (msg.event === 'gift')         console.log(msg.data.user.uniqueId, `lv${lvl}`, msg.data.giftName, msg.data.diamondCount);
    if (msg.event === 'battleArmies') console.log(msg.data);
});

Event payloads are identical to Direct mode. Toggle modes by changing one line - your event handlers stay the same.

User object shape

Every user-bearing event (chat, gift, like, follow, member, share) carries the same user object. Fields surfaced verbatim from the TikTok proto:

FieldTypeDescription
idstringTikTok numeric user id.
uniqueIdstringPublic @handle (without the @).
nicknamestringDisplay name.
profilePictureUrlstringAvatar URL (TikTok CDN).
secUidstring?TikTok's permanent secure user id. Always starts with MS4wLjAB (e.g. MS4wLjABAAAARsyn...). Survives a user changing their uniqueId handle, so it's the canonical key for long-term gifter identity across rename events. Empty when TikTok ships a lightweight User payload (rare; ~28% of gifts in production today carry it). Basic+ tier required - free / sandbox keys receive an upgrade-hint string. Also persisted on every gift in our ClickHouse store as gifter_sec_uid.
isAnonymousboolean?True when TikTok flags the gifter as anonymous (their "Enigma" feature - paid gifts where the gifter hides identity). Empirically, anonymous gifts surface with nickname = "Enigma XXXXX" (5-char random session suffix, e.g. "Enigma TUYUL", "Enigma 91124") - the suffix changes per session, NOT per user. Use this flag (not nickname-matching) to detect anonymity. A small fraction (~0.6% in production) of Enigma gifts STILL leak the real secUid - if present, you can resolve the actual sender despite the masked nickname.
badgesArray<{url, name, colors?}>Active badges with full visual metadata. Each badge has:
url (string) - PNG icon URL on TikTok CDN;
name (string) - the label TikTok renders inside the gem (numeric like "30" for grade gems, text like "BEST" / "AURA" for fans badges, empty for subscriber / moderator icons);
colors.primary (string?) - 8-char alpha-prefix ARGB hex (e.g. "#BF2A19EE") - the main gem fill color, ready to drop into CSS;
colors.secondary (string?) - second ARGB hex when TikTok ships a gradient (e.g. "#FFFFBF83"). Use both to render the gem's gradient fill. Extracted from the proto CombineBadge color struct at field 11 / 12.
payGrade / levelnumber?Gifter / donator level (1-50). Higher = more coins spent platform-wide. Extracted from the proto badge category 20 / sub-category 8, priv field 12 -> level string field 5. The @tiktool/live npm SDK exposes it as user.payGrade; the raw managed-edge WebSocket (wss://api.tik.tools/?...) exposes the same value as user.level. Renders as the small tier-colored badge on user avatars in the Battles.app dashboard. Absent when the user has never gifted. Verified live values: e.g. rongnomber1 = 40, sagarshrestha500 = 22.

Side-by-side comparison

AspectDirect (Mode 1)Relayed (Mode 2)
WebSocket opens fromYour runtimeTikTools managed edge
Setupnew TikTokLive(...)new TikTokLive({ ..., mode: 'relayed' })
EgressYour host's networkSingle endpoint: api.tik.tools
Recommended forLow- to mid-volumeProduction scale, multi-tenant

Python SDK

The tiktok-live-api PyPI package provides a high-level Python client for TikTok LIVE streams - sync and async support, decorator-based event handling, and built-in live captions.

Install
pip install tiktok-live-api
PyPI ->Python 3.8+Async SupportLive Captions
Live example

Same flow, idiomatic Python

Pick Python from the dropdown - zero deps beyond pip install websockets. The browser preview on the right runs the HTML variant of the same code.

live-events.htmlRUNNING
Preview loads once visible.

Basic Example

basic.py - Python
from tiktok_live_api import TikTokLive

client = TikTokLive("streamer_username")

@client.on("connected")
def on_connected(event):
    print(f"Connected to @{event['uniqueId']}")

@client.on("chat")
def on_chat(event):
    lvl = event['user'].get('payGrade', 0)
    print(f"[chat] {event['user']['uniqueId']} lv{lvl}: {event['comment']}")

@client.on("gift")
def on_gift(event):
    diamonds = event.get("diamondCount", 0)
    lvl = event['user'].get('payGrade', 0)
    print(f"[gift] {event['user']['uniqueId']} lv{lvl} sent {event['giftName']} ({diamonds} diamonds)")
# event['user']['payGrade'] = senders gifter/donator level (1-50). On every event.

@client.on("like")
def on_like(event):
    print(f"[like] {event['user']['uniqueId']} liked (total: {event['totalLikes']})")

@client.on("follow")
def on_follow(event):
    print(f"[follow] {event['user']['uniqueId']} followed")

@client.on("roomUserSeq")
def on_viewers(event):
    print(f"[viewers] {event['viewerCount']} watching")

client.run()

Live Captions Example

captions.py - Python
from tiktok_live_api import TikTokCaptions

captions = TikTokCaptions(
    "streamer_username",
    translate="en",
    diarization=True,
)

@captions.on("connected")
def on_connected(event):
    print(f"Listening to @{event['uniqueId']}")

@captions.on("caption")
def on_caption(event):
    speaker = event.get("speaker", "")
    text = event["text"]
    is_final = event.get("isFinal", False)
    status = "FINAL" if is_final else "partial"
    print(f"[{status}] [{speaker}] {text}")

@captions.on("translation")
def on_translation(event):
    print(f"  -> {event['text']}")

captions.run()
TikTokLive

The main SDK class for connecting to TikTok LIVE streams via WebSocket. Handles authentication, reconnection, and event parsing automatically.

new TikTokLive({ uniqueId, apiKey, ... })

Parameters

NameTypeDescription
uniqueIdstringTikTok username (without @)
apiKeystringYour TikTool API key
sessionIdstring?Optional TikTok session ID for authenticated features

Returns

TikTokLive instance

Example

TikTokLive - Node.js
import { TikTokLive } from '@tiktool/live';

const client = new TikTokLive({
  uniqueId: 'streamer_name',
  apiKey: process.env.TIKTOOL_API_KEY
});

client.on('chat', (event) => {
  console.log(`${event.nickname}: ${event.comment}`);
});

client.on('gift', (event) => {
  console.log(`${event.nickname} sent ${event.giftName} x${event.repeatCount}`);
});

await client.connect();
// client.disconnect() to close
callApi

High-level utility that handles the full sign-and-return API flow automatically: resolves room ID → calls API → fetches signed URL → returns parsed TikTok data. Ideal for server-side integrations.

callApi(options: CallApiOptions): Promise<any>

Parameters

NameTypeDescription
apiKeystringYour TikTool API key
endpointstringAPI endpoint path (e.g. '/webcast/room_info')
uniqueIdstringTikTok username to query
method'GET'|'POST'HTTP method (default: POST)
serverUrlstring?API server URL (default: https://api.tik.tools)
extraBodyRecord?Additional body fields for POST requests

Returns

Parsed TikTok data object or null if user is not live

Example

callApi - Node.js
import { callApi } from '@tiktool/live';

// Get room info
const roomInfo = await callApi({
  apiKey: 'YOUR_KEY',
  endpoint: '/webcast/room_info',
  uniqueId: 'streamer_name'
});
console.log(roomInfo?.data?.title);

// Get stream video URLs
const video = await callApi({
  apiKey: 'YOUR_KEY',
  endpoint: '/webcast/room_video',
  uniqueId: 'streamer_name'
});
console.log(video?.data?.stream_urls?.origin?.hls);
resolveLivePage

Retrieves metadata from a TikTok live page including the room ID, session cookie (ttwid), and cluster region. Results are cached for 5 minutes. Used internally by TikTokLive.connect().

resolveLivePage(uniqueId: string): Promise<LivePageInfo | null>

Parameters

NameTypeDescription
uniqueIdstringTikTok username (with or without @)

Returns

LivePageInfo { roomId, ttwid, clusterRegion } or null

Example

resolveLivePage - Node.js
import { resolveLivePage } from '@tiktool/live';

const info = await resolveLivePage('streamer_name');
if (info) {
  console.log('Room ID:', info.roomId);
  console.log('Region:', info.clusterRegion);
  console.log('Cookie:', info.ttwid);
} else {
  console.log('User is not live');
}
resolveRoomId

Resolves a TikTok username to a numeric room ID. Thin wrapper around resolveLivePage() - returns just the room ID string. Results are cached for 5 minutes.

resolveRoomId(uniqueId: string): Promise<string | null>

Parameters

NameTypeDescription
uniqueIdstringTikTok username (with or without @)

Returns

Room ID string or null if not live

Example

resolveRoomId - Node.js
import { resolveRoomId } from '@tiktool/live';

const roomId = await resolveRoomId('streamer_name');
if (roomId) {
  console.log('Room ID:', roomId);
  // Use with REST API endpoints that require room_id
}
fetchSignedUrl

Executes a signed-URL response from the API server. When the API returns action: "fetch_signed_url", pass the response to this function to fetch the actual TikTok data. Used internally by callApi().

fetchSignedUrl(response: SignedUrlResponse): Promise<any>

Parameters

NameTypeDescription
responseSignedUrlResponseThe API response containing signed_url, headers, and cookies

Returns

Parsed JSON data from TikTok

Example

fetchSignedUrl - Node.js
import { fetchSignedUrl } from '@tiktool/live';

// Low-level usage (callApi handles this automatically)
const apiResponse = await fetch(
  'https://api.tik.tools/webcast/room_info?apiKey=KEY',
  { method: 'POST', body: JSON.stringify({ room_id: '123' }) }
).then(r => r.json());

if (apiResponse.action === 'fetch_signed_url') {
  const tiktokData = await fetchSignedUrl(apiResponse);
  console.log(tiktokData);
}

Authentication

All API requests require authentication via an API key or JWT token.

API Key

Pass your API key as a query parameter or header:

Authentication
# Query parameter
GET /webcast/check_alive?apiKey=YOUR_KEY&unique_id=username

# Header
GET /webcast/check_alive?unique_id=username
x-api-key: YOUR_KEY

JWT Token

For frontend WebSocket connections, generate a JWT token server-side and pass it as jwtKey:

WebSocket JWT
wss://api.tik.tools?uniqueId=username&jwtKey=YOUR_JWT

JWTs can be scoped to specific creators and have configurable expiry. See /authentication/jwt.

JWT in action

Mint a token, open a socket, watch events

The preview on the right is a single HTML file that POSTs the JWT mint, then opens a sandboxed WebSocket. No backend, no build step.

auth-flow.htmlRUNNING
Preview loads once visible.

WebSocket Connection

Connect via WebSocket to receive real-time events from any TikTok LIVE stream.

Connection URL

Connection
wss://api.tik.tools?uniqueId=USERNAME&apiKey=YOUR_KEY

Message Format

Events are sent as JSON with the following structure:

JSON
{ "event": "chat", "data": { "type": "chat", "user": { ... }, "comment": "Hello!" } }

First Message

Upon connection, the server sends a roomInfo event:

roomInfo
{ "event": "roomInfo", "roomId": "7123456789", "uniqueId": "streamer", "connectedAt": "..." }
WebSocket Example - Node.js
Node.js
Python
cURL
Java
Go
C#
import WebSocket from 'ws';

const ws = new WebSocket('wss://api.tik.tools?uniqueId=streamer&apiKey=YOUR_KEY');

ws.on('open', () => console.log('Connected!'));

ws.on('message', (raw) => {
  const { event, data } = JSON.parse(raw);
  // data.user.level (raw WS) / data.user.payGrade (SDK) = gifter/donator
  // level (1-50). Always on the wire when the sender has gifted before.
  const lvlVal = data?.user?.level ?? data?.user?.payGrade ?? 0;
  const lvl = lvlVal ? ` lv${lvlVal}` : '';
  switch (event) {
    case 'roomInfo': console.log('Room:', data.roomId); break;
    case 'chat':     console.log(`${data.user.uniqueId}${lvl}:`, data.comment); break;
    case 'gift':     console.log(`${data.user.uniqueId}${lvl}`, 'sent', data.giftName); break;
    case 'like':     console.log(`${data.user.uniqueId}${lvl}`, 'liked ×' + data.likeCount); break;
    case 'member':   console.log(`${data.user.uniqueId}${lvl}`, 'joined'); break;
  }
});

ws.on('close', (code, reason) => console.log('Closed:', code, reason.toString()));
Try the WebSocket

Real frames in your browser, right now

The sandboxed iframe opens a real WebSocket against api.tik.tools and prints every event as it arrives. Reload to pick another live creator.

websocket-demo.htmlRUNNING
Preview loads once visible.

REST API Reference

HTTP endpoints for signing, fetching room data, and managing authentication.

Public REST sample

The ticker is the simplest REST call

GET /api/live/top-channels needs no auth - 1 call / 5 min per IP. Use it to seed any of the WebSocket examples with a known-live creator.

top-channels.htmlRUNNING
Preview loads once visible.
POST/webcast/sign_url

Sign any TikTok webcast URL with the required X-Bogus and X-Gnarly anti-automation parameters. This is the foundation of the API - TikTok rejects unsigned requests, so this endpoint adds the cryptographic signatures needed for valid requests.

How it works: Send any raw TikTok webcast URL in the request body. The API returns the signed URL with all required parameters appended, plus the User-Agent and cookies you must use when fetching.

Use cases: Building custom TikTok integrations, signing WebSocket URLs for direct connections, or signing any TikTok API endpoint that requires X-Bogus validation.

Response data: Returns signed_url (the URL with signatures), x_bogus, x_gnarly, user_agent, and cookies fields. Always use the returned User-Agent when making the final request to TikTok.

Parameters

NameTypeRequiredDescription
urlstringYesThe TikTok URL to sign

Request

POST /webcast/sign_url - Node.js
Node.js
Python
cURL
Java
Go
C#
const res = await fetch('https://api.tik.tools/webcast/sign_url?apiKey=YOUR_KEY', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ url: 'https://webcast.tiktok.com/...' })
});
const data = await res.json();
console.log(data.data.signed_url);

Response

response.json
{ "status_code": 0, "data": { "signed_url": "...", "x_bogus": "...", "x_gnarly": "...", "user_agent": "...", "cookies": "..." } }
GET/webcast/check_alive

Check if one or more TikTok users are currently live streaming. Returns live status, room ID, stream title, and viewer count for each user.

Lookup methods: Pass unique_id (username) for single-user lookups, or room_ids (comma-separated) to check multiple rooms at once. At least one parameter is required.

Response data: Each entry includes room_id, alive (boolean), title, and userCount. Use this to build monitoring dashboards, trigger alerts when creators go live, or validate a stream before connecting via WebSocket.

Performance: This endpoint is lightweight and cached - ideal for polling. Combine with bulk_live_check for monitoring large lists of creators.

Parameters

NameTypeRequiredDescription
room_idsstringNoComma-separated room IDs
unique_idstringNoTikTok username (without @)

Request

GET /webcast/check_alive - Node.js
Node.js
Python
cURL
Java
Go
C#
const res = await fetch('https://api.tik.tools/webcast/check_alive?apiKey=YOUR_KEY&unique_id=username');
const { data } = await res.json();
console.log(data[0].alive ? 'Live!' : 'Offline');

Response

response.json
{ "status_code": 0, "data": [{ "room_id": "...", "alive": true, "title": "...", "userCount": 1234 }] }
GET/webcast/live_status

Instant zero-latency liveness check sourced from the relay's internal state - no TikTok API call required. Use this before opening a WebSocket connection to avoid connecting to channels that are offline.

How it works: Checks the relay's Dragonfly cache for any channel currently connected to the relay or recently seen by the leaderboard poller (90s TTL). Returns a result in <5ms.

Limitation: Only covers channels actively tracked by the relay or the live leaderboard. If a channel is live but has never been connected to the relay, it may return alive: false. For a definitive check, use /webcast/check_alive.

Best practice: Use live_status as a fast pre-flight check. Only fall back to check_alive (which calls TikTok) when live_status returns offline for a channel you expect to be live.

Single channel per request. To check many creators in one call, use bulk_live_check (POST with a unique_ids array) - it returns is_live + room_id for each. Do NOT fire many live_status calls in parallel from one server IP; that trips upstream burst throttling and they time out. Batch with bulk_live_check (e.g. 50/call on Pro) and poll every couple of minutes.

Parameters

NameTypeRequiredDescription
uniqueIdstringYesTikTok username (single, no @ prefix). Accepts uniqueId or unique_id. For many creators, use bulk_live_check.

Request

GET /webcast/live_status - Node.js
Node.js
Python
cURL
Java
Go
C#
// Single channel (use bulk_live_check for many - do NOT loop live_status in parallel)
const res = await fetch('https://api.tik.tools/webcast/live_status?apiKey=YOUR_KEY&uniqueId=username');
const { data } = await res.json();
if (data.is_live) {
  // Open WebSocket immediately - relay already has this room
  const ws = new WebSocket(wss://api.tik.tools?uniqueId=username&apiKey=YOUR_KEY`);
}

// Many creators? ONE batched call instead of parallel live_status:
const bulk = await fetch('https://api.tik.tools/webcast/bulk_live_check?apiKey=YOUR_KEY', {
  method: 'POST', headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ unique_ids: ['user1', 'user2', 'user3'] })
});
const live = (await bulk.json()).data.filter(c => c.is_live);

Response

response.json
{ "status_code": 0, "data": { "unique_id": "username", "is_live": true, "room_id": "7637157...", "cached": false } }
GET/webcast/rate_limits

Check your current API key's rate limit status, tier information, and remaining quota across all features.

What's returned: Your current tier (free/pro/ultra), API request limits (per-minute sliding window), WebSocket connection limits, bulk check capacity, and feed daily quota. Also includes reset_at timestamp for when limits refresh.

Use cases: Monitor quota consumption in production, display remaining capacity in dashboards, or implement client-side throttling to avoid hitting limits.

Headers: Every API response also includes X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers - but this endpoint gives a comprehensive overview of all your limits in one call.

Request

GET /webcast/rate_limits - Node.js
Node.js
Python
cURL
Java
Go
C#
const res = await fetch('https://api.tik.tools/webcast/rate_limits?apiKey=YOUR_KEY');
const { data } = await res.json();
console.log(`${data.api.remaining}/${data.api.limit} requests remaining`);

Response

response.json
{ "status_code": 0, "data": { "tier": "basic", "api": { "limit": 30, "remaining": 28, "reset_at": 1234567890 }, "websocket": { "limit": 10, "current": 2 } } }
POST/webcast/fetch

Fetch live stream events via HTTP long-polling. Returns the same real-time data as WebSocket (chat, gifts, likes, battles) but over HTTP. Uses TikTok's binary protobuf protocol and returns decoded events.

How it works: The API signs and fetches TikTok's internal polling endpoint, decodes the protobuf response, and returns structured event data. Pass a cursor from the previous response for continuous polling.

Identification: Provide either unique_id (username) or room_id. If using unique_id, the server resolves the room ID automatically.

WebSocket vs Fetch: WebSocket connections are preferred for real-time use - they deliver events instantly with lower latency. Use /webcast/fetch when WebSocket is not possible (serverless environments, HTTP-only infrastructure) or for one-off data snapshots.

Parameters

NameTypeRequiredDescription
unique_idstringNoTikTok username
room_idstringNoRoom ID (alternative to unique_id)
cursorstringNoPagination cursor from previous response

Request

POST /webcast/fetch - Node.js
Node.js
Python
cURL
Java
Go
C#
const res = await fetch('https://api.tik.tools/webcast/fetch?apiKey=YOUR_KEY', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ unique_id: 'username' })
});
const { data } = await res.json();
console.log(`Messages: ${data.message_count}`);

Response

response.json
{ "status_code": 0, "data": { "room_id": "...", "alive": true, "message_count": 5, "raw_data": "base64...", "cursor": "" } }
POST/webcast/room_info

Get comprehensive information about a live stream room including the host's profile, stream title, viewer count, start time, and stream configuration.

Response data: Returns owner profile (nickname, uniqueId, profilePictureUrl, follower count), room metadata (title, user_count, like_count, create_time), stream URLs, cover images, and room status.

Identification: Provide either unique_id (username) or room_id. Returns an error if the user is not currently live.

Use cases: Pre-flight checks before WebSocket connection, building stream info cards/embeds, monitoring dashboards, or collecting the room ID for use with other endpoints like rankings or gift_info.

Server-side / datacenter IPs - use mode: "fetch" (Pro+): By default this endpoint returns a signed_url you fetch yourself. TikTok blocks datacenter egress to its webcast host, so that fetch returns an empty body from a server. Instead pass mode: "fetch" with a room_id and TikTool fetches TikTok on our infrastructure and returns the parsed JSON directly - user_count (live viewers), total_user, title, like_count, live_duration_seconds, owner, status - and it works from any datacenter IP. mode: "fetch" needs a room_id, so resolve it first server-side with live_status (GET /webcast/live_status?unique_id=<handle> returns is_live + room_id), then call room_info with { "room_id": "...", "mode": "fetch" }. No page-scraping, no signed_url, no residential IP needed. (title may be empty if the creator set none; total_user may be null.)

Parameters

NameTypeRequiredDescription
unique_idstringNoTikTok username (resolves to room_id; for mode=fetch resolve via live_status first)
room_idstringNoRoom ID (required when mode=fetch)
modestringNoSet to "fetch" (Pro+) to have TikTool fetch + parse the room data server-side and return user_count/title/total_user directly - works from datacenter IPs. Requires room_id. Without it you get a signed_url you must fetch yourself (browser/residential only).

Request

POST /webcast/room_info - Node.js
Node.js
Python
cURL
Java
Go
C#
const res = await fetch('https://api.tik.tools/webcast/room_info?apiKey=YOUR_KEY', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ unique_id: 'username' })
});
const { data } = await res.json();
console.log(`${data.title} - ${data.user_count} viewers`);

Response

response.json
{ "status_code": 0, "data": { "room_id": "...", "alive": true, "title": "...", "user_count": 500, "owner": {...}, "like_count": 1234, "share_count": 56 } }

// With mode:"fetch" (Pro+, server-side, datacenter-friendly):
{ "status_code": 0, "mode": "fetch", "data": { "room_id": "...", "status": 2, "user_count": 2924, "total_user": null, "title": "...", "like_count": 0, "live_duration_seconds": 13362, "owner": { "nickname": "...", "display_id": "..." } } }
POST/webcast/room_video

Get live stream video playback URLs in multiple formats and quality levels. Returns HLS (.m3u8), FLV, and direct origin URLs suitable for embedding or processing.

Stream formats: hls_pull_url for adaptive bitrate (best for web players), flv_pull_url for low-latency playback, and origin URLs for the highest quality source stream. Multiple resolutions are available (SD, HD, FULL_HD).

Use cases: Building custom live stream players, recording/archiving streams, feeding audio to transcription services (used internally by our Live Captions feature), or creating multi-stream monitoring walls.

Important: Stream URLs are time-limited and will expire. Re-fetch periodically if you need to maintain long-running playback. The hls format provides the most reliable playback across browsers and devices.

Parameters

NameTypeRequiredDescription
unique_idstringNoTikTok username
room_idstringNoRoom ID

Request

POST /webcast/room_video - Node.js
Node.js
Python
cURL
Java
Go
C#
const res = await fetch('https://api.tik.tools/webcast/room_video?apiKey=YOUR_KEY', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ unique_id: 'username' })
});
const { data } = await res.json();
const hlsUrl = data.stream_urls?.origin?.hls;

Response

response.json
{ "status_code": 0, "data": { "room_id": "...", "alive": true, "stream_urls": { "origin": { "hls": "...", "flv": "..." }, "sd": {...} }, "default_quality": "origin" } }
POST/webcast/bulk_live_check

Check live status for multiple TikTok users in a single request. Returns the same data as check_alive but for up to 100 users simultaneously.

Batch limits: Free: 1 user, Pro: up to 50 users, Ultra: up to 100 users per request. Pass usernames as a JSON array in the request body.

Response data: Returns an array with each user's unique_id, room_id, alive status, title, and userCount. Users who are offline return alive: false with an empty room_id.

Use cases: Creator monitoring dashboards that track dozens of streamers, automated alert systems, multi-stream aggregators, or periodically scanning talent rosters to detect who's live.

Parameters

NameTypeRequiredDescription
unique_idsstring[]YesArray of TikTok usernames (or use "room_ids" array of room IDs)

Request

POST /webcast/bulk_live_check - Node.js
Node.js
Python
cURL
Java
Go
C#
const res = await fetch('https://api.tik.tools/webcast/bulk_live_check?apiKey=YOUR_KEY', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ unique_ids: ['user1', 'user2', 'user3'] })
});
const { data } = await res.json();
data.forEach(row => console.log(`${row.unique_id}: ${row.is_live ? 'LIVE' : 'offline'} (room ${row.room_id})`));

Response

response.json
{ "status_code": 0, "data": [
  { "unique_id": "user1", "is_live": true,  "alive": true,  "room_id": "7234567890123456789" },
  { "unique_id": "user2", "is_live": false, "alive": false, "room_id": "7234567890123456790" },
  { "unique_id": "user3", "is_live": true,  "alive": true,  "room_id": "7234567890123456791" }
] }
POST/webcast/ws_credentials

Get pre-signed WebSocket connection credentials for establishing a direct connection to TikTok's live stream WebSocket servers. Returns everything needed to connect without using the managed relay.

What's returned: A signed websocket_url (with X-Bogus), cookies (ttwid, session), user_agent, room_id, and cluster_region. Use these to create your own WebSocket connection directly to TikTok.

When to use: Advanced users building custom WebSocket clients who need direct TikTok connections (without using our managed relay). Most users should use the managed WebSocket connection (wss://api.tik.tools) or the Node.js SDK instead.

Important: Direct connections count toward TikTok's per-IP WebSocket limit (~4 concurrent). If you need more, use our managed relay which distributes connections across multiple IPs.

Parameters

NameTypeRequiredDescription
unique_idstringYesTikTok username

Request

POST /webcast/ws_credentials - Node.js
Node.js
Python
cURL
Java
Go
C#
const res = await fetch('https://api.tik.tools/webcast/ws_credentials?apiKey=YOUR_KEY', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ unique_id: 'username' })
});
const { data } = await res.json();
console.log('WS URL:', data.ws_url);

Response

response.json
{ "status_code": 0, "data": { "ws_url": "wss://...", "cookies": "ttwid=...", "room_id": "...", "ws_host": "...", "cluster_region": "US_TTP" } }
POST/webcast/sign_websocket

Sign a TikTok WebSocket URL with X-Bogus parameters for direct connection to TikTok's webcast-ws servers. This is the WebSocket-specific equivalent of sign_url.

How it works: Provide a room_id and the API generates a fully signed WebSocket URL targeting the correct regional TikTok WebSocket server (webcast-ws.tiktok.com, webcast-ws.us.tiktok.com, etc.).

Response: Returns signed_url (the complete wss:// URL), cookies, and user_agent. Connect using these credentials with the provided cookies in the WebSocket handshake headers.

Use case: Building custom WebSocket clients in languages without our SDK, or when you need fine-grained control over the WebSocket lifecycle. The SDK handles this internally - most users won't need this endpoint directly.

Parameters

NameTypeRequiredDescription
room_idstringYesTikTok room ID
cursorstringNoPagination cursor

Request

POST /webcast/sign_websocket - Node.js
Node.js
Python
cURL
Java
Go
C#
const res = await fetch('https://api.tik.tools/webcast/sign_websocket?apiKey=YOUR_KEY', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ room_id: '7123456789' })
});
const { data } = await res.json();
// Connect with: new WebSocket(data.signed_url)

Response

response.json
{ "status_code": 0, "data": { "signed_url": "wss://...", "x_bogus": "...", "x_gnarly": "...", "user_agent": "...", "cookies": "..." } }
POST/webcast/resolve_user_ids

Resolve numeric TikTok user IDs to their corresponding usernames (unique_id/display_id). Accepts up to 20 user IDs per request with server-side caching for fast lookups.

When you need this: TikTok's internal APIs and WebSocket events often send numeric user IDs (like 6892636847263982593) instead of readable usernames. This endpoint converts them back to @username format.

Caching: Results are cached server-side for 24 hours. Repeated lookups for the same user IDs are instant and don't count toward rate limits.

Response data: Returns a data object keyed by the userId, each value { userId, username, nickname, avatarUrl, cached }. Users whose IDs can't be resolved (deleted/banned accounts) come back with username/nickname/avatarUrl all null. This is the canonical way to turn a numeric userId from a WS event (e.g. the co-host id in a linkMic frame's extras["5"]) into a readable profile.

Parameters

NameTypeRequiredDescription
user_idsstring[]YesArray of numeric user IDs (max 20)

Request

POST /webcast/resolve_user_ids - Node.js
Node.js
Python
cURL
Java
Go
C#
const res = await fetch('https://api.tik.tools/webcast/resolve_user_ids?apiKey=YOUR_KEY', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ user_ids: ['107955', '6789012345'] })
});
const { data } = await res.json();
Object.values(data).forEach(u => console.log(`${u.userId} → @${u.username}`));

Response

response.json
{ "status_code": 0, "data": { "7529001445733385224": { "userId": "7529001445733385224", "username": "cirelieazap", "nickname": "Eric_2.0", "avatarUrl": "https://...", "cached": false } } }
GET/webcast/user_profilePRO+

Reverse lookup: unique_id (the @username) → numeric TikTok user ID + full public profile metadata.

When you need this: - You have a TikTok username and need the numeric user ID (e.g. for cross-referencing with chat/gift events that ship userId) - You want bio, follower/following counts, video count, verification status, avatar URLs - You need the secUid (TikTok's signed user identifier, required for some downstream APIs)

Caching: Profile results cached server-side for 24h. Repeated lookups for the same unique_id are instant and don't burn quota. Pass nocache=1 to bypass (useful for bio-verification flows where the user just updated their profile).

Returns: - id - numeric TikTok user ID - uniqueId - @username - secUid - signed user identifier (used internally by TikTok) - nickname - display name - signature - bio text - bioLink - the creator's native link-in-bio (URL); empty string if they haven't set one. bioLinkRisk is TikTok's link-risk score. Resolved server-side, so it works from a datacenter IP. (Some creators paste their link into the bio text instead - that's in signature.) - verified - boolean blue-check flag - avatarThumb / avatarMedium / avatarLarger - three avatar resolutions - stats - followerCount, followingCount, heartCount, videoCount - league - current Diamond Rush league standing (see below)

League field (Ultra+ only): Every response includes a top-level league object with the user's current Diamond Rush class + rank. Ultra and Global Agency tiers receive the real class label (A1/A2/D5/etc.) + region + rank. Pro and lower tiers receive an upgrade prompt.

- Ultra+: { found: true, region: 'IT+', classType: 100, classLabel: 'D5', rank: 1 } - Pro/Basic: { found: true, available_on: 'ultra+', message: 'Upgrade to Ultra or Global for Rankings', upgrade_url: 'https://tik.tools/pricing' } - Not in any league: { found: false, region: null, classType: null, classLabel: null, rank: null }

League snapshots refresh every ~60s from the leaderboard poller; per-user lookup is cached server-side for 5 minutes.

Tier limits: Pro 1,000/day, Ultra 10,000/day, Admin unlimited. Sub-Pro tiers receive 403.

Parameters

NameTypeRequiredDescription
unique_idstringYesTikTok @username (no leading @)
nocache0 | 1NoSet to 1 to bypass cache (useful after profile updates)

Request

GET /webcast/user_profile - Node.js
Node.js
Python
cURL
Java
Go
C#

Response

response.json
{
  "status_code": 0,
  "data": {
    "profile": {
      "id": "7355610677036581896",
      "uniqueId": "dalga.ahmedov",
      "secUid": "MS4wLjABAAAA...",
      "nickname": "🤍DENIZ🖤🌊",
      "signature": "",
      "bioLink": "battles.app",
      "bioLinkRisk": 3,
      "verified": false,
      "avatarThumb": "https://...",
      "avatarMedium": "https://...",
      "avatarLarger": "https://...",
      "stats": { "followerCount": 12345, "followingCount": 250, "heartCount": 980000, "videoCount": 142 }
    },
    "league": {
      "found": true,
      "region": "IT+",
      "classType": 100,
      "classLabel": "D5",
      "rank": 1
    },
    "cached": false,
    "usage": { "profile_lookups_used": 47, "profile_lookups_limit": 1000 }
  }
}
POST/authentication/jwt

Generate a JWT (JSON Web Token) for secure frontend WebSocket connections. JWTs allow your client-side code to connect without exposing your API key.

Security model: Generate JWTs server-side using your API key, then pass the jwtKey to your frontend. The JWT can be scoped to specific creators (allowed_creators) and limited to a set number of concurrent connections.

Expiry: Tokens expire after expire_after seconds (default: 3600 = 1 hour). After expiry, the client must request a new token from your backend.

Use cases: Web applications where the frontend connects directly to wss://api.tik.tools?uniqueId=USERNAME&jwtKey=TOKEN. This keeps your API key on the server while letting browsers connect to the WebSocket.

Parameters

NameTypeRequiredDescription
expire_afternumberNoSeconds until expiry (default: 3600)
allowed_creatorsstring[]NoRestrict to specific creators
max_websocketsnumberNoMax concurrent WS connections (default: 1)

Request

POST /authentication/jwt - Node.js
Node.js
Python
cURL
Java
Go
C#
const res = await fetch('https://api.tik.tools/authentication/jwt?apiKey=YOUR_KEY', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ expire_after: 3600, allowed_creators: ['streamer1'] })
});
const { data } = await res.json();
// Use data.token for WebSocket: wss://api.tik.tools?jwtKey=TOKEN

Response

response.json
{ "status_code": 0, "data": { "token": "eyJ..." } }
GET/webcast/rankings

Get real-time leaderboard data for a live room - top gifters, online audience rankings, and engagement scores. Uses the "sign-and-return" pattern for authenticated data.

Data available: In-room top gifters (online_audience - ranked by diamonds sent this session), all-time gifter leaderboard (anchor_rank_list), and entrance configuration (ranking tabs, timer info, class/league badges).

Authentication: For full leaderboard data, include your TikTok session cookies via x-cookie-header. Without authentication, the endpoint returns limited ranking data.

Response pattern: Returns a signed_url that you fetch with session cookies to get TikTok's ranking data. Each rank entry includes user (nickname, avatar, follower count), score (diamonds), and rank position.

Parameters

NameTypeRequiredDescription
room_idstringNoTikTok room ID
unique_idstringNoTikTok username (alternative to room_id)

Request

GET /webcast/rankings - Node.js
Node.js
Python
cURL
Java
Go
C#
const res = await fetch('https://api.tik.tools/webcast/rankings?unique_id=username&apiKey=YOUR_KEY');
const { data } = await res.json();
console.log(data.rankings);

Response

response.json
{ "status_code": 0, "data": { "room_id": "...", "rankings": [...] } }
GET/api/leaderboards/leagues/:regionULTRA+

List all available league classes (A1, A2, B5, C1, D1, etc.) discovered for a region.

What are leagues? TikTok groups LIVE creators into competitive classes based on earnings tier. Each class (A1 = top earners, D2 = entry level) has its own leaderboard. Agencies use this to identify which class a creator competes in without manually checking TikTok.

Access: Ultra developer tier or Agency subscription only. Non-qualifying users see league tab names but not creator data.

Data source: Updated every 5 minutes by the leaderboard poller via live anchor probing. Regions with active streams populate faster.

Response: Array of { classType, classLabel, count, updatedAt } - one entry per discovered class.

Parameters

NameTypeRequiredDescription
regionstringYesRegion code (BK=Balkans, BC=Baltics, US+=North America, UK+=United Kingdom, etc.)

Request

GET /api/leaderboards/leagues/:region - Node.js
Node.js
Python
cURL
Java
Go
C#
const res = await fetch('https://tik.tools/api/leaderboards/leagues/BK', {
  headers: { 'x-api-key': 'YOUR_ULTRA_API_KEY' }
});
const { leagues } = await res.json();
leagues.forEach(l => console.log(l.classLabel, l.count));

Response

response.json
{
  "region": "BK",
  "available": true,
  "leagues": [
    { "classType": 2000, "classLabel": "A1", "count": 87, "updatedAt": 1715000000000 },
    { "classType": 1900, "classLabel": "A2", "count": 92, "updatedAt": 1715000000000 },
    { "classType": 1000, "classLabel": "C1", "count": 78, "updatedAt": 1715000000000 }
  ]
}
GET/api/leaderboards/league/:region/:classTypeULTRA+

Get the ranked creator list for a specific league class within a region.

Full access (Ultra + Agency): All entries (up to 99) with nickname, avatar, score, rank, and live status.

Teaser (all other tiers): 5 randomly sampled entries only.

classType values: A1=2000, A2=1900, B5=1100, C1=1000, C2=900, D1=500, D2=400, D3=300. Fetch available classTypes from /api/leaderboards/leagues/:region first.

Live enrichment: Each entry includes isLive and roomId if the creator is currently streaming.

Agency use case: Identify which A1/A2 creators are active in a region for talent scouting without manually checking dozens of profiles.

Parameters

NameTypeRequiredDescription
regionstringYesRegion code (BK, BC, US+, etc.)
classTypenumberYesClass type integer (2000=A1, 1900=A2, 1000=C1, etc.)

Request

GET /api/leaderboards/league/:region/:classType - Node.js
Node.js
Python
cURL
Java
Go
C#
const headers = { 'x-api-key': 'YOUR_ULTRA_API_KEY' };

// Step 1: list available leagues
const { leagues } = await fetch(
  'https://tik.tools/api/leaderboards/leagues/BK',
  { headers }
).then(r => r.json());

// Step 2: fetch A1 entries
const a1 = leagues.find(l => l.classLabel === 'A1');
const { entries } = await fetch(
  https://tik.tools/api/leaderboards/league/BK/${a1.classType}`,
  { headers }
).then(r => r.json());
console.log('A1 top creator:', entries[0].nickname);

Response

response.json
{
  "region": "BK",
  "classType": 2000,
  "classLabel": "A1",
  "hasFullAccess": true,
  "totalCount": 87,
  "liveCount": 12,
  "maskedCount": 0,
  "entries": [
    {
      "rank": 1,
      "userId": "6841523190294479361",
      "uniqueId": "creator_username",
      "nickname": "Creator Name",
      "avatar": "https://...",
      "score": 1250000,
      "isLive": true,
      "roomId": "7123456789012345678"
    }
  ]
}

`userId` is TikTok's stable numeric backend ID (never changes). `uniqueId` is the @-handle (user-changeable). `nickname` is the display name (user-changeable).
POST/webcast/room_id

Resolve a TikTok username to their current live room ID. The server resolves the unique_id to a room_id on your behalf - no client-side page parsing needed.

How it works: The server checks its cache first (30-minute TTL). Resolution falls back to a live lookup on cache miss. Reliability varies by tier - Pro/Ultra/Admin requests use a higher-priority resolution path.

Freshness guarantee (Pro+): If a cached room ID is found but the user is no longer live on that room, the server automatically invalidates all cache layers and re-resolves. This ensures you always get the current room ID, even if a streamer went offline and started a new stream (which creates a new room ID). When this happens, the response includes stale_fix: true.

Response data: Returns unique_id, room_id, alive (whether the user is currently live), cached (whether the result came from server cache), and optionally stale_fix (if a stale cached room was detected and re-resolved). Use the returned room_id with other endpoints like sign_websocket, rankings, gift_info, etc.

Use cases: When you only have a username and need the room_id for other API calls. Many endpoints accept unique_id directly, but some (like sign_websocket) require the room ID. This endpoint eliminates the need for client-side TikTok page parsing. Safe to poll every 5-10 minutes - cached results are instant and re-resolution only triggers when needed.

Parameters

NameTypeRequiredDescription
unique_idstringYesTikTok username (without @)

Request

POST /webcast/room_id - Node.js
Node.js
Python
cURL
Java
Go
C#
const res = await fetch('https://api.tik.tools/webcast/room_id?apiKey=YOUR_KEY', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ unique_id: 'username' })
});
const { data } = await res.json();
console.log(`Room ID: ${data.room_id}, Live: ${data.alive}`);

Response

response.json
{ "status_code": 0, "data": { "unique_id": "username", "room_id": "7123456789", "alive": true, "cached": false, "stale_fix": false } }
GET/webcast/room_cover

Get the high-quality cover image (thumbnail) URL of an active live stream. Returns the same image TikTok displays on the live feed before a user taps to watch.

Image quality: Returns the highest resolution available from TikTok's CDN. Cover images are typically 720x960 or higher, served as JPEG/WebP from TikTok's image CDN.

Use cases: Building live stream galleries, social media embeds, notification cards with stream previews, or monitoring dashboards that show stream thumbnails alongside viewer counts.

Note: Cover images are set by the streamer before going live. If no custom cover is set, TikTok uses a default or auto-generated thumbnail. Returns an error if the user is not currently live.

Parameters

NameTypeRequiredDescription
unique_idstringNoTikTok username
room_idstringNoTikTok room ID

Request

GET /webcast/room_cover - Node.js
Node.js
Python
cURL
Java
Go
C#
const res = await fetch('https://api.tik.tools/webcast/room_cover?unique_id=username&apiKey=YOUR_KEY');
const { data } = await res.json();
console.log(data.cover_url);

Response

response.json
{ "status_code": 0, "data": { "room_id": "...", "cover_url": "https://..." } }
GET/webcast/hashtag_list

Get currently trending live stream hashtags from TikTok. Returns hashtag names, live viewer counts, and associated metadata.

Response data: Each hashtag includes id, title (the hashtag text), user_count (total viewers across all streams using this hashtag), and related metadata. Results are sorted by popularity.

Use cases: Discover trending topics for content strategy, build hashtag explorers, analyze live streaming trends over time, or filter the feed by popular hashtags.

Pagination: Use the count parameter to control results per page (default: 20, max: 50). The endpoint uses TikTok's internal trending algorithm, so results update frequently as trending topics shift.

Parameters

NameTypeRequiredDescription
countnumberNoNumber of results (default: 20, max: 50)

Request

GET /webcast/hashtag_list - Node.js
Node.js
Python
cURL
Java
Go
C#
const res = await fetch('https://api.tik.tools/webcast/hashtag_list?count=10&apiKey=YOUR_KEY');
const { data } = await res.json();
data.forEach(tag => console.log(tag.title));

Response

response.json
{ "status_code": 0, "data": [{ "id": "...", "title": "#gaming", "user_count": 15000 }, ...] }
GET/webcast/gift_info

Get the global TikTok gift catalog: every gift name, diamond cost, icon URL, ID. The catalog is room-independent - call the endpoint with no params, get the catalog back. Tier-branched response:

- Sandbox / Basic / Pro → returns a static 50-gift SAMPLE so you can build + test your UI against the real schema without paying. Shape: { status_code, source: "sample", is_sample: true, count: 50, data: { gifts: [...] }, your_tier, upgrade_url }. ~9.7KB, instant response. - Ultra / Global Agency → returns the full live catalog (~500-1000 gifts), parsed and ready to use. Shape: { status_code, mode: "fetch", data: { room_id, gifts: [...] } }. Catalog is refreshed hourly upstream from TikTok. ~100KB, ~1-2s response (server-side fetch via our proxy pool).

Same response key path (data.gifts[]) on every tier so your client code works without changes when you upgrade.

Recommended workflow (matches what every enterprise client builds): 1. Cache the full catalog on Ultra at app startup (one call/day is plenty). 2. Live events (all paid plans) → WebSocket sends just gift_id + sender + count for zero-latency delivery. 3. Local match → look up the gift_id in your cached catalog to display the correct icon + calculate diamonds. 4. Auto-update → if a WebSocket gift_id is missing from your cache, refresh the catalog (new gift just shipped from TikTok).

Each gift entry has: id, name, icon_url (or icon on Ultra fetch shape), diamond_count, value_usd (1 diamond ≈ $0.005). Sandbox/Basic/Pro samples include is_sample: true and source: "sample" so your client can detect dev vs prod data.

Why Ultra? TikTok ships new gifts hourly. A one-off pull goes stale fast. Ultra subscribers get the hourly auto-refresh + worldwide creator coverage across every leaderboard + league.

Parameters

NameTypeRequiredDescription
(none required)-NoCatalog is global - no params needed. Optional: `mode=sign` (Ultra+) to get a signed URL back instead of the parsed catalog if you want to fetch upstream yourself.

Request

GET /webcast/gift_info - Node.js
Node.js
Python
cURL
Java
Go
C#
// Sample (sandbox/basic/pro) OR full live catalog (ultra/agency) - tier-branched server-side
const res = await fetch('https://api.tik.tools/webcast/gift_info', {
  headers: { 'x-api-key': 'YOUR_KEY' }
});
const json = await res.json();
const gifts = json.data.gifts;
console.log(`got ${gifts.length} gifts (source=${json.source || 'live'})`);
gifts.forEach(g => console.log(`${g.name}: ${g.diamond_count} diamonds`));

Response

response.json
// Sandbox/Basic/Pro:
{ "status_code": 0, "source": "sample", "is_sample": true, "count": 50,
  "sample_generated_at": "2026-06-05T11:18:13Z",
  "data": { "gifts": [{ "id": "9072", "name": "TikTok Universe", "icon_url": "https://cdn.tik.tools/gifts/...", "diamond_count": 44999, "value_usd": 449.99 }, ...] },
  "your_tier": "sandbox", "upgrade_url": "https://tik.tools/pricing" }

// Ultra/Agency (default mode=fetch - no params needed):
{ "status_code": 0, "mode": "fetch",
  "data": { "room_id": "...", "gifts": [{ "id": "9072", "name": "TikTok Universe", "diamond_count": 44999, "icon": "https://..." }, ...] } }

// Ultra/Agency mode=sign (returns the upstream signed URL for you to fetch yourself):
{ "status_code": 0, "signed_url": "https://webcast.tiktok.com/webcast/gift/list/?...", "headers": { "User-Agent": "...", "Referer": "..." } }
GET/webcast/gifts_by_countryULTRA+

Get the gift catalog for a specific country/region. TikTok scopes the gift panel to the region of the LIVE room it is served from, so this endpoint anchors to a currently-live creator in the requested region's leaderboard and returns that room's catalog - each gift with ID, icon URL, diamond value, USD value, and whether it is a streakable/combo gift.

Tier-branched response: - Sandbox / Basic / Pro → static 50-gift sample so you can build against the schema. Shape: { status_code, source: "sample", is_sample: true, message, data: { country, region, gifts }, your_tier, upgrade_url }. - Ultra / Global Agencylive per-country catalog for the region you request.

Params: country - an ISO country code (US, GB, BR, JP, DE, ...) or a tracked region code. One country per call.

Each gift entry: id, name, icon_url, diamond_count, value_usd (= diamond_count / 100), type, combo, streakable (streakable = combo gift, can be held/sent in a rapid streak).

Notes: A creator must be currently LIVE in the region to anchor the panel (else 503 - retry shortly or try another country). Catalogs change slowly, so cache results - this endpoint is rate-limited to 60 calls/hour per key.

Available on Ultra+. Upgrade at https://tik.tools/pricing.

Parameters

NameTypeRequiredDescription
countrystringYesISO country code (US, GB, BR, JP, ...) or a tracked region code. One country per call.

Request

GET /webcast/gifts_by_country - Node.js
Node.js
Python
cURL
Java
Go
C#
const res = await fetch('https://api.tik.tools/webcast/gifts_by_country?country=US', {
  headers: { 'x-api-key': 'YOUR_KEY' }
});
const json = await res.json();
json.data.gifts.forEach(g =>
  console.log(`${g.name}: ${g.diamond_count} diamonds ($${g.value_usd})${g.streakable ? ' [streakable]' : ''}`));

Response

response.json
// Ultra/Agency:
{ "status_code": 0, "country": "US", "region": "US+", "region_label": "United States",
  "anchor_room_id": "765...", "count": 587,
  "data": { "gifts": [
    { "id": "59881", "name": "A Shard of Hope", "icon_url": "https://...png",
      "diamond_count": 1, "value_usd": 0.01, "type": 1, "combo": true, "streakable": true }, ...
  ] } }

// Sandbox/Basic/Pro (static sample):
{ "status_code": 0, "source": "sample", "is_sample": true,
  "message": "Live per-country gift catalog is an Ultra+ feature...",
  "data": { "country": "US", "region": null, "gifts": [...] },
  "your_tier": "sandbox", "upgrade_url": "https://tik.tools/pricing" }
POST/chat-sendBASIC+

Send a chat message to a live TikTok room as the logged-in user. One call - the server signs, joins the room, sends, and confirms the message actually appeared in the live chat.

Authentication: include TikTok session cookies via the x-cookie-header header. Minimum required is sessionid + tt-target-idc (the data-center cookie, e.g. eu-ttp2). Get them from browser DevTools → Application → Cookies → tiktok.com.

Use a real, established account. Brand-new accounts (auto usernames like user35441..., no avatar/posts, or under ~24h old) are auto-shadowbanned from live chat by TikTok and return delivered: false no matter what.

delivered is the truth. TikTok returns status_code: 0 even on messages it silently shadow-drops. This endpoint confirms your message appeared in the room's live event stream and reports delivered: true / false. Do not rely on status_code alone.

Parameters

NameTypeRequiredDescription
channelstringYesThe live creator's @username (the server resolves the current room)
textstringYesChat message text (max ~150 chars)

Request

POST /chat-send - Node.js
Node.js
Python
cURL
Java
Go
C#
const res = await fetch('https://api.tik.tools/chat-send?apiKey=YOUR_KEY', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-cookie-header': 'sessionid=YOUR_SESSIONID; tt-target-idc=eu-ttp2'
  },
  body: JSON.stringify({ channel: 'creatorname', text: 'Hello!' })
});
console.log(await res.json()); // { delivered: true, msg_id: '...', confirmed_via: 'live-event' }

Response

response.json
{ "delivered": true, "status_code": 0, "msg_id": "7650032344963615510", "channel": "creatorname", "confirmed_via": "live-event" }
GET/webcast/user_earnings

Retrieve TikTok LIVE earnings data for a creator including diamonds received, coin equivalent, and period breakdowns. This is an authenticated endpoint that relays TikTok's creator earnings API.

Authentication required: You must include TikTok session cookies via x-cookie-header. Only works when the session belongs to the creator whose earnings are being requested (you can't view other people's earnings).

Response data: Returns diamonds (total received), coins (equivalent coin value), and period breakdown. Diamond-to-USD conversion: 1 diamond ≈ $0.005 USD.

Use cases: Creator dashboards showing real-time earnings, financial analytics for agency talent management, or automated earnings reports for multi-stream operations.

Parameters

NameTypeRequiredDescription
unique_idstringYesTikTok username
periodstringNoPeriod filter: "daily" (default)

Request

GET /webcast/user_earnings - Node.js
Node.js
Python
cURL
Java
Go
C#
const res = await fetch('https://api.tik.tools/webcast/user_earnings?unique_id=creator&apiKey=YOUR_KEY', {
  headers: { 'x-cookie-header': 'sessionid=YOUR_TIKTOK_COOKIES' }
});
const { data } = await res.json();
console.log(`Diamonds: ${data.diamonds}`);

Response

response.json
{ "status_code": 0, "data": { "diamonds": 1500, "coins": 75000, "period": "daily" } }
GET/webcast/feedPRO+

Discover currently live TikTok LIVE streams. Uses a two-step "sign-and-return" pattern - the API returns a signed URL with headers and cookies that you fetch from YOUR own IP to get the actual TikTok feed data.

Authentication: Include your TikTok session_id cookie for personalized, populated results. Without it, results are anonymous and limited (~5 rooms).

Channels: 87 = Recommended - the main "For You" infinite scroll feed, 86 = Suggested - sidebar host recommendations (shown while watching), 89 or 1111006 = Gaming, 42 = Following (requires session).

Geo-targeting: Feed results are determined by the IP address making the final TikTok fetch (Step 2), NOT by the region parameter. Since you fetch the signed URL from your own client, you'll see content relevant to your geographic location. The region param is passed to TikTok but has minimal effect on results.

Pagination: The API automatically switches to TikTok's "load more" mode when you pass a max_time cursor. Fetch the signed URL → parse the TikTok JSON response → read data.extra.max_time → pass it as max_time in your next call. Each page returns ~6-15 rooms; keep paginating for continuous discovery. The response also includes a load_more_url template - just replace {MAX_TIME} with the cursor.

Rate limits: Pro: 100 calls/day. Ultra: 2,000 calls/day. Response includes feed_remaining and feed_limit fields.

Response data: Each room includes owner.display_id (username), owner.nickname (display name), title, user_count (viewers), owner.avatar_thumb (profile photo), id_str (room ID), and more.

Parameters

NameTypeRequiredDescription
session_idstringNoYour TikTok sessionid cookie - strongly recommended. Get from browser DevTools → Application → Cookies → tiktok.com. Without it, results are sparse.
channel_idstringNoFeed channel: 87 = Recommended "For You" (default), 86 = Suggested sidebar, 89 or 1111006 = Gaming, 42 = Following
countnumberNoRooms per page (default: 20, max: 50)
max_timestringNoPagination cursor from previous TikTok response (data.extra.max_time). Omit or "0" for first page.
regionstringNoHint passed to TikTok (default: US). Note: actual results are geo-targeted by the IP making the final fetch, not this param.
ttwidstringNoTikTok ttwid visitor cookie. Server provides one if omitted.
ms_tokenstringNoTikTok msToken cookie (optional, a placeholder is used if omitted).

Request

GET /webcast/feed - Node.js
Node.js
Python
cURL
Java
Go
C#
// Step 1: Get signed URL with session for populated results
const res = await fetch(
  'https://api.tik.tools/webcast/feed?' + new URLSearchParams({
    apiKey: 'YOUR_KEY',
    session_id: 'YOUR_TIKTOK_SESSION_ID',  // from browser cookies
    region: 'US',
    channel_id: '87',  // 87=recommended, 86=suggested, 1111006=gaming
    count: '20',
  })
);
const { signed_url, headers, cookies, feed_remaining, feed_limit } = await res.json();
console.log(`Quota: ${feed_remaining}/${feed_limit} calls remaining`);

// Step 2: Fetch TikTok data from YOUR IP
const tikRes = await fetch(signed_url, {
  headers: { ...headers, Cookie: cookies }
});
const feed = await tikRes.json();

// Step 3: Parse live rooms
for (const entry of feed.data) {
  const room = entry.data;
  console.log(`🔴 @${room.owner.display_id} (${room.owner.nickname})`);
  console.log(`   "${room.title}" - ${room.user_count} viewers`);
}

// Step 4: Load more (pagination)
const nextCursor = feed.extra?.max_time;
if (nextCursor) {
  const page2 = await fetch(
    'https://api.tik.tools/webcast/feed?' + new URLSearchParams({
      apiKey: 'YOUR_KEY', session_id: 'YOUR_SESSION_ID',
      region: 'US', max_time: nextCursor, count: '20',
    })
  );
  // ... repeat Step 2-3
}

Response

response.json
{
  "status_code": 0,
  "signed_url": "https://webcast.tiktok.com/webcast/feed/?...",
  "method": "GET",
  "headers": { "User-Agent": "...", "Referer": "https://www.tiktok.com/", ... },
  "cookies": "ttwid=...; sessionid=...; sessionid_ss=...",
  "region": "US",
  "channel_id": "87",
  "feed_remaining": 99,
  "feed_limit": 100,
  "note": "Fetch signed_url from your client/IP with these headers and cookies."
}

// After fetching signed_url, TikTok returns:
{
  "status_code": 0,
  "data": [{
    "data": {
      "id_str": "7123456789",
      "title": "🔴 Live Now!",
      "user_count": 1250,
      "owner": {
        "display_id": "streamer_name",
        "nickname": "Display Name",
        "avatar_thumb": { "url_list": ["https://..."] }
      }
    }
  }, ...],
  "extra": { "max_time": "1773128824428" }
}
GET/webcast/live_analytics/video_list

List historical live stream recordings for a TikTok account. Returns past streams with metadata including title, duration, viewer count, and stream date. Requires authenticated TikTok session cookies.

Authentication required: Include session cookies via x-cookie-header. Only accessible for the account that owns the session - you cannot view another creator's video history.

Response data: Each entry includes video_id (stream identifier), title, duration (seconds), viewer_count (peak viewers), create_time (start timestamp), and engagement metrics.

Use cases: Building creator analytics dashboards, tracking streaming performance over time, generating stream history reports, or identifying a specific video_id for use with the video_detail endpoint.

Parameters

NameTypeRequiredDescription
unique_idstringYesTikTok username
countnumberNoNumber of results (default: 20)

Request

GET /webcast/live_analytics/video_list - Node.js
Node.js
Python
cURL
Java
Go
C#
const res = await fetch('https://api.tik.tools/webcast/live_analytics/video_list?unique_id=creator&apiKey=YOUR_KEY', {
  headers: { 'x-cookie-header': 'sessionid=YOUR_TIKTOK_COOKIES' }
});
const { data } = await res.json();
data.forEach(v => console.log(`${v.title}: ${v.viewer_count} viewers`));

Response

response.json
{ "status_code": 0, "data": [{ "video_id": "...", "title": "...", "duration": 3600, "viewer_count": 1200 }, ...] }
GET/webcast/live_analytics/video_detail

Get granular analytics for a specific past live stream session. Returns detailed performance metrics including total views, peak concurrent viewers, gifts received, new followers gained, and engagement rates. Requires authenticated TikTok session cookies.

Authentication required: Include session cookies via x-cookie-header. Access is restricted to the account's own streams.

Response data: Includes views (total), duration (seconds), peak_viewers, gifts (total diamond value), new_followers, likes, comments, and period-specific breakdowns. The video_id comes from the video_list endpoint.

Use cases: Post-stream analytics reports, A/B testing different stream formats, tracking creator growth metrics, or building agency reporting dashboards that aggregate performance across multiple creators.

Parameters

NameTypeRequiredDescription
video_idstringYesVideo/stream ID

Request

GET /webcast/live_analytics/video_detail - Node.js
Node.js
Python
cURL
Java
Go
C#
const res = await fetch('https://api.tik.tools/webcast/live_analytics/video_detail?video_id=VID123&apiKey=YOUR_KEY', {
  headers: { 'x-cookie-header': 'sessionid=YOUR_TIKTOK_COOKIES' }
});
const { data } = await res.json();
console.log(`Duration: ${data.duration}s, Peak: ${data.peak_viewers}`);

Response

response.json
{ "status_code": 0, "data": { "video_id": "...", "duration": 3600, "views": 12000, "gifts": 350, "peak_viewers": 800 } }
GET/webcast/live_analytics/user_interactions

Get the real-time online audience roster for a live room, ranked by gift score (diamonds sent during this session). Returns each viewer's rank, score, and full profile. Requires authenticated TikTok session cookies.

How it works: Uses the "sign-and-return" pattern. The API returns a signed_url that you fetch with your session cookies to get TikTok's audience data. The anchor_id (host's user ID) is auto-resolved from the room_id.

Response data: Each entry includes rank (1-based position), score (diamonds sent this session), and user profile with nickname, display_id, id_str, avatar_thumb, and follow_info (follower/following counts).

Use cases: Identifying top supporters in real-time, building live leaderboard overlays, tracking viewer engagement and spending patterns, or providing viewer analytics for agency clients.

Parameters

NameTypeRequiredDescription
room_idstringYesRoom ID of the stream
user_idstringNoFilter to specific user ID
countnumberNoNumber of results (default: 50)

Request

GET /webcast/live_analytics/user_interactions - Node.js
Node.js
Python
cURL
Java
Go
C#
const res = await fetch('https://api.tik.tools/webcast/live_analytics/user_interactions?room_id=7123456789&apiKey=YOUR_KEY', {
  headers: { 'x-cookie-header': 'sessionid=YOUR_TIKTOK_COOKIES' }
});
const { signed_url, headers, anchor_id } = await res.json();

// Fetch from your IP with your session
const tikRes = await fetch(signed_url, {
  headers: { ...headers, Cookie: `sessionid=YOUR_SESSIONID; ${headers.Cookie || ''}` }
});
const { data } = await tikRes.json();
data.ranks.forEach(r => console.log(`#${r.rank} ${r.user.nickname}: ${r.score} pts`));

Response

response.json
{ "status_code": 0, "signed_url": "https://...", "anchor_id": "...", "note": "Fetch signed_url with your session cookies" }

// After fetching signed_url with session cookies, TikTok returns:
{ "data": { "ranks": [{ "rank": 1, "score": 192, "user": { "nickname": "...", "display_id": "...", "id_str": "..." } }], "total": 49 } }
GET/api/live/connect

Bundled endpoint - fetches both a scoped JWT token and stream video URLs in a single request. Ideal for frontend apps that need to open a WebSocket and play video simultaneously. Rate limited to 10 req/min.

Parameters

NameTypeRequiredDescription
uniqueIdstringYesTikTok username (without @)

Request

GET /api/live/connect - Node.js
Node.js
Python
cURL
Java
Go
C#
const res = await fetch('https://tik.tools/api/live/connect?uniqueId=username');
const data = await res.json();
// data.token - short-lived JWT for WebSocket auth
// data.wsUrl  - WebSocket server URL
// data.stream - video URLs (HLS/FLV)
const ws = new WebSocket(`${data.wsUrl}?uniqueId=username&jwtKey=${data.token}`);

Response

response.json
{ "token": "eyJ...", "wsUrl": "wss://api.tik.tools", "uniqueId": "streamer", "stream": { "room_id": "...", "alive": true, "stream_urls": { "origin": { "hls": "...", "flv": "..." } }, "default_quality": "origin" } }
POST/webcast/ranklist/regionalPRO+

Get regional LIVE leaderboard rankings - daily, hourly, popular, or league. Returns a signed URL that the client fetches with their own TikTok session cookie. Requires Pro or Ultra tier.

Parameters

NameTypeRequiredDescription
unique_idstringNoTikTok username (auto-resolves room_id and anchor_id)
room_idstringNoRoom ID (alternative to unique_id)
anchor_idstringNoAnchor/owner user ID. Auto-resolved if unique_id is used.
rank_typestringNoRanking period: "1" = Hourly, "8" = Daily (default), "15" = Popular LIVE, "16" = League
typestringNo"list" (default) for rankings, "entrance" for available tabs
gap_intervalstringNoGap interval filter (default: "0")

Request

POST /webcast/ranklist/regional - Node.js
Node.js
Python
cURL
Java
Go
C#
// Step 1: Get signed URL from the API
const res = await fetch('https://api.tik.tools/webcast/ranklist/regional?apiKey=YOUR_KEY', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    room_id: '7607695933891218198',
    anchor_id: '7444599004337652758',
    rank_type: '8'  // Daily
  })
});
const signData = await res.json();

// Step 2: Fetch from YOUR IP with YOUR session cookie
const tikResp = await fetch(signData.signed_url, {
  method: signData.method,
  headers: { ...signData.headers, Cookie: `sessionid=YOUR_SESSIONID; ${signData.cookies}` },
  body: signData.body
});
const { data } = await tikResp.json();
data.rank_view.ranks.forEach((r, i) =>
  console.log(`${i+1}. ${r.user.nickname} - ${r.score} pts`)
);

Response

response.json
{
  "status_code": 0,
  "action": "fetch_signed_url",
  "signed_url": "https://webcast.tiktok.com/webcast/ranklist/list/v2/?...",
  "method": "POST",
  "headers": { "User-Agent": "...", "Content-Type": "application/x-www-form-urlencoded" },
  "body": "room_id=123&rank_type=8&anchor_id=456&gap_interval=0",
  "cookies": "ttwid=...",
  "note": "POST this URL with your sessionid cookie from your browser/IP."
}
GET/webcast/ranklist/gamingGLOBAL

TikTok LIVE Gaming Ranks per region: the overall gaming leaderboard plus that region's 3 top game boards (per-game weekly, ~99 creators each). Served from our continuously-maintained cache - no TikTok session needed. Global Agency tier gets every name unmasked; all other tiers receive the exact same response shape with the top 3 names as a teaser and the rest dot-masked, plus an upgrade prompt, so you can build against the structure before upgrading.

Parameters

NameTypeRequiredDescription
regionstringNoRegion code, e.g. US+ (URL-encode the plus as %2B), BR, JP, ID, TR, BK. Defaults to US+.

Request

GET /webcast/ranklist/gaming - Node.js
Node.js
Python
cURL
Java
Go
C#
const res = await fetch(
  'https://api.tik.tools/webcast/ranklist/gaming?region=US%2B&apiKey=YOUR_KEY'
);
const data = await res.json();
console.log(`${data.region}: ${data.count} gaming creators, ${data.games.length} games`);
for (const g of data.games) {
  console.log(`${g.game} (${g.official ? 'weekly' : 'live'}): ${g.count} creators`);
  g.entries.slice(0, 5).forEach(e => console.log(`  #${e.rank} ${e.unique_id} - ${e.score}`));
}

Response

response.json
{
  "status_code": 0,
  "region": "US+",
  "gated": false,            // true for non-global tiers (masked)
  "tier": "agency_global",
  "updated_at": 1781500000000,
  "count": 99,
  "entries": [               // overall gaming leaderboard
    { "rank": 1, "unique_id": "creator", "nickname": "Creator",
      "avatar": "https://...", "score": 607740, "is_live": true, "room_id": "7..." }
  ],
  "games": [                 // the region's 3 game boards
    { "rank_type": 23, "game": "Call of Duty: Mobile", "game_id": 0,
      "official": true,      // true = TikTok's weekly board; false = grouped fallback
      "weekly": true, "reset_at": 1782050000000, "count": 99,
      "entries": [ { "rank": 1, "unique_id": "...", "score": 926566, "is_live": true, "room_id": "7..." } ] }
  ]
  // non-global tiers also include: "upgrade": "...Upgrade to the Global tier..."
}
GET/webcast/ranklist/gaming_moversGLOBAL

The "99+" view: every creator who entered the Gaming Top 99 at ANY point during the current rank-day - including the reset-time churn where a creator holds a spot for seconds - per game. TikTok only ever exposes the live Top 99; this surfaces who moved through and below the cutoff, sourced from our rank-history (no extra consumption). Each mover carries their best (peak) rank of the day, peak diamonds, how many times they appeared, whether they are live now, and the diamonds needed to re-enter the current Top 99. Global Agency tier gets every name unmasked; all other tiers get the same shape with dot-masked identifiers (real ranks + scores shown) plus an upgrade prompt.

Parameters

NameTypeRequiredDescription
regionstringNoRegion code, e.g. US+ (URL-encode the plus as %2B), BR, JP, ID, TR, BK. Defaults to US+.

Request

GET /webcast/ranklist/gaming_movers - Node.js
Node.js
Python
cURL
Java
Go
C#
const res = await fetch(
  'https://api.tik.tools/webcast/ranklist/gaming_movers?region=US%2B&apiKey=YOUR_KEY'
);
const data = await res.json();
for (const g of data.games) {
  const below = g.movers.filter(m => !m.in_top99_now);
  console.log(`${g.game}: ${below.length} below the #99 cutoff (${g.cutoff_score})`);
  below.slice(0, 5).forEach(m => console.log(`  peak #${m.peak_rank} ${m.unique_id} - ${m.peak_score}, reenter ${m.diamonds_to_reenter}`));
}

Response

response.json
{
  "status_code": 0,
  "region": "US+",
  "region_label": "United States",
  "gated": false,            // true for non-global tiers (masked)
  "tier": "agency_global",
  "games": [
    { "class_type": 0, "game": "Overall (Daily)", "cutoff_score": 54771, "total": 393,
      "movers": [
        { "peak_rank": 2, "unique_id": "creator", "nickname": "Creator", "avatar": "https://...",
          "peak_score": 202840, "appearances": 155, "live_now": false,
          "in_top99_now": false, "diamonds_to_reenter": 0 }
      ] }
  ]
  // non-global tiers also include: "upgrade": "...Upgrade to the Global tier..."
}
GET/webcast/ranklist/region_moversGLOBAL

The "entered today, below cutoff now" view for the standard regional/country leaderboards (single board per region, vs gaming_movers per game). Every creator who held a board spot at ANY point during the current rank-day - including the reset-time churn - but sits below the live cutoff now, sourced from our rank-history (no extra consumption). Each mover carries their best (peak) rank of the day, peak diamonds, appearances, whether they are live now, and the diamonds needed to re-enter. Global Agency tier gets every name unmasked; all other tiers get the same shape with dot-masked identifiers (real ranks + scores shown) plus an upgrade prompt.

Parameters

NameTypeRequiredDescription
regionstringNoRegion code, e.g. US+ (URL-encode the plus as %2B), BR, JP, ID, TR, BK, VN. Defaults to US+.

Request

GET /webcast/ranklist/region_movers - Node.js
Node.js
Python
cURL
Java
Go
C#
const res = await fetch(
  'https://api.tik.tools/webcast/ranklist/region_movers?region=US%2B&apiKey=YOUR_KEY'
);
const data = await res.json();
console.log(`${data.total} entered today, now below the ${data.cutoff_score} cutoff`);
data.movers.slice(0, 5).forEach(m => console.log(`  peak #${m.peak_rank} ${m.unique_id} - ${m.peak_score}, reenter ${m.diamonds_to_reenter}`));

Response

response.json
{
  "status_code": 0,
  "region": "US+",
  "region_label": "United States",
  "gated": false,            // true for non-global tiers (masked)
  "tier": "agency_global",
  "cutoff_score": 284791,
  "total": 247,
  "movers": [
    { "peak_rank": 4, "unique_id": "creator", "nickname": "Creator", "avatar": "https://...",
      "peak_score": 146343, "appearances": 102, "live_now": false,
      "in_board_now": false, "diamonds_to_reenter": 138448 }
  ]
  // non-global tiers also include: "upgrade": "...Upgrade to the Global tier..."
}
GET/webcast/ranklist/shoppingGLOBAL

NEW: the weekly TikTok Shop Live Shopping leaderboard per region - the creators driving the most GMV on LIVE this week, recorded continuously on the same rotation as the rest of the rank intel (no TikTok session needed). Each rank carries the creator handle, display name, the week's GMV score, and whether they are live now. The response also carries the rank-window countdown so you know when the weekly board resets. Global Agency tier gets every name unmasked; all other tiers receive the exact same response shape with the top 3 names as a teaser and the rest dot-masked, plus an upgrade prompt, so you can build against the structure before upgrading. Also live on the website at /tiktok-live-gaming-ranks?board=shopping.

Parameters

NameTypeRequiredDescription
regionstringNoRegion code, e.g. US+ (URL-encode the plus as %2B), BR, JP, ID, TR, BK. Defaults to US+.

Request

GET /webcast/ranklist/shopping - Node.js
Node.js
Python
cURL
Java
Go
C#
const res = await fetch(
  'https://api.tik.tools/webcast/ranklist/shopping?region=US%2B&apiKey=YOUR_KEY'
);
const data = await res.json();
console.log(`${data.region}: ${data.count} sellers, board resets in ${Math.round(data.countdown / 3600000)}h`);
data.ranks.slice(0, 5).forEach(r => console.log(`  #${r.rank} ${r.uniqueId} - GMV ${r.score}${r.isLive ? ' (live)' : ''}`));

Response

response.json
{
  "status_code": 0,
  "region": "US+",
  "region_label": "United States",
  "gated": false,            // true for non-global tiers (masked)
  "tier": "agency_global",
  "weekly": true,
  "updated_at": 1781500000000,
  "next_reset": 1782050000000,   // ms epoch when the weekly board rolls over
  "countdown": 549999000,        // ms remaining until next_reset
  "count": 99,
  "ranks": [
    { "rank": 1, "uniqueId": "shopcreator", "displayName": "Shop Creator",
      "avatar": "https://...", "score": 184320, "isLive": true, "roomId": "7..." }
  ]
  // non-global tiers also include: "upgrade": "...Upgrade to the Global tier..."
  // and dot-mask uniqueId/displayName for ranks 4+ (real rank + score kept)
}
GET/webcast/leaderboard

Returns the pre-aggregated TikTok daily creator leaderboard for a region in <50ms (no TikTok roundtrip). The leaderboard-poller continuously snapshots 28 regions every 30-60s using anchor-verified probes and a top-10 cross-region validator that rejects wrong-region scrapes before they corrupt the snapshot. Each entry includes rank, uniqueId, userId, nickname, avatar, diamond score, isLive flag, and roomId for the live ones.

Tier behaviour: SANDBOX / FREE / BASIC / PRO callers receive the real uniqueId + nickname + avatar + score + isLive for ranks 1-3 of every region (so the response shape is fully demonstrable). userId and roomId are nulled even on those top-3 ranks. Ranks 4 to 99 are replaced with random per-request placeholder rows (\creator-<8 hex>\, blank nickname, generic avatar). \masked: true\ is set on every placeholder row. ULTRA / AGENCY GLOBAL / ADMIN receive every rank fully unmasked including userId + roomId.

Parameters

NameTypeRequiredDescription
regionstringYesOne of: BR, LATAM, US+, CA, AU+, JP, KR, TW, ID, MY, PH, CCA, TH, VN, DE+, ES+, HU+, PT+, FR+, BK, PL, UK+, IT+, SE+, NL, RO, MENA, TR

Request

GET /webcast/leaderboard - Node.js
Node.js
Python
cURL
Java
Go
C#
const r = await fetch('https://api.tik.tools/webcast/leaderboard?region=KR', {
  headers: { 'x-api-key': process.env.TIKTOOL_API_KEY }
})
const data = await r.json()
console.log(`${data.region}: ${data.live_count} creators live, top: ${data.entries[0].uniqueId}`)

Response

response.json
{
  "status_code": 0,
  "region": "KR",
  "rank_type": "daily",
  "fetched_at": 1780775023239,
  "live_count": 611,
  "entries": [
    { "rank": 1, "uniqueId": "park1h", "userId": "...", "nickname": "Wonho", "avatar": "...", "score": 41003344, "isLive": true, "roomId": "..." },
    { "rank": 2, "uniqueId": "...", "score": 30221100, "isLive": false }
  ]
}
GET/webcast/ranklist/gamingGLOBAL

Returns the TikTok LIVE Gaming Ranking for a region - the live in-app gaming leaderboard, recorded continuously by anchor-verified probes on the same rotation as leagues. Each entry includes rank, uniqueId, nickname, avatar, diamond score, estimated USD, and isLive.

Access: Global Agency only. SANDBOX / FREE / BASIC / PRO / ULTRA callers receive a masked 3-name preview (ranks 1-3 real with real scores as the hook; ranks 4+ replaced with length-matched placeholder rows, masked: true, no avatar). AGENCY GLOBAL / ADMIN receive every rank fully unmasked.

Also live on the website at /tiktok-live-gaming-ranks.

Parameters

NameTypeRequiredDescription
regionstringNoRegion code (one of: BR, LATAM, US+, AU+, JP, KR, TW, ID, MY, PH, CCA, TH, VN, DE+, ES+, HU+, PT+, FR+, BC, BK, PL, UK+, IT+, SE+, NL, RO, MENA, TR). Omit or pass "top" for the most-populated region.

Request

GET /webcast/ranklist/gaming - Node.js
Node.js
Python
cURL
Java
Go
C#
const r = await fetch('https://api.tik.tools/webcast/ranklist/gaming?region=US%2B', {
  headers: { 'x-api-key': process.env.TIKTOOL_API_KEY }
})
const data = await r.json()
console.log(`${data.region}: top gaming creator ${data.entries[0].uniqueId} (${data.entries[0].score})`)

Response

response.json
{
  "status_code": 0,
  "region": "US+",
  "fetched_at": 1780775023239,
  "total": 99,
  "entries": [
    { "rank": 1, "uniqueId": "yazitax_", "nickname": "Kiki", "avatar": "...", "score": 530587, "usd": 2653, "isLive": true },
    { "rank": 2, "uniqueId": "...", "score": 412900, "isLive": false }
  ]
}
GET/webcast/leaderboard/leagues

Returns ALL 18 league brackets for a region in one response. Each bracket is a class_type from 100 (lowest) up to 2000 (Diamond) plus 1800/1900 intermediate brackets, with the top-99 creators currently in that bracket. Useful for full-region league analytics, dashboards, or replicating a region's league standings inside another product.

Region parameter: accepts a region code (IT+, BK, UK+, ...) OR a country code (IT, GR, GB, DE, FR, ...) - country codes resolve to their TikTok Business Market region automatically. The response region field echoes the resolved canonical code.

Tier behaviour: every tier can hit this endpoint. SANDBOX through PRO see the top-3 of each bracket fully unmasked (uniqueId + nickname + avatar + score + isLive), with userId and roomId nulled; ranks 4 to 99 are replaced with random placeholder rows. ULTRA / AGENCY GLOBAL / ADMIN see every rank with all identifiers.

Parameters

NameTypeRequiredDescription
regionstringYesRegion code (one of 28: BR, LATAM, US+, CA, AU+, JP, KR, TW, ID, MY, PH, CCA, TH, VN, DE+, ES+, HU+, PT+, FR+, BK, PL, UK+, IT+, SE+, NL, RO, MENA, TR) OR a country code (IT, DE, FR, GB, GR, AE, etc) that resolves to one of those regions.

Request

GET /webcast/leaderboard/leagues - Node.js
Node.js
Python
cURL
Java
Go
C#
const r = await fetch('https://api.tik.tools/webcast/leaderboard/leagues?region=KR', {
  headers: { 'x-api-key': process.env.TIKTOOL_API_KEY }
})
const data = await r.json()
console.log('Diamond top:', data.classes['2000'].entries[0])

Response

response.json
{
  "status_code": 0,
  "region": "KR",
  "classes": {
    "100":  { "fetched_at": 1780414384749, "entries": [...] },
    "200":  { "fetched_at": ..., "entries": [...] },
    "...":  "...",
    "2000": { "fetched_at": 1780774261464, "entries": [{ "rank": 1, "uniqueId": "park1h", "score": 23119099, "isLive": true, ... }] }
  }
}
GET/webcast/leaderboard/league

Returns a single league bracket (one class_type) inside a region. Cheaper than /leaderboard/leagues when you only care about the top-tier bracket (Diamond = 2000) or want to poll a specific class on a tight schedule.

Tier behaviour: same masking rules as the other /webcast/leaderboard endpoints - SANDBOX through PRO see the top-3 unmasked (with userId/roomId nulled) and ranks 4 to 99 as placeholder rows; ULTRA / AGENCY GLOBAL / ADMIN see every rank fully.

Parameters

NameTypeRequiredDescription
regionstringYesRegion code (e.g. "KR").
classstringYesLeague class type. One of: 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400, 1500, 1800, 1900, 2000.

Request

GET /webcast/leaderboard/league - Node.js
Node.js
Python
cURL
Java
Go
C#
const r = await fetch('https://api.tik.tools/webcast/leaderboard/league?region=KR&class=2000', {
  headers: { 'x-api-key': process.env.TIKTOOL_API_KEY }
})
const data = await r.json()

Response

response.json
{
  "status_code": 0,
  "region": "KR",
  "class_type": "2000",
  "fetched_at": 1780774261464,
  "entries": [
    { "rank": 1, "uniqueId": "park1h", "userId": "...", "nickname": "Wonho", "score": 23119099, "isLive": true, "roomId": "..." }
  ]
}
GET/webcast/live-counts

Returns the per-region count of unique live creators (across the daily leaderboard + every league bracket) plus the global total and a second "verified" total that intersects the leaderboard with an active relay/gift WS signal (lower bound). Powers the tik.tools/ranking page header. Refreshed every 30s.

Tier behaviour: open to every tier. No per-creator identifiers are returned at all - the response is pure aggregate counts - so there is nothing to mask.

Request

GET /webcast/live-counts - Node.js
Node.js
Python
cURL
Java
Go
C#
const r = await fetch('https://api.tik.tools/webcast/live-counts', {
  headers: { 'x-api-key': process.env.TIKTOOL_API_KEY }
})
const data = await r.json()
console.log(`Global live: ${data.global_live}`)

Response

response.json
{
  "status_code": 0,
  "global_live": 12046,
  "global_live_verified": 1837,
  "regions": {
    "BR": 401, "LATAM": 314, "US+": 321, "CA": 271, "AU+": 139,
    "JP": 604, "KR": 611, "TW": 486, "ID": 654, "MY": 474,
    "PH": 420, "CCA": 528, "TH": 485, "VN": 723,
    "DE+": 491, "ES+": 359, "HU+": 310, "PT+": 260, "FR+": 412,
    "BK": 317, "PL": 286, "UK+": 343, "IT+": 403, "SE+": 192,
    "NL": 232, "RO": 326, "MENA": 304, "TR": 345
  }
}
GET/api/leaderboards/country/:slugPRO+

Real-time creator leaderboard for any country or TikTok Business Market region. Returns ranked entries with diamonds, live status, room IDs (for the alive ones), and 7-90 day rank history for paid tiers. Region slug examples: balkans, united-states-region, japan, latam, united-kingdom. Country slugs follow standard kebab-case names. Identifiers are masked to someone-<hash> for Sandbox viewers; PRO+ unmasks names, handles, and profile URLs.

Parameters

NameTypeRequiredDescription
slugstringYesCountry slug (e.g. "greece") or region slug (e.g. "balkans"). 28 regions + every TikTok-served country supported.

Request

GET /api/leaderboards/country/:slug - Node.js
Node.js
Python
cURL
Java
Go
C#

Response

response.json
{
  "country": { "code": "GR", "name": "Greece", "slug": "greece" },
  "region":  { "code": "BK", "countries": ["GR","BG","RS","RO","..."] },
  "is_active": true,
  "tier": "pro",
  "masked": false,
  "history_days": 7,
  "next_reset": "2026-05-26T00:00:00.000Z",
  "current": {
    "fetched_at": "2026-05-25T10:34:00.000Z",
    "live_count": 142,
    "total_score": 89234500,
    "channels": [
      { "rank": 1, "uniqueId": "...", "displayName": "...", "score": 4502300, "alive": true, "roomId": "...", "profilePic": "..." }
    ]
  },
  "history": [ { "date": "2026-05-24", "top3": [...], "total_score": 76234500, "entry_count": 200 } ]
}
GET/api/leaderboards/leagues/:regionULTRA+

Returns the full league overview for a region: every active class (2000 down to 100, with up to 18 brackets corresponding to TikTok's A1-D5 ladder) with rank windows, member counts, anchor pointers, and the next reset boundary. Identifiers are masked for Sandbox and Pro tiers; ULTRA+ unmasks names + handles + profile URLs. Combine with /api/leaderboards/league/:region/:classType for the per-class entry rows.

Valid region codes (28): BR, LATAM, US+, CA, AU+, JP, KR, TW, ID, MY, PH, CCA, TH, VN, DE+, ES+, HU+, PT+, FR+, BK, PL, UK+, IT+, SE+, NL, RO, MENA, TR. The "+" suffix is REQUIRED for multi-country buckets (eg Italy is "IT+", United Kingdom is "UK+"). Country codes are accepted as aliases - hitting /leagues/IT resolves to IT+, /leagues/GB resolves to UK+, /leagues/GR resolves to BK, etc.

available flag: available: false means our cache has not yet captured any league brackets for the resolved region in the current cycle. It does NOT mean TikTok has no league for that market - we re-probe per missing class every cycle and most regions hold 14-18 of 20 possible classes at any moment. If you see available: false on a region you expect to be active, check that the resolved region field in the response matches what you intended (eg passing IT returns region: "IT+" because IT+ is the canonical code).

Parameters

NameTypeRequiredDescription
regionstringYesRegion code (e.g. "IT+", "BK", "US+", "JP") OR country code (e.g. "IT", "GR", "DE", "GB"). Country codes resolve to their region automatically. See full list of 28 region codes in the description.

Request

GET /api/leaderboards/leagues/:region - Node.js
Node.js
Python
cURL
Java
Go
C#

Response

response.json
{
  "region": "IT+",
  "requested": "IT",
  "leagues": [
    { "classType": 2000, "classLabel": "A1", "count": 99, "updatedAt": 1780779587561, "anchorUniqueId": "abc", "anchorRoomId": "...", "stale": false, "staleMinutes": 1 },
    { "classType": 1900, "classLabel": "A2", "count": 99, "updatedAt": 1780779590451, "anchorUniqueId": "xyz", "anchorRoomId": "...", "stale": false, "staleMinutes": 0 }
  ],
  "available": true,
  "hasFullAccess": true,
  "valid_regions": ["BR","LATAM","US+","CA","AU+","JP","KR","TW","ID","MY","PH","CCA","TH","VN","DE+","ES+","HU+","PT+","FR+","BK","PL","UK+","IT+","SE+","NL","RO","MENA","TR"]
}
GET/api/leaderboards/league/:region/:classTypeULTRA+

All creator entries in a specific league class within a region. Useful for tracking who is winning the current cycle in Diamond/Platinum/Gold/Silver/Bronze. Same mask rule: ULTRA+ unmasks identifiers. ClassType values: 2000 (Diamond), 1000 (Platinum), 600 (Gold), 300 (Silver), 100 (Bronze).

Parameters

NameTypeRequiredDescription
regionstringYesRegion code (e.g. "BK")
classTypenumberYesLeague class: 2000 | 1000 | 600 | 300 | 100

Request

GET /api/leaderboards/league/:region/:classType - Node.js
Node.js
Python
cURL
Java
Go
C#

Response

response.json
{
  "region": "BK",
  "classType": 2000,
  "className": "Diamond",
  "entries": [
    { "rank": 1, "uniqueId": "...", "nickname": "...", "score": 12450, "roomId": "..." }
  ],
  "masked": false,
  "fetched_at": "2026-05-25T10:34:00.000Z"
}
GET/api/gifters/leaderboard

Cross-creator gifter leaderboard - the actual whales spending diamonds across TikTok LIVE in a given region and time window. Global Agency exclusive for unmasked rows; Pro / Ultra / Sandbox all see stable someone-<hash> placeholders. Aggregate numbers (diamonds, ranks, creators gifted) remain visible for everyone so you can still build analytics without paying.

Parameters

NameTypeRequiredDescription
regionstringNoRegion filter (e.g. "BK", "US+"). Default "GLOBAL".
periodstringNo"daily" (default) | "weekly" | "monthly" | "lifetime"
limitnumberNoRows to return (max 200, default 100)

Request

GET /api/gifters/leaderboard - Node.js
Node.js
Python
cURL
Java
Go
C#

Response

response.json
{
  "region": "GLOBAL",
  "period": "daily",
  "gifters": [
    { "rank": 1, "platformId": "...", "username": "...", "displayName": "...",
      "totalDiamonds": 1240000, "creatorsGifted": 6, "giftCount": 1840,
      "profilePic": "...", "profileUrl": "...", "masked": false }
  ],
  "fetched_at": "2026-05-25T10:34:00.000Z"
}
GET/api/gifters/top

Top gifters for a single creator with per-period breakdowns + each gifter's share of the creator's total income. Useful for VIP outreach and roster prioritisation. Global Agency exclusive for unmasked rows; Pro / Ultra / Sandbox see the same masked placeholders. Pass the AES-GCM masked-handle token (?p=<token>) to resolve a masked URL without exposing the real handle.

Parameters

NameTypeRequiredDescription
creatorstringYesCreator username (without @)
pstringNoAES-GCM mask token (used by masked-handle URLs to resolve back to the real creator without leaking the username in the URL)
limitnumberNoRows to return (max 50, default 10)

Request

GET /api/gifters/top - Node.js
Node.js
Python
cURL
Java
Go
C#

Response

response.json
{
  "creator": "...",
  "totalDiamonds": 8420000,
  "topGifters": [
    { "rank": 1, "platformId": "...", "username": "...", "displayName": "...",
      "diamonds": 1240000, "share": 0.147, "lastGiftAt": "2026-05-25T10:30:00.000Z",
      "masked": false }
  ]
}
GET/api/gifters/profile

Deep gifter profile - lifetime diamonds, list of creators they support, wallet-fatigue cohort, loyalty score, latest big gifts, and historical velocity. Global Agency exclusive for unmasked identifiers + profile URL; Pro / Ultra / Sandbox see placeholders with aggregate numbers intact.

Parameters

NameTypeRequiredDescription
usernamestringYesGifter username (without @)
pstringNoAES-GCM mask token for masked-URL resolution

Request

GET /api/gifters/profile - Node.js
Node.js
Python
cURL
Java
Go
C#

Response

response.json
{
  "gifter": {
    "platformId": "...",
    "username": "...",
    "displayName": "...",
    "profilePic": "...",
    "profileUrl": "...",
    "lifetimeDiamonds": 18420000,
    "loyaltyScore": 0.86,
    "walletFatigue": "elevated",
    "creatorsSupported": [
      { "creator": "...", "totalDiamonds": 8420000, "share": 0.46 }
    ],
    "recentBigGifts": [...],
    "history": { "daily": [...], "weekly": [...] }
  }
}
POST/ws/sweepBASIC+

Self-service ghost-WebSocket cleanup. Closes every WebSocket the server currently tracks for your own API key - and nothing else. No admin auth required, just your normal apiKey.

When you need this: - Your SDK crashed without a clean .close() (e.g. SIGKILL, hard reboot, container OOM), leaving TCP sockets open at the OS level. The WebSocket library auto-replies to control PING frames at the OS/library layer even when your app code is gone, so the server side can't tell the connection is logically dead. Result: your concurrent-WS count grows until you hit the tier cap and can't open new ones. - You're deploying a new build and want a clean slate before the new container starts subscribing. - You implemented your own their_count vs our_count drift detector (recommended) and want a button to call when the gap exceeds your tolerance.

What it does NOT do: - Does not touch any OTHER account's connections. - Does not change your tier or limits. - Does not affect future connections - your SDK can immediately reconnect.

Rate limit: 10 calls per hour per key. Polling this is pointless: every NEW WebSocket connect already triggers FIFO eviction of the oldest tracked socket for the same key, so steady-state ghost accumulation is already prevented server-side. Manual sweep is for after-crash hygiene, not a fix-the-drift loop.

Tier: Basic+ (Demo + Sandbox keys have wsLimit=1 so the endpoint is meaningless for them - 403).

Close code your SDK will see: 4502 (SWEEP_INITIATED) with reason text "ws-sweep: caller initiated ghost-WS cleanup". This is distinct from 4401 (INVALID_AUTH). If your SDK auto-treats 4401 as "fatal, stop retrying", a sweep will NOT trigger that path - 4502 is a clean, retryable close.

Request

POST /ws/sweep - Node.js
Node.js
Python
cURL
Java
Go
C#
const r = await fetch('https://api.tik.tools/ws/sweep', {
    method: 'POST',
    headers: { 'x-api-key': process.env.TIKTOOL_API_KEY },
});
const { closed } = await r.json();
console.log('Swept ' + closed + ' stale WebSocket(s).');

Response

response.json
{
  "status_code": 0,
  "message": "Closed 94 WebSocket connection(s) tracked for this key.",
  "closed": 94,
  "close_code": 4502,
  "close_reason_prefix": "ws-sweep:",
  "note": "Sockets closed with code 4502 (SWEEP_INITIATED). Treat 4502 as a clean, retryable close - NOT a 4401 auth failure. Reason text starts with \"ws-sweep:\". Your SDK can immediately reconnect."
}
GET/webcast/eligible_creatorsGLOBAL

Find Eligible Creators to Recruit - the API behind the agency dashboard's recruiting page. Returns a paginated, score-ranked list of every creator currently tracked across the live league boards, so a scouting script can filter out low earners before reaching out.

Payload per creator: username, current_league (e.g. A1, B2), and current_snapshot_score - the real-time score TikTok is reporting for them on their league board at the moment of the query. Each row also carries its region code.

Live snapshot only. This route returns the current snapshot score. Historical data, 30-day timelines, and heavy aggregations stay in the web dashboard by design.

Pagination: results are ranked by current_snapshot_score descending. Page through with page (1-based) and limit (up to 100). The response includes total, total_pages, and returned.

Rate limit: 60 requests/hour per key on this route (it queries the heavy aggregation tables). At limit=100 that is up to ~6,000 profiles/hour. x-ratelimit-limit / x-ratelimit-remaining / x-ratelimit-reset headers and a rate_limit object are returned on every call; over the cap returns HTTP 429.

Access: Agency tier only.

Parameters

NameTypeRequiredDescription
pageintegerNo1-based page number (default 1)
limitintegerNoResults per page, 1-100 (default 50)
regionstringNoOptional region code filter (e.g. US+, MENA, IT+)
min_scoreintegerNoOptional - only return creators at or above this current_snapshot_score

Request

GET /webcast/eligible_creators - Node.js
Node.js
Python
cURL
Java
Go
C#
// Page through every eligible creator, filtering low earners client-side.
let page = 1;
while (true) {
  const res = await fetch(https://api.tik.tools/webcast/eligible_creators?apiKey=YOUR_KEY&page=${page}&limit=100`);
  const data = await res.json();
  for (const c of data.results) {
    if (c.current_snapshot_score < 50000) continue; // skip low earners
    console.log(c.username, c.current_league, c.current_snapshot_score);
  }
  if (page >= data.total_pages) break;
  page++;
}

Response

response.json
{ "status_code": 0, "page": 1, "page_size": 50, "total": 32920, "total_pages": 659, "returned": 50,
  "results": [ { "username": "azd0092", "current_league": "A1", "current_snapshot_score": 10923989, "region": "MENA" } ],
  "rate_limit": { "limit": 60, "remaining": 59, "reset": 1781550485 } }
POST/api/webhooksBASIC+

Register a webhook that pushes events to your URL when one of your watched creators goes live. This is go-live push, not a forward of an already-connected session - we monitor your creators server-side, so nothing needs to be running on your end. Authenticate with your x-api-key (server-to-server) or while logged in to the dashboard.

Watched creators are set per webhook via the creators array (TikTok usernames, no @). Events are tier-gated: - live.start / live.end - all paid plans (Basic and up) - live.peak_viewers, live.gift.high_value - Ultra and Global - creator.follower_milestone, creator.eligible - Global

Per-plan limits (webhook endpoints / deliveries per day / creators per webhook): - Basic: 1 endpoint, 500 deliveries/day, up to 1000 creators - Pro: 3 endpoints, 5,000 deliveries/day, up to 1000 creators - Ultra: 5 endpoints, 50,000 deliveries/day, priority delivery - Global: unlimited endpoints, 500,000 deliveries/day, IP allowlist + priority delivery - Sandbox: test-only (1 disabled config, up to 5 creators, no live delivery)

Delivery is at-least-once with automatic retries; every attempt is recorded and can be replayed. Payloads are signed (X-Tiktool-Signature over the raw body, with X-Tiktool-Timestamp) so you can verify authenticity.

---

Endpoint requirements - Your URL must be publicly reachable. Both http and https are accepted (https recommended - but the HMAC signature protects every delivery either way, so a self-hosted http://IP:port receiver is fine). Private, internal, loopback and link-local hosts are rejected. - Respond 2xx fast. Return 200 the moment you receive the POST, then do your real work (recording, DB writes, downstream calls) asynchronously. If your handler blocks before responding, your listener stops accepting new connections and deliveries start failing. Connect timeout is 10s, read timeout 15s. - Keep the listener always-on. A refused connection (server busy or restarting) or a timeout both count as a failed delivery. Run multiple workers and raise your accept backlog so you never drop connections under load.

Detection & timing - We monitor your creators server-side and fire live.start within seconds of them going live - nothing needs to run on your end. - Detection is near-real-time but poll-based: a live that both starts and ends within a few seconds may not be observed. Lives of normal length are caught reliably.

Delivery & retries - At-least-once. Each event is attempted immediately, then retried on failure at 0s, 30s, 2m, 10m, 1h, 6h (6 attempts over ~6 hours). After the final attempt it is dead-lettered. - Dedupe on event_id - the same event can arrive more than once. - Every attempt is logged; dead-letters can be replayed from the dashboard or API once your endpoint is healthy.

Verifying signatures - Each delivery carries X-Tiktool-Signature (HMAC-SHA256 of the raw request body) and X-Tiktool-Timestamp. Recompute the HMAC with your webhook secret and compare. Also sent on every delivery: X-Tiktool-Event, X-Tiktool-Event-Id, X-Tiktool-Delivery-Id, X-Tiktool-Attempt, X-Tiktool-Webhook-Id.

Troubleshooting - receiving fewer events than expected - Test deliveries work but live events do not: detection is not the problem - look at your endpoint. A "pending" status or missing events almost always means deliveries are failing at your server. - Failures that return in ~200ms are a refused connection: your server was not accepting connections at that moment (busy or restarting). Keep the listener responsive - acknowledge with 200 first, process after. - Slow failures are timeouts: your handler took longer than the read timeout. Acknowledge first, work after. - GET /api/webhooks shows recent delivery health (last status, fail streak) per webhook. Stabilise your endpoint, then replay any dead-lettered deliveries.

Parameters

NameTypeRequiredDescription
target_urlstringYesPublic http or https URL that receives the POST. https recommended; http is allowed (the HMAC signature protects each delivery). Private/internal/loopback hosts are rejected. Can be changed later via PATCH.
event_typesstring[]YesEvents to subscribe to, e.g. ["live.start","live.end"]. Must be allowed by your plan.
creatorsstring[]NoTikTok usernames to watch (no @). Up to 1000 on paid plans, 5 on sandbox. Omit to receive events for all creators allowed by your plan scope.
namestringNoLabel for this webhook in the dashboard.
descriptionstringNoFree-text note.
bearer_tokenstringNoOptional bearer token we send as Authorization on each delivery.
extra_headersobjectNoOptional static headers added to each delivery.

Request

POST /api/webhooks - Node.js
Node.js
Python
cURL
Java
Go
C#
const res = await fetch('https://tik.tools/api/webhooks', {
  method: 'POST',
  headers: { 'x-api-key': 'YOUR_KEY', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    target_url: 'https://your-app.com/hooks/tiktool',
    event_types: ['live.start', 'live.end'],
    creators: ['charlidamelio', 'khaby.lame'],
  }),
})
const webhook = await res.json()
console.log('store this secret:', webhook.secret)

Response

response.json
{
  "id": "wh_01J...",
  "target_url": "https://your-app.com/hooks/tiktool",
  "event_types": ["live.start", "live.end"],
  "creators": ["charlidamelio", "khaby.lame"],
  "secret": "whsec_...",   // shown once - store it to verify signatures
  "active": true,
  "created_at": "2026-06-18T13:00:00.000Z"
}
GET/api/webhooksBASIC+

List your registered webhooks with their event types, watched creators, active state, and recent delivery health. Same auth as register (x-api-key or dashboard session).

Request

GET /api/webhooks - Node.js
Node.js
Python
cURL
Java
Go
C#
const res = await fetch('https://tik.tools/api/webhooks', {
  headers: { 'x-api-key': 'YOUR_KEY' },
})
console.log(await res.json())

Response

response.json
{
  "webhooks": [
    {
      "id": "wh_01J...",
      "target_url": "https://your-app.com/hooks/tiktool",
      "event_types": ["live.start", "live.end"],
      "creators": ["charlidamelio"],
      "active": true,
      "deliveries_today": 42
    }
  ]
}
POST/api/webhooks/{id}/testBASIC+

Fire a synthetic live.start at your target URL (signed with the real secret, flagged is_test:true) so you can validate your integration end-to-end before a real creator goes live. The delivery is logged like any other.

Parameters

NameTypeRequiredDescription
idstringYesWebhook id (path parameter).

Request

POST /api/webhooks/{id}/test - Node.js
Node.js
Python
cURL
Java
Go
C#
await fetch('https://tik.tools/api/webhooks/wh_01J.../test', {
  method: 'POST', headers: { 'x-api-key': 'YOUR_KEY' },
})

Response

response.json
{ "delivered": true, "status_code": 200, "delivery_id": "del_..." }

Live-State Workflow

End-to-end pattern for monitoring a creator watchlist at scale. Combines cheap preflight (/webcast/bulk_live_check) with managed WebSocket open on confirmed-live, plus close-code classification for healthy backoff.

Why this shape: - Cap-aware preflight keeps you from wasting WS slots on offline creators - Tiered backoff per close code avoids hammering streams that just ended - Stateful retry registry prevents reconnect storms after a TikTok throttle - Single connection per creator (no fanout) so end-users read from your store, not from our WS

Production guidance: preflight every 60-120s for high-priority creators, every 10-30min for low-priority rotation. Skip preflight when active WS >= 90% of your tier's cap. On code 4404 (NOT_LIVE), back off 30s/90s/300s then 5-30min. On 4005/4006/4555 (stream-end family), wait until the next preflight tick rather than reconnecting immediately.

Live-State Workflow - Node.js
Node.js
Python
cURL
Java
Go
C#
// Live-state workflow: preflight + WS open + close-code backoff
// Requires: npm i ws
import WebSocket from 'ws';

const API_KEY = process.env.TIKTOOL_API_KEY;
const BASE = 'https://api.tik.tools';
const WS_BASE = 'wss://api.tik.tools';
const WATCHLIST = ['creator1', 'creator2', 'creator3'];
const WS_BUDGET = 100;
const STREAM_END = new Set([4005, 4006, 4555]);

const live = new Map();      // uniqueId -> WebSocket
const cooldown = new Map();  // uniqueId -> retryAt (ms epoch)
const fails = new Map();     // uniqueId -> consecutive 4404 count

// Returns { uniqueId: is_live } map built from the array response.
async function preflight(ids) {
  if (!ids.length) return {};
  const r = await fetch(`${BASE}/webcast/bulk_live_check?apiKey=${API_KEY}`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ unique_ids: ids }),
  });
  const { data = [] } = await r.json();
  return Object.fromEntries(data.map(row => [row.unique_id, !!row.is_live]));
}

function backoffFor(code, n) {
  if (code === 4404) return [, , ][Math.min(n, 2)] ?? (5 + Math.min(n - 3, 5) * 5) * ;
  if (STREAM_END.has(code)) return ;
  if (code === 4429) return ;
  return ;
}

function openWs(uniqueId) {
  if (live.has(uniqueId)) return;
  const ws = new WebSocket(`${WS_BASE}?apiKey=${API_KEY}&uniqueId=${encodeURIComponent(uniqueId)}`);
  live.set(uniqueId, ws);
  ws.on('message', raw => {
    const ev = JSON.parse(raw.toString());
    if (ev.event === 'roomInfo') return;
    console.log(uniqueId, ev.event);
  });
  ws.on('close', (code) => {
    live.delete(uniqueId);
    const n = code === 4404 ? (fails.get(uniqueId) || 0) + 1 : 0;
    fails.set(uniqueId, n);
    cooldown.set(uniqueId, Date.now() + backoffFor(code, n));
  });
  ws.on('error', () => {});
}

async function tick() {
  if (live.size >= WS_BUDGET * 0.9) return;
  const now = Date.now();
  const due = WATCHLIST.filter(u => !live.has(u) && (cooldown.get(u) || 0) <= now);
  const status = await preflight(due);
  for (const [u, isLive] of Object.entries(status)) {
    if (isLive) { fails.delete(u); openWs(u); }
    else cooldown.set(u, now + );
  }
}

setInterval(tick, ); tick();

WebSocket Events

All event types sent over the WebSocket connection.

Filter to one event

Chat-only and gift-only feeds

Same socket, narrower filter. Drop-in starter for moderation dashboards, tip jars, OBS overlays, sentiment tracking. The multi-creator gift firehose (region/league filters, min-diamond threshold) is gated to Global Agency tier.

chat-tape.htmlRUNNING
Preview loads once visible.
live.start (webhook)Delivered to your registered webhook the moment a watched creator goes live. POSTed as JSON. Headers on every delivery: X-Tiktool-Event, X-Tiktool-Event-Id, X-Tiktool-Delivery-Id, X-Tiktool-Attempt, X-Tiktool-Webhook-Id, plus X-Tiktool-Signature and X-Tiktool-Timestamp (HMAC over the raw body using your webhook secret - verify both to confirm authenticity). See the /api/webhooks endpoint to register creators and events.

Fields

NameTypeDescription
event_idstringUnique id for this event (idempotency key - dedupe on it).
eventstring"live.start".
occurred_atstringISO timestamp when the event was generated.
is_testbooleantrue for deliveries fired via the test endpoint, false for real events.
creatorobject{ unique_id, nickname, avatar_url, user_id, follower_count } for the creator who went live.
liveobject{ room_id, started_at, live_url } for the live session.
_linksobjectConvenience links, e.g. { creator: "https://tik.tools/@username" }.

Example Payload

live.start (webhook).json
// POST https://your-app.com/hooks/tiktool
// X-Tiktool-Event: live.start
// X-Tiktool-Signature: <hmac>   X-Tiktool-Timestamp: 1750000000
{
  "event_id": "evt_01J...",
  "event": "live.start",
  "occurred_at": "2026-06-18T13:00:00.000Z",
  "is_test": false,
  "creator": {
    "unique_id": "charlidamelio",
    "nickname": "charli d'amelio",
    "avatar_url": "https://p16-sign.tiktokcdn.com/...",
    "user_id": "6569595380449902593",
    "follower_count": 155000000
  },
  "live": {
    "room_id": "7000000000000000000",
    "started_at": "2026-06-18T13:00:00.000Z",
    "live_url": "https://www.tiktok.com/@charlidamelio/live"
  },
  "_links": { "creator": "https://tik.tools/@charlidamelio" }
}
live.end (webhook)Mirror of live.start, delivered when a watched creator ends their stream. Same envelope and headers; the live object carries ended_at instead of started_at. Available on all paid plans alongside live.start.

Fields

NameTypeDescription
event_idstringUnique id for this event (dedupe key).
eventstring"live.end".
occurred_atstringISO timestamp when the event was generated.
creatorobjectSame creator shape as live.start.
liveobject{ room_id, ended_at } for the session that ended.

Example Payload

live.end (webhook).json
{
  "event_id": "evt_01J...",
  "event": "live.end",
  "occurred_at": "2026-06-18T14:30:00.000Z",
  "is_test": false,
  "creator": { "unique_id": "charlidamelio", "nickname": "charli d'amelio" },
  "live": { "room_id": "7000000000000000000", "ended_at": "2026-06-18T14:30:00.000Z" }
}
chatA viewer sent a chat message.

Fields

NameTypeDescription
userWebcastUserThe user who sent the message. Includes a `payGrade` (SDK) / `level` (raw WS) field carrying the sender's TikTok gifter level (1-50, higher = more coins spent platform-wide). Present on every user-bearing event.
commentstringThe chat message text
starredobject | undefinedPresent only for starred (paid highlighted) messages. Contains { claps: number, score: number }
languagestring | undefined**v3 (2026-06-07)**: ISO 639-1 language code TikTok auto-detected for this comment ("en", "tr", "un" = unknown). Use for per-message routing or translation.
messageUuidstring | undefined**v3 (2026-06-07)**: stable per-message UUID. Used by the `imDelete` moderation event so consumers can correlate "this chat got deleted" back to the original line.
replyToUserWebcastUser | undefined**v3 (2026-06-07)**: set when this comment is a reply to another viewer (~8% of chats). Contains the target viewer's `uniqueId`, `nickname`, and avatar URLs.
protoVersionnumber | undefined**v3 (2026-06-07)**: 3 when this chat carries v3-only fields, otherwise 2. Lets v1/v2 consumers branch on schema age without sniffing field presence.

Example Payload

chat.json
{
  "event": "chat",
  "data": {
    "type": "chat",
    "user": {
      "id": "123",
      "nickname": "John",
      "uniqueId": "john123",
      "level": 17                      // raw WS field name (Gifter level 1-50)
    },
    "comment": "Hello!",
    "starred": { "claps": 3, "score": 1200 }
  }
}

// SDK consumers (Node / Python TikTokLive client) receive the same field
// renamed to user.payGrade for consistency with the official tikfinity /
// tiktok-live-connector typings.
// Same applies to every other event with a "user" field (gift, like,
// follow, member, share, social, roomPin, etc).
likeA viewer liked the stream.

Fields

NameTypeDescription
userWebcastUserThe user who liked
likeCountnumberNumber of likes in this event
totalLikesnumberTotal likes on the stream

Example Payload

like.json
{ "event": "like", "data": { "type": "like", "user": { "id": "123", "uniqueId": "fan99" }, "likeCount": 15, "totalLikes": 4200 } }
giftA viewer sent a virtual gift.

Fields

NameTypeDescription
userWebcastUserThe gifter
giftIdnumberGift identifier
giftNamestringDisplay name of the gift
repeatCountnumberNumber of gifts in combo
diamondCountnumberDiamond value per gift
repeatEndbooleanWhether the gift combo has ended
transactionIdstring | undefined**v3 (2026-06-07)**: stable per-gift transaction UUID (hex). Use as dedup key across upstream retries - the same combo will always carry the same transactionId even if `repeatCount` updates twice.
senderUserIdstring | undefined**v3 (2026-06-07)**: explicit sender id parsed from the new `{"sender_id":"..."}` envelope. Mirrors `user.id` but is delivered separately on the wire so it remains available even on truncated gift frames.
relationship{ joinDayNumber?, fromUser?, toUser? } | undefined**v3 (2026-06-07)**: relationship metadata when TikTok attaches it. `joinDayNumber` = how many days the gifter has been following the creator. Use for "Day 131 fan sent X" overlays.
protoVersionnumber | undefined**v3 (2026-06-07)**: 3 when this gift carries v3-only fields, otherwise 2.

Example Payload

gift.json
{ "event": "gift", "data": { "type": "gift", "user": { "uniqueId": "generous" }, "giftName": "Rose", "repeatCount": 5, "diamondCount": 1, "repeatEnd": true, "transactionId": "202606070219033A38D7B466DD66036089", "senderUserId": "7595581591918347286", "relationship": { "joinDayNumber": 131 }, "protoVersion": 3 } }
memberA viewer joined the live stream.

Fields

NameTypeDescription
userWebcastUserThe user who joined
actionstringJoin action type
actionCodenumber | undefined**v3 (2026-06-07)**: granular numeric subcode (38, 44, ...). Finer-grained than `action`.
entrySourcestring | undefined**v3 (2026-06-07)**: where the viewer came from. Examples: "homepage_hot-live_cell" (Discover hot row), "follow-tab", "share-link". Critical creator-analytics signal - shows what surface drives a creator's viewer base.
entryActionstring | undefined**v3 (2026-06-07)**: how the viewer entered. "draw" = TikTok algorithmically pulled the viewer in. "click" = explicit click (search / follow). "other" = uncategorised.
entryTypestring | undefined**v3 (2026-06-07)**: "rec" when TikTok recommended this stream. Absent on direct joins.
protoVersionnumber | undefined**v3 (2026-06-07)**: 3 when this member event carries v3-only fields, otherwise 2.

Example Payload

member.json
{ "event": "member", "data": { "type": "member", "user": { "uniqueId": "viewer1" }, "action": "join", "actionCode": 38, "entrySource": "homepage_hot-live_cell", "entryAction": "draw", "entryType": "rec", "protoVersion": 3 } }
socialA social action occurred (follow, share).

Fields

NameTypeDescription
userWebcastUserThe user
actionstringSocial action (follow, share)

Example Payload

social.json
{ "event": "social", "data": { "type": "social", "user": { "uniqueId": "newfan" }, "action": "follow" } }
roomUserSeqPeriodic viewer count update.

Fields

NameTypeDescription
totalViewersnumberTotal unique viewers
viewerCountnumberCurrent concurrent viewers

Example Payload

roomUserSeq.json
{ "event": "roomUserSeq", "data": { "type": "roomUserSeq", "totalViewers": 15000, "viewerCount": 342 } }
subscribeA viewer subscribed to the streamer.

Fields

NameTypeDescription
userWebcastUserThe subscriber
subMonthnumberSubscription month count

Example Payload

subscribe.json
{ "event": "subscribe", "data": { "type": "subscribe", "user": { "uniqueId": "subscriber1" }, "subMonth": 3 } }
emoteChatA viewer sent an emote in chat.

Fields

NameTypeDescription
userWebcastUserThe user
emoteIdstringEmote identifier
emoteUrlstringURL of the emote image

Example Payload

emoteChat.json
{ "event": "emoteChat", "data": { "type": "emoteChat", "user": { "uniqueId": "emojifan" }, "emoteId": "123", "emoteUrl": "https://..." } }
battlePK lifecycle. Fires when a battle starts, changes status, or ends.

Fields

NameTypeDescription
battleIdstringBattle identifier
statusnumber1 = ACTIVE, 2 = STARTING, 3 = ENDED, 4 = PREPARING
battleDurationnumberTotal battle duration in seconds (typically 300)
teamsBattleTeam[]Array of competing teams
extraHostUserIdsstring[] | undefined**v3 (2026-06-07)**: additional host user IDs surfaced on multi-guest battles (host pairs beyond the primary two).
layoutSubtypestring | undefined**v3 (2026-06-07)**: TikTok layout subtype ("cohost_normal_expand_2", ...) - overlays use this to pick the right PK frame template.
protoVersionnumber | undefined**v3 (2026-06-07)**: 3 when v3-only fields are present, otherwise 2.

Example Payload

battle.json
{ "event": "battle", "data": { "type": "battle", "battleId": "456", "status": 1, "battleDuration": 300, "teams": [{ "hostUserId": "1", "score": 100 }], "extraHostUserIds": ["6832673084131902469"], "layoutSubtype": "cohost_normal_expand_2", "protoVersion": 3 } }
battleArmiesPeriodic score update during a PK with per-host MVP breakdown. Contributors are sorted highest-score first per side. Fires every ~2-6s while a battle is active. To detect when a battle has finished, see the status + secsRemaining notes below - do NOT wait on a status 3/5 frame (those do not exist in the current protocol).

Fields

NameTypeDescription
battleIdstringBattle session tag. Often "0" on the opening and early frames - use matchId as the stable identity instead.
matchIdstringStable match ID across a multi-round PK. Use this as the battle key (battleId can be "0").
sessionIdstringPer-round session ID
statusnumberBattle phase. **1 and 4 are both ACTIVE score frames** - treat them identically; status 4 is the most common live frame and carries the countdown tail, so do not filter it out. **2 = settle/end** (fires once when the round finishes). Status 3 and 5 do NOT appear in the current protocol - do not key completion on them. The final countdown frame (secsRemaining = 0) almost always arrives as a status 4 or status 2 frame, never status 1.
serverTsMsnumberTikTok server clock (ms epoch) at frame emit
durationSecnumberTotal battle duration in seconds
secsRemainingnumberSeconds remaining, derived from serverTsMs (drift-free). **secsRemaining === 0 is the primary completion signal** and is the most reliable way to finalize a battle.
hostsBattleHost[]One entry per PK-side host. Multi-guest PKs include up to 4 per side. Each carries hostUserId, teamTotalScore, teamIdx and a sorted contributors[] array (MVP first).
transactionIdstring | undefined**v3 (2026-06-07)**: stable per-frame transaction UUID (hex). Use as a dedup key to avoid double-counting score updates when upstream retries a frame.
protoVersionnumber | undefined**v3 (2026-06-07)**: 3 when transactionId is present, otherwise 2.

Example Payload

battleArmies.json
{
  "event": "battleArmies",
  "data": {
    "type": "battleArmies",
    "battleId": "7298765432109876543",
    "status": 4,
    "matchId": "...",
    "sessionId": "...",
    "serverTsMs": 1747654321000,
    "durationSec": 300,
    "secsRemaining": 187,
    "hosts": [
      {
        "hostUserId": "6892636847263982593",
        "teamTotalScore": 12450,
        "teamIdx": 0,
        "contributors": [
          { "userId": "111", "score": 8000, "nickname": "MVP_Gifter" },
          { "userId": "222", "score": 3000, "nickname": "OtherFan" },
          { "userId": "333", "score": 1450, "nickname": "ThirdFan" }
        ]
      },
      {
        "hostUserId": "7012345678901234567",
        "teamTotalScore": 9800,
        "teamIdx": 1,
        "contributors": []
      }
    ]
  }
}

// hosts[0].contributors[0] is always the MVP on that side.

// ── Detecting battle completion (read this if you persist battle results) ──
// status 1 and 4 are BOTH active score frames - process them the same way.
// The terminal/settle frame is status 2. status 3 and 5 do not exist in the
// current protocol; do not wait for them. The secsRemaining = 0 frame almost
// always arrives on a status 4 or status 2 frame, so a consumer that only
// reads status 1 frames will never observe secsRemaining = 0 and will leave
// battles unfinalized.
//
// Recommended finalize logic (priority order):
//   1. secsRemaining === 0 on any frame   -> round finished (primary signal)
//   2. status === 2                        -> settle frame (confirms result)
//   3. WALL-CLOCK fallback (required):
//        finalizeAt = startedAt + durationSec * 1000 + 30000
//      Battles that end early (host cancels the PK, opponent leaves, or the
//      host ends the stream) emit NO terminal frame - the event stream simply
//      stops. Finalize with the last known active score once finalizeAt passes.
//   4. IDLE-GAP fallback: if an active battle receives no battleArmies frame
//      for 30s, finalize it with the last score (frames normally fire every
//      2-6s). Catches host-cancel / disconnect.
//
// matchId is the stable battle key. Keep the last active score per matchId and
// commit it when any of the conditions above fire.
battleItemCardBooster / power-up card activated during a PK: x2 / x3 multipliers, gloves (crit), mist, thunder, extra-time, match-guide. Carries drop-in overlay assets from TikTok CDN.

Fields

NameTypeDescription
battleIdstringBattle session tag
cardTypenumber2 = gloves / crit, 3 = mist, 4 = match guide, 11 = booster x2/x3, ...
effectstring'gloves' | 'mist' | 'booster_x2' | 'booster_x3' | 'match_guide' | 'thunder' | 'extra_time' | raw key
multipliernumber2 or 3 for booster_x2 / booster_x3, otherwise 0
senderUserIdstringUser ID of the gifter who triggered the card
senderNicknamestringDisplay name of the trigger
senderUniqueIdstringUsername of the trigger
senderAvatarUrlstringAvatar URL of the trigger
activatedAtSecnumberWhen the card activated (epoch seconds)
durationSecnumberHow long the effect lasts
endsAtSecnumberWhen the effect ends (epoch seconds)
iconUrlstringFull TikTok CDN URL for the card art
iconKeystringShort identifier like 'card_mist_v3' or 'card_crit_v3'
accentColorstringHex accent color (#BCD9E0 mist, #E0D4BC gloves, ...)

Example Payload

battleItemCard.json
{
  "event": "battleItemCard",
  "data": {
    "type": "battleItemCard",
    "battleId": "7298765432109876543",
    "cardType": 11,
    "effect": "booster_x3",
    "multiplier": 3,
    "senderUserId": "111",
    "senderNickname": "Big_Spender",
    "senderUniqueId": "big_spender",
    "senderAvatarUrl": "https://p16-sign.tiktokcdn-us.com/...",
    "activatedAtSec": 1747654321,
    "durationSec": 30,
    "endsAtSec": 1747654351,
    "iconUrl": "https://...",
    "iconKey": "card_booster_x3",
    "accentColor": "#FFCB47"
  }
}
questionA viewer asked a Q&A question.

Fields

NameTypeDescription
userWebcastUserThe user who asked
questionTextstringThe question text

Example Payload

question.json
{ "event": "question", "data": { "type": "question", "user": { "uniqueId": "curious" }, "questionText": "What game is this?" } }
controlStream control event (e.g. stream ended).

Fields

NameTypeDescription
actionnumberControl action code (3 = stream ended)

Example Payload

control.json
{ "event": "control", "data": { "type": "control", "action": 3 } }
envelopeTreasure box / envelope event.

Fields

NameTypeDescription
envelopeIdstringEnvelope identifier
diamondCountnumberDiamond value

Example Payload

envelope.json
{ "event": "envelope", "data": { "type": "envelope", "envelopeId": "789", "diamondCount": 50 } }
roomPinA chat message was pinned/starred by the host or a moderator.

Fields

NameTypeDescription
userWebcastUserThe user who wrote the pinned message
commentstringThe pinned comment text
actionnumberPin action: 1 = pin, 2 = unpin
durationSecondsnumberHow long the pin lasts (e.g. 60)
pinnedAtnumberTimestamp when pinned (ms)
originalMsgTypestringOriginal message type (e.g. "WebcastChatMessage")
originalMsgIdstringID of the original chat message
operatorUserIdstringUser ID of who pinned the message

Example Payload

roomPin.json
{ "event": "roomPin", "data": { "type": "roomPin", "user": { "uniqueId": "viewer1", "nickname": "Viewer" }, "comment": "Great stream!", "action": 1, "durationSeconds": 60, "pinnedAt": 1773564373063, "originalMsgType": "WebcastChatMessage", "originalMsgId": "7617400904630995734", "operatorUserId": "7444599004337652758" } }
superFanA viewer became a Super Fan of the streamer. Detected from WebcastBarrageMessage via displayType.

Fields

NameTypeDescription
userWebcastUserThe user who became a Super Fan
levelnumberSuper Fan level achieved (e.g. 10)
displayTypestringRaw TikTok display type string

Example Payload

superFan.json
{ "event": "superFan", "data": { "type": "superFan", "user": { "uniqueId": "loyalfan", "nickname": "LoyalFan" }, "level": 10, "displayType": "ttlive_superFan_commentNotif_someoneBecameSuperFan" } }
superFanJoinA Super Fan joined the live stream. Distinguished from regular member joins by displayType.

Fields

NameTypeDescription
userWebcastUserThe Super Fan who joined
displayTypestringRaw TikTok display type string

Example Payload

superFanJoin.json
{ "event": "superFanJoin", "data": { "type": "superFanJoin", "user": { "uniqueId": "superfan1", "nickname": "SuperFan" }, "displayType": "ttlive_superFan_commentNotif_superFanJoined" } }
superFanBoxA viewer sent a Super Fan Box gift. This is a special envelope subtype detected via business type field.

Fields

NameTypeDescription
userWebcastUserThe user who sent the Super Fan Box
envelopeIdstringEnvelope identifier
diamondCountnumberDiamond value of the Super Fan Box

Example Payload

superFanBox.json
{ "event": "superFanBox", "data": { "type": "superFanBox", "user": { "uniqueId": "generous", "nickname": "Generous" }, "envelopeId": "123456", "diamondCount": 100 } }
collabGiftCollaborative / co-host gifting: one viewer sends a gift that credits every creator currently co-streaming in the LinkMic (co-host) session at once. Fires on each gift inside a co-host room. The structural fields (giftId, diamond totals, co-host count, roomId) are visible on every plan; the sender + co-host display identities (nickname, avatar) are unmasked only on Pro and above - sub-Pro callers receive masked nicknames and null avatars plus a _masked flag. Numeric userIds are always present so you can resolve identities yourself via resolve_user_ids on any tier. Note: TikTok does not ship a @handle (uniqueId) inside this message - only userId, nickname and avatar per co-host.

Fields

NameTypeDescription
giftIdnumberTikTok gift catalog id. Cross-reference with /webcast/gift_info for name + diamond cost.
giftNamestringResolved gift name when known, otherwise empty.
repeatCountnumberSequence counter for the gift within the combo.
comboCountnumberCombo counter (mirrors repeatCount).
diamondValuenumberCollab session diamond total carried on the gift.
diamondTotalnumberCollab session score total carried on the gift.
sender{ uniqueId, nickname, userId, avatar }The viewer who sent the gift. userId is authoritative; nickname/avatar are not shipped by TikTok for the sender and are masked below Pro.
coHosts{ userId, nickname, uniqueId, avatar }[]Every co-streaming creator credited by this gift. nickname + avatar unmasked on Pro+, masked below. uniqueId is empty (not shipped by TikTok).
recipients{ userId, nickname, uniqueId, avatar }[]Alias of coHosts for SDK ergonomics.
coHostCountnumberNumber of co-hosts credited on this gift instance.
roomIdstring | nullRoom the gift fired in, when resolvable.
_maskedboolean | undefinedPresent and true when identities were masked (sub-Pro). Absent on Pro+.
_upgrade_requiredstring | undefinedTier needed to unmask identities ("pro"). Present only when masked.

Example Payload

collabGift.json
{ "event": "collabGift", "data": { "type": "collabGift", "giftId": 1233, "giftName": "Collab Gift", "repeatCount": 1, "comboCount": 1, "diamondValue": 62819, "diamondTotal": 62816, "coHostCount": 3, "sender": { "uniqueId": "", "nickname": "", "userId": "7387482728746353696" }, "coHosts": [ { "userId": "7327728695157376032", "nickname": "Chloe", "uniqueId": "", "avatar": "https://..." }, { "userId": "6761121990676218885", "nickname": "Danielle", "uniqueId": "", "avatar": "https://..." } ], "recipients": [ ], "roomId": "7655296008007584534" } }
// Sub-Pro callers receive the same shape with nickname masked (e.g. "•••••"), avatar null, plus "_masked": true, "_upgrade_required": "pro".
barrageRaw barrage event from TikTok. Captures all WebcastBarrageMessage data including announcements, special effects, and other unrecognized signals. Useful as a raw feed dump for debugging.

Fields

NameTypeDescription
userWebcastUserThe associated user (if any)
labelstringBarrage label text
displayTypestringRaw TikTok display type string for classification
contentstringBarrage content text

Example Payload

barrage.json
{ "event": "barrage", "data": { "type": "barrage", "user": { "uniqueId": "viewer1" }, "label": "BecomingSuperFanLv10", "displayType": "ttlive_superFan_commentNotif_someoneBecameSuperFan", "content": "{0:user} just became a Super Fan" } }
captionv3 (2026-06-07): TikTok native auto-captions on the LIVE WebSocket. Each frame carries one caption window (start..end ms) with text + isFinal flag. Independent of the operator-managed TikTok Live Captions product - this is what TikTok's own viewer UI renders for accessibility and Discover.

Fields

NameTypeDescription
textstringCaption text (utf-8). Partial windows arrive while the speaker talks; the final window for the same segment has isFinal=true.
languagestring | undefined**v3 (2026-06-07)**: ISO 639-1 language code of the spoken caption ("en", "ko", "es"). Detect mid-stream language switches without a separate translation request.
isFinalbooleantrue on the segment-final caption, false on partial / streaming updates.
startedAtMsnumberCaption window start (ms epoch on TikTok server clock).
endsAtMsnumberCaption window end (ms epoch).
protoVersionnumberAlways 3 - this event was added in the v3 schema.

Example Payload

caption.json
{ "event": "caption", "data": { "type": "caption", "text": "Hello everyone welcome to the stream", "language": "en", "isFinal": true, "startedAtMs": 1780798747292, "endsAtMs": 1780798748292, "protoVersion": 3 } }
linkMicOpponentGiftv3 (2026-06-07): per-gift breakdown from the OPPONENT side of a PK. TikTok now ships every gift sent on the opposing host's stream as a separate frame, so PK dashboards no longer need to relay-tap the opponent's room.

Fields

NameTypeDescription
senderUserIdstringTikTok user id of the opponent-side gifter.
opponentRoomIdstringRoom id of the opposing host.
giftIdnumberGift identifier (same id-space as the main /webcast/gift_info catalog).
giftPictureUrlstringFull TikTok CDN URL for the gift image.
startedAtMsnumberGift animation start (ms epoch).
endsAtMsnumberGift animation end (ms epoch).
transactionIdstringStable per-gift UUID (hex) - use as dedup key.
protoVersionnumberAlways 3 - this event was added in the v3 schema.

Example Payload

linkMicOpponentGift.json
{ "event": "linkMicOpponentGift", "data": { "type": "linkMicOpponentGift", "senderUserId": "7366557450243605522", "opponentRoomId": "7569885742970635284", "giftId": 5655, "giftPictureUrl": "https://...", "startedAtMs": 1780798748470, "endsAtMs": 1780798748589, "transactionId": "2026060710190765D03BD889F88B8F065D", "protoVersion": 3 } }
imDeletev3 (2026-06-07): chat moderation delete. Correlate back to the original message via ChatEvent.messageUuid (also v3).

Fields

NameTypeDescription
deletedMsgIdstringTikTok message id of the chat that was deleted.
protoVersionnumberAlways 3.

Example Payload

imDelete.json
{ "event": "imDelete", "data": { "type": "imDelete", "deletedMsgId": "7648470148944628496", "protoVersion": 3 } }
goalUpdatev3 (2026-06-07): stream goal progress (subscriber goal, gift goal, watch-time goal).

Fields

NameTypeDescription
goalKeystringTikTok goal key, e.g. "live_goal_indicator_stream_goal".
creatorUserIdstringUser id of the creator running the goal.
contributionLevelnumberCurrent contribution level (1..N).
metadataJsonstringRaw JSON: { is_first_contribute, challenge_type, update_source, ... }.
protoVersionnumberAlways 3.

Example Payload

goalUpdate.json
{ "event": "goalUpdate", "data": { "type": "goalUpdate", "goalKey": "live_goal_indicator_stream_goal", "creatorUserId": "7280230115615114245", "contributionLevel": 4, "metadataJson": "{\"is_first_contribute\":false,\"challenge_type\":0}", "protoVersion": 3 } }
privilegeAdvancev3 (2026-06-07): viewer privilege tier-up notification. Carries the privilege key + TikTok CDN background frame URLs so overlays can render the banner without a separate fetch.

Fields

NameTypeDescription
privilegeKeystringPrivilege key, e.g. "gallery_all_lit_up_d".
actionstringAction descriptor, e.g. "gift_broadcast_msg".
bgUrlsstring[]TikTok CDN background frame URLs.
protoVersionnumberAlways 3.

Example Payload

privilegeAdvance.json
{ "event": "privilegeAdvance", "data": { "type": "privilegeAdvance", "privilegeKey": "gallery_all_lit_up_d", "action": "gift_broadcast_msg", "bgUrls": ["https://p16-webcast.tiktokcdn.com/..."], "protoVersion": 3 } }
commentTrayv3 (2026-06-07): comment tray UI state change.

Fields

NameTypeDescription
trayCountnumberNumber of items in the tray.
updatedAtMsnumberUpdate timestamp (ms epoch).
relatedMsgIdstringSnowflake id of the related message.
protoVersionnumberAlways 3.

Example Payload

commentTray.json
{ "event": "commentTray", "data": { "type": "commentTray", "trayCount": 3978, "updatedAtMs": 1780798747613, "relatedMsgId": "7648470148944628496", "protoVersion": 3 } }
linkLayerv3 (2026-06-07): PK / link-mic negotiation event (invite, cancel, accept, source change).

Fields

NameTypeDescription
actionnumberTop-level action code (11 = invite, ...).
subActionnumberSub-action code.
sourceTypestring | undefinedWhere the link originated. Examples: "inviteCancel_new_arc", "SOURCE_TYPE_FRIEND_LIST".
targetUserIdstring | undefinedUser id of the link target.
protoVersionnumberAlways 3.

Example Payload

linkLayer.json
{ "event": "linkLayer", "data": { "type": "linkLayer", "action": 11, "subAction": 4, "sourceType": "SOURCE_TYPE_FRIEND_LIST", "targetUserId": "7648408274391419681", "protoVersion": 3 } }
linkMicCo-host (link-mic) roster/state. TikTok only embeds full host profiles in `users[]` on some frames; most link-mic frames are layout/state updates that ship ONLY the co-host's numeric userId (in `extras`, usually field "5") with an empty `users[]`. That empty array is expected, not a bug or a tier limit. Take the userId and resolve it to uniqueId/nickname/avatar with the resolve_user_ids endpoint (sandbox tier - works on every plan, batchable to 20, server-cached).

Fields

NameTypeDescription
actionstringRoster action (e.g. join / leave / update).
usersWebcastUser[]Full host profiles when TikTok includes them - frequently EMPTY on layout/state frames. When empty, read the co-host userId from `extras["5"]` and resolve it (see snippet).
extrasRecord<string,any>Raw unmapped protobuf fields. For link-mic, field "5" is the co-host userId; fields "6"/"7" are link-mic type/score metadata.
protoVersionnumberAlways 3.

Example Payload

linkMic.json
// Real link-mic frame: users[] empty, co-host userId lives in extras["5"].
// { "event": "linkMic", "data": { "users": [], "extras": { "5": "7529001445733385224", "6": 5, "7": 7986 } } }

// Resolve the id to a profile with /webcast/resolve_user_ids (works on every tier).
const API = 'https://api.tik.tools';
client.on('linkMic', async (e) => {
  const userId = e.users?.[0]?.userId || e.extras?.['5'];
  if (!userId) return;
  const { data } = await fetch(`${API}/webcast/resolve_user_ids?apiKey=${API_KEY}`, {
    method: 'POST', headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ user_ids: [String(userId)] })
  }).then(r => r.json());
  const u = data[String(userId)]; // { userId, username, nickname, avatarUrl }
  console.log('co-host:', u.username, u.nickname, u.avatarUrl);
});

// Use the link event for join/leave TIMING (its relatedUser is usually empty too).
client.on('link', (e) => console.log('link state change:', e.action, e.subAction));
giftPanelUpdatev3 (2026-06-07): real-time gift catalog change for the room. Use as a cache-bust signal for local /webcast/gift_info copies - no need to re-fetch on every frame.

Fields

NameTypeDescription
panelIdstringPanel id (or room id) the update applies to.
updatedAtSecnumberUpdate timestamp (epoch seconds, NOT ms).
protoVersionnumberAlways 3.

Example Payload

giftPanelUpdate.json
{ "event": "giftPanelUpdate", "data": { "type": "giftPanelUpdate", "panelId": "7648436315382123281", "updatedAtSec": 1780798748, "protoVersion": 3 } }
gameServerFeaturev3 (2026-06-07): TikTok Gaming live integration descriptor (e.g. "106 Roblox", "2348 Marvel Rivals").

Fields

NameTypeDescription
rawTagstringRaw packed game tag + title.
protoVersionnumberAlways 3.

Example Payload

gameServerFeature.json
{ "event": "gameServerFeature", "data": { "type": "gameServerFeature", "rawTag": "106Roblox", "protoVersion": 3 } }
anchorToolModificationv3 (2026-06-07): creator modified a panel/widget (video anchor, Q&A topic, etc.).

Fields

NameTypeDescription
toolPayloadstringTool descriptor + ids.
protoVersionnumberAlways 3.

Example Payload

anchorToolModification.json
{ "event": "anchorToolModification", "data": { "type": "anchorToolModification", "toolPayload": "video_anchor 7392706231896379905", "protoVersion": 3 } }
shareRevenueNoticev3 (2026-06-07): share-revenue subscriber count change notice.

Fields

NameTypeDescription
creatorUserIdstringCreator user id this notice applies to.
protoVersionnumberAlways 3.

Example Payload

shareRevenueNotice.json
{ "event": "shareRevenueNotice", "data": { "type": "shareRevenueNotice", "creatorUserId": "7648373500877261590", "protoVersion": 3 } }
viewerPicksUpdatev3 (2026-06-07): "viewer picks" - TikTok-promoted gift highlights shown to viewers as quick picks.

Fields

NameTypeDescription
pickTypenumberPick category (2 = standard).
payloadstringPacked gift highlight list.
protoVersionnumberAlways 3.

Example Payload

viewerPicksUpdate.json
{ "event": "viewerPicksUpdate", "data": { "type": "viewerPicksUpdate", "pickType": 2, "payload": "Kiss 5655, Bullet 7934", "protoVersion": 3 } }
fanTicketv3 (2026-06-07): fan-club ticket flow event.

Fields

NameTypeDescription
rawPayloadstringPacked fan-ticket descriptor.
protoVersionnumberAlways 3.

Example Payload

fanTicket.json
{ "event": "fanTicket", "data": { "type": "fanTicket", "rawPayload": "mg_default ...", "protoVersion": 3 } }
giftDynamicRestrictionv3 (2026-06-07): dynamic gift-catalog restriction flip (per-room availability / age-gating).

Fields

NameTypeDescription
rawPayloadstringPacked restriction descriptor.
protoVersionnumberAlways 3.

Example Payload

giftDynamicRestriction.json
{ "event": "giftDynamicRestriction", "data": { "type": "giftDynamicRestriction", "rawPayload": "...", "protoVersion": 3 } }
inRoomBannerv3 (2026-06-07): in-room activity banner (challenges, donation drives) carrying a JSON descriptor.

Fields

NameTypeDescription
activityJsonstringTikTok activity indicator JSON, e.g. {"activity_indicator":{"currents":[...]}}.
protoVersionnumberAlways 3.

Example Payload

inRoomBanner.json
{ "event": "inRoomBanner", "data": { "type": "inRoomBanner", "activityJson": "{\"activity_indicator\":{\"currents\":[]}}", "protoVersion": 3 } }
battlePunishFinishv3 (2026-06-07): PK punishment phase finished. Loser-side punishment screen ended.

Fields

NameTypeDescription
battleIdstringBattle id this punishment belonged to.
punishedUserIdstringUser id of the loser-side host.
punishTypenumberPunishment subtype code.
sessionIdstringBattle session id.
protoVersionnumberAlways 3.

Example Payload

battlePunishFinish.json
{ "event": "battlePunishFinish", "data": { "type": "battlePunishFinish", "battleId": "7648485765504125714", "punishedUserId": "7204121790628185094", "punishType": 1, "sessionId": "7648485804360108818", "protoVersion": 3 } }
battleNoticev3 (2026-06-07): PK notice (version-mismatch toasts, invite-failure messages).

Fields

NameTypeDescription
noticeCodenumberNotice category code.
noticeKeystringLocalized notice key, e.g. "ttlive_liveMatch_inviteFail_versionUpdateTo".
noticeTextstringDecoded notice text in TikTok's active locale.
protoVersionnumberAlways 3.

Example Payload

battleNotice.json
{ "event": "battleNotice", "data": { "type": "battleNotice", "noticeCode": 6, "noticeKey": "ttlive_liveMatch_inviteFail_versionUpdateTo", "noticeText": "Update your TikTok...", "protoVersion": 3 } }
hostBoardv3 (2026-06-07): host leaderboard board update.

Fields

NameTypeDescription
rawPayloadstringBoard snapshot payload.
protoVersionnumberAlways 3.

Example Payload

hostBoard.json
{ "event": "hostBoard", "data": { "type": "hostBoard", "rawPayload": "...", "protoVersion": 3 } }
pollv3 (2026-06-07): in-stream poll lifecycle (open / vote / close).

Fields

NameTypeDescription
pollIdstringSnowflake id of the poll.
actionnumberAction code (2 = update).
statusnumberPoll status.
questionPayloadstringQuestion text / packed body.
optionsPayloadstringOptions text / packed body.
protoVersionnumberAlways 3.

Example Payload

poll.json
{ "event": "poll", "data": { "type": "poll", "pollId": "7648486279037946655", "action": 2, "status": 2, "questionPayload": "LETS FILL UP THE GALLERY", "optionsPayload": "HeartMe | Like | Share", "protoVersion": 3 } }
competitionv3 (2026-06-07): cross-stream competition event (gift duels, group challenges).

Fields

NameTypeDescription
competitionTypenumberCompetition category code.
layoutSubtypestring | undefinedLayout subtype overlays use to render the competition frame ("cohost_take_stage_v2_2", ...).
protoVersionnumberAlways 3.

Example Payload

competition.json
{ "event": "competition", "data": { "type": "competition", "competitionType": 8, "layoutSubtype": "cohost_take_stage_v2_2", "protoVersion": 3 } }
streamStatusv3 (2026-06-07): stream status flip (recording state, content-classification rechecks).

Fields

NameTypeDescription
rawPayloadstringPacked status payload.
protoVersionnumberAlways 3.

Example Payload

streamStatus.json
{ "event": "streamStatus", "data": { "type": "streamStatus", "rawPayload": "...", "protoVersion": 3 } }
battleGameplayv3 (2026-06-07): PK mini-game gameplay state.

Fields

NameTypeDescription
gameplayIdstringGameplay session id.
gameplayTypenumberGame category code.
subTypenumberGame subtype.
protoVersionnumberAlways 3.

Example Payload

battleGameplay.json
{ "event": "battleGameplay", "data": { "type": "battleGameplay", "gameplayId": "7648486290535369480", "gameplayType": 2, "subType": 1, "protoVersion": 3 } }
aiSummaryv3 (2026-06-07): TikTok AI summary of the room (entry-time recap).

Fields

NameTypeDescription
summarystringAI summary text in TikTok's active locale.
scenarioKeystringScenario key, e.g. "web_enter_room_asr_summary".
iconUrlstringTikTok CDN icon URL.
labelKeystringLabel key, e.g. "ttlive_AIsummary_viewer_label".
displayDurationMsnumberHow long to display (ms).
protoVersionnumberAlways 3.

Example Payload

aiSummary.json
{ "event": "aiSummary", "data": { "type": "aiSummary", "summary": "Casual dance session with spontaneous singing", "scenarioKey": "web_enter_room_asr_summary", "iconUrl": "https://...", "labelKey": "ttlive_AIsummary_viewer_label", "displayDurationMs": 3000, "protoVersion": 3 } }
giftGalleryv3 (2026-06-07): host-side gift wall snapshot.

Fields

NameTypeDescription
transactionIdstringStable per-snapshot UUID (hex).
protoVersionnumberAlways 3.

Example Payload

giftGallery.json
{ "event": "giftGallery", "data": { "type": "giftGallery", "transactionId": "202606070317035805FCD15954308C96D1", "protoVersion": 3 } }
cohostLayoutUpdatev3 (2026-06-07): cohost layout subtype change.

Fields

NameTypeDescription
layoutSubtypestringCohost layout subtype ("cohost_normal_expand_4", ...).
protoVersionnumberAlways 3.

Example Payload

cohostLayoutUpdate.json
{ "event": "cohostLayoutUpdate", "data": { "type": "cohostLayoutUpdate", "layoutSubtype": "cohost_normal_expand_4", "protoVersion": 3 } }
fansEventv3 (2026-06-07): fan-club event (tier-up, community refresh).

Fields

NameTypeDescription
fanTypenumberFan category code.
eventKeystringFan-club event key, e.g. "ttlive_fanClub_communityHeartMe_tierUp_club".
protoVersionnumberAlways 3.

Example Payload

fansEvent.json
{ "event": "fansEvent", "data": { "type": "fansEvent", "fanType": 7, "eventKey": "ttlive_fanClub_communityHeartMe_tierUp_club", "protoVersion": 3 } }
linkScreenChangev3 (2026-06-07): PK split-screen layout flip (1v1 / 1vN / cohost mode swap).

Fields

NameTypeDescription
changeTypenumberChange category.
sessionInfostringPacked session ids ("hostId_opponentId_subType").
protoVersionnumberAlways 3.

Example Payload

linkScreenChange.json
{ "event": "linkScreenChange", "data": { "type": "linkScreenChange", "changeType": 2, "sessionInfo": "7648433431395453718_16994212205653601499_0", "protoVersion": 3 } }
roomStickerv3 (2026-06-07): room-wide sticker drop (host posted a labelled sticker).

Fields

NameTypeDescription
stickerPayloadstringSticker label + color + descriptor.
protoVersionnumberAlways 3.

Example Payload

roomSticker.json
{ "event": "roomSticker", "data": { "type": "roomSticker", "stickerPayload": "My label - BAYAN NG KONOHA - #000000", "protoVersion": 3 } }
bottomMessagev3 (2026-06-07): bottom-bar safety / risk notice ("LIVE health warning", "risk_notice ...").

Fields

NameTypeDescription
noticeKeystringNotice localization key (e.g. "pm_mt_health_warning").
anchorUserIdstringAnchor (creator) user id.
durationSecnumberHow long the notice stays.
riskKeystringRisk descriptor.
protoVersionnumberAlways 3.

Example Payload

bottomMessage.json
{ "event": "bottomMessage", "data": { "type": "bottomMessage", "noticeKey": "pm_mt_health_warning", "anchorUserId": "6925591924343669765", "durationSec": 599, "riskKey": "risk_notice7648487247360672544", "protoVersion": 3 } }
oecLiveShoppingv3 (2026-06-07): OEC live-shopping event (product card display / hide).

Fields

NameTypeDescription
statusnumberStatus code (1 = show, 3 = update, ...).
productInfostringProduct title + price.
protoVersionnumberAlways 3.

Example Payload

oecLiveShopping.json
{ "event": "oecLiveShopping", "data": { "type": "oecLiveShopping", "status": 1, "productInfo": "Make Me Melt Makeup Removing Balm $23.80", "protoVersion": 3 } }
rankTextv3 (2026-06-07): rank text update (top-viewer announcement template).

Fields

NameTypeDescription
templateKeystringTemplate key, e.g. "pm_mt_topviewer_comment/{0:user} just became...".
userIdstringUser id this announcement is about.
protoVersionnumberAlways 3.

Example Payload

rankText.json
{ "event": "rankText", "data": { "type": "rankText", "templateKey": "pm_mt_topviewer_comment/{0:user} just became the top viewer", "userId": "7089122432367674373", "protoVersion": 3 } }
accessRecallv3 (2026-06-07): access recall (content-classification recheck pulls a gift / chat permission).

Fields

NameTypeDescription
statusnumberRecall status.
reasonstringReason code, e.g. "CONTENT_CLASSIFICATION".
durationSecnumberHow long the suspension lasts.
suspendKeystringLocalized suspend key.
protoVersionnumberAlways 3.

Example Payload

accessRecall.json
{ "event": "accessRecall", "data": { "type": "accessRecall", "status": 1, "reason": "CONTENT_CLASSIFICATION", "durationSec": 600, "suspendKey": "pm_mt_giftAccess_10MinSuspend_toast", "protoVersion": 3 } }
unauthorizedMemberv3 (2026-06-07): unauthorized member notice (non-logged-in viewer hit a gated feature).

Fields

NameTypeDescription
templateKeystringTemplate key, e.g. "web_nonlogin_im_1Viewer%s".
countLabelstringCounter shown in the notice.
enterToastKeystringLocalized enter-toast key.
protoVersionnumberAlways 3.

Example Payload

unauthorizedMember.json
{ "event": "unauthorizedMember", "data": { "type": "unauthorizedMember", "templateKey": "web_nonlogin_im_1Viewer%s", "countLabel": "440680", "enterToastKey": "live_room_enter_toast", "protoVersion": 3 } }
guestShowdownv3 (2026-06-07): guest showdown lifecycle (showdown intro / round flip).

Fields

NameTypeDescription
showdownTypenumberShowdown subtype.
protoVersionnumberAlways 3.

Example Payload

guestShowdown.json
{ "event": "guestShowdown", "data": { "type": "guestShowdown", "showdownType": 4, "protoVersion": 3 } }
hotRoomv3 (2026-06-07): TikTok promoted the room to a high-traffic slot.

Fields

NameTypeDescription
hotKeystringSurface descriptor, e.g. "revenue_high_traffic".
protoVersionnumberAlways 3.

Example Payload

hotRoom.json
{ "event": "hotRoom", "data": { "type": "hotRoom", "hotKey": "revenue_high_traffic", "protoVersion": 3 } }
envelopePortalv3 (2026-06-07): red-envelope portal advance (multi-room envelope chain).

Fields

NameTypeDescription
sessionInfostringPacked portal session info.
statusnumberPortal status.
kindnumberPortal kind.
levelnumberPortal level.
protoVersionnumberAlways 3.

Example Payload

envelopePortal.json
{ "event": "envelopePortal", "data": { "type": "envelopePortal", "sessionInfo": "...", "status": 2, "kind": 2, "level": 2, "protoVersion": 3 } }
groupLiveMemberNotifyv3 (2026-06-07): group-live member join / leave notify.

Fields

NameTypeDescription
userIdstringTikTok user id.
nicknamestringDisplay name.
protoVersionnumberAlways 3.

Example Payload

groupLiveMemberNotify.json
{ "event": "groupLiveMemberNotify", "data": { "type": "groupLiveMemberNotify", "userId": "7467181997212075026", "nickname": "Kris", "protoVersion": 3 } }
shortTouchv3 (2026-06-07): short-touch UI state change (poll / ecommerce lucky bag / custom UI element).

Fields

NameTypeDescription
variantstringVariant tag, e.g. "CustomPoll".
actionstringAction descriptor, e.g. "shortTouchCustomPoll".
refIdstringReference id (poll / lucky-bag id).
protoVersionnumberAlways 3.

Example Payload

shortTouch.json
{ "event": "shortTouch", "data": { "type": "shortTouch", "variant": "CustomPoll", "action": "shortTouchCustomPoll", "refId": "7648483782558993174", "protoVersion": 3 } }
linkMicAnchorGuidev3 (2026-06-07): anchor (creator) guide nudges (TikTok prompts the host with a tip).

Fields

NameTypeDescription
guideCodenumberGuide category code.
rawPayloadstringPacked guide payload.
protoVersionnumberAlways 3.

Example Payload

linkMicAnchorGuide.json
{ "event": "linkMicAnchorGuide", "data": { "type": "linkMicAnchorGuide", "guideCode": 7, "rawPayload": "...", "protoVersion": 3 } }
gameMomentv3 (2026-06-07): PK / mini-game moment window (highlight clip start / end).

Fields

NameTypeDescription
momentTypenumberMoment category code.
startedAtMsnumberMoment window start (ms epoch).
endsAtMsnumberMoment window end (ms epoch).
momentMsgIdstringMoment message id.
protoVersionnumberAlways 3.

Example Payload

gameMoment.json
{ "event": "gameMoment", "data": { "type": "gameMoment", "momentType": 3, "startedAtMs": 1780803292414, "endsAtMs": 1780803297414, "momentMsgId": "7648491877775774737", "protoVersion": 3 } }
competitionContributorv3 (2026-06-07): per-contributor breakdown inside a cross-stream competition.

Fields

NameTypeDescription
competitionTypenumberCompetition category code.
contributorUserIdstringContributor user id.
receiverUserIdstringReceiving creator user id.
protoVersionnumberAlways 3.

Example Payload

competitionContributor.json
{ "event": "competitionContributor", "data": { "type": "competitionContributor", "competitionType": 3, "contributorUserId": "7648483205057776397", "receiverUserId": "7648489576457587469", "protoVersion": 3 } }
pictionaryUpdatev3 (2026-06-07): drawing-game (Pictionary) round update.

Fields

NameTypeDescription
statusnumberRound status.
pictionaryIdstringPictionary session id.
protoVersionnumberAlways 3.

Example Payload

pictionaryUpdate.json
{ "event": "pictionaryUpdate", "data": { "type": "pictionaryUpdate", "status": 2, "pictionaryId": "7648487101176056583", "protoVersion": 3 } }
pictionaryEndv3 (2026-06-07): drawing-game round end with revealed answer.

Fields

NameTypeDescription
pictionaryIdstringPictionary session id.
answerstringRevealed answer.
statusnumberEnd status code.
protoVersionnumberAlways 3.

Example Payload

pictionaryEnd.json
{ "event": "pictionaryEnd", "data": { "type": "pictionaryEnd", "pictionaryId": "7648487101176056583", "answer": "dog", "status": 2, "protoVersion": 3 } }
pictionaryExitv3 (2026-06-07): drawing-game exit (early quit).

Fields

NameTypeDescription
pictionaryIdstringPictionary session id.
exitReasonnumberExit reason code.
protoVersionnumberAlways 3.

Example Payload

pictionaryExit.json
{ "event": "pictionaryExit", "data": { "type": "pictionaryExit", "pictionaryId": "7648483515134167815", "exitReason": 1, "protoVersion": 3 } }
oecLiveManagerv3 (2026-06-07): OEC (commerce) live-manager event (manager assigned / unassigned).

Fields

NameTypeDescription
statusnumberManager assignment status.
managerNicknamestringManager display name.
protoVersionnumberAlways 3.

Example Payload

oecLiveManager.json
{ "event": "oecLiveManager", "data": { "type": "oecLiveManager", "status": 2, "managerNickname": "Luna Volare", "protoVersion": 3 } }
oecLiveBillboardv3 (2026-06-07): OEC live billboard slot (product wall snapshot).

Fields

NameTypeDescription
statusnumberBillboard status.
slotCountnumberNumber of product slots.
updatedAtMsnumberLast update timestamp (ms epoch).
productPayloadstringPacked product info.
flagsPayloadstring[]Display flags ("is_new0", "is_auto_display0", ...).
protoVersionnumberAlways 3.

Example Payload

oecLiveBillboard.json
{ "event": "oecLiveBillboard", "data": { "type": "oecLiveBillboard", "status": 2, "slotCount": 5, "updatedAtMs": 1780802349750, "productPayload": "...", "flagsPayload": ["is_new0"], "protoVersion": 3 } }
shopProductLive Shop commerce: a single product card was pinned / shown on screen by the host. Carries the product the room is currently selling so you can render the active offer in an overlay or log which SKU was promoted when.

Fields

NameTypeDescription
productIdstringTikTok Shop product id.
titlestringProduct title as shown on the card.
pricestringDisplayed price string (already currency-formatted by TikTok).
imageUrlstringCDN URL for the product thumbnail.
statusnumberCard status (1 = shown / pinned, 0 = hidden).
protoVersionnumberAlways 3.

Example Payload

shopProduct.json
{ "event": "shopProduct", "data": { "type": "shopProduct", "productId": "1729...", "title": "Make Me Melt Removing Balm", "price": "$23.80", "imageUrl": "https://...", "status": 1, "protoVersion": 3 } }
shopProductListLive Shop commerce: the full ordered list of products in the room's shopping cart panel (the "shop" tray). Fired when the catalogue for the live session is set or reordered. Use it to mirror the room's product line-up.

Fields

NameTypeDescription
productsobject[]Ordered list, each { productId, title, price, imageUrl, soldCount? }.
totalnumberNumber of products in the tray.
protoVersionnumberAlways 3.

Example Payload

shopProductList.json
{ "event": "shopProductList", "data": { "type": "shopProductList", "total": 2, "products": [ { "productId": "1729...", "title": "Removing Balm", "price": "$23.80", "imageUrl": "https://..." }, { "productId": "1731...", "title": "Cleansing Oil", "price": "$19.00", "imageUrl": "https://..." } ], "protoVersion": 3 } }
shopInventoryLive Shop commerce: an inventory / sold-count update for a product in the room. Fired as stock depletes during a flash-sale segment - track units sold and remaining stock per SKU in real time.

Fields

NameTypeDescription
productIdstringTikTok Shop product id this update applies to.
stocknumberRemaining inventory for the product.
soldCountnumberUnits sold so far in this live session.
protoVersionnumberAlways 3.

Example Payload

shopInventory.json
{ "event": "shopInventory", "data": { "type": "shopInventory", "productId": "1729...", "stock": 142, "soldCount": 58, "protoVersion": 3 } }
shopBillboardLive Shop commerce: a snapshot of the product billboard / wall slots (the multi-product showcase). Mirrors the underlying oecLiveBillboard frame in the cleaner SDK shape - the slot count plus the products currently occupying the wall.

Fields

NameTypeDescription
slotCountnumberNumber of product slots on the wall.
productsobject[]Slot contents, each { slot, productId, title, price, imageUrl }.
updatedAtMsnumberLast update timestamp (ms epoch).
protoVersionnumberAlways 3.

Example Payload

shopBillboard.json
{ "event": "shopBillboard", "data": { "type": "shopBillboard", "slotCount": 3, "updatedAtMs": 1780802349750, "products": [ { "slot": 1, "productId": "1729...", "title": "Removing Balm", "price": "$23.80", "imageUrl": "https://..." } ], "protoVersion": 3 } }
perceptionv3 (2026-06-07): perception event (mute cancel etc - TikTok hint signal).

Fields

NameTypeDescription
perceptionCodenumberPerception category code.
actionstringAction descriptor, e.g. "muting_cancel<msgId>".
protoVersionnumberAlways 3.

Example Payload

perception.json
{ "event": "perception", "data": { "type": "perception", "perceptionCode": 8, "action": "muting_cancel7648486963015877389", "protoVersion": 3 } }
questionSelectedv3 (2026-06-07): host picked a viewer-submitted question.

Fields

NameTypeDescription
questionTextstringQuestion text.
protoVersionnumberAlways 3.

Example Payload

questionSelected.json
{ "event": "questionSelected", "data": { "type": "questionSelected", "questionText": "Quel est ton acteur ou ton actrice preferee?", "protoVersion": 3 } }
questionSlideDownv3 (2026-06-07): selected-question card slid down (UI dismiss).

Fields

NameTypeDescription
questionIdstringQuestion id.
protoVersionnumberAlways 3.

Example Payload

questionSlideDown.json
{ "event": "questionSlideDown", "data": { "type": "questionSlideDown", "questionId": "7648490528341835796", "protoVersion": 3 } }
giftUnlockv3 (2026-06-07): gift-unlock reveal (host unlocked a gated gift).

Fields

NameTypeDescription
iconUrlstringCDN icon URL for the unlocked gift.
tooltipKeystringLocalized tooltip key.
protoVersionnumberAlways 3.

Example Payload

giftUnlock.json
{ "event": "giftUnlock", "data": { "type": "giftUnlock", "iconUrl": "https://p16-webcast.tiktokcdn.com/...", "tooltipKey": "ttlive_commGift_postReveal_tooltip", "protoVersion": 3 } }
ecShortItemRefreshv3 (2026-06-07): short-touch ecommerce item refresh (lucky bag drop refreshed).

Fields

NameTypeDescription
refreshTokenstringRefresh token / item identifier.
protoVersionnumberAlways 3.

Example Payload

ecShortItemRefresh.json
{ "event": "ecShortItemRefresh", "data": { "type": "ecShortItemRefresh", "refreshToken": "shortTouchEcommerceLuckyBag7648485714367643661", "protoVersion": 3 } }
capsulev3 (2026-06-07): capsule overlay (TikTok service-plus pin reminder for high-intention viewers).

Fields

NameTypeDescription
imageUrlstringCDN icon URL.
titleKeystringLocalized title key.
btnKeystringLocalized button key.
deepLinkstringDeep link target.
reminderKeystringReminder category key.
displayDurationSecnumberHow long to display the capsule.
protoVersionnumberAlways 3.

Example Payload

capsule.json
{ "event": "capsule", "data": { "type": "capsule", "imageUrl": "https://...", "titleKey": "ttlive_servicePlus_live_modPinCapsule", "btnKey": "ttlive_servicePlus_live_modPinCapsule_btnPin", "deepLink": "sslocal://webcast_subscribe/allinone?sec_anchor_id=...", "reminderKey": "high_intention_comment_pincard_reminder", "displayDurationSec": 90, "protoVersion": 3 } }
roomVerifyv3 (2026-06-07): room age / content-classification verification event.

Fields

NameTypeDescription
verifyCodenumberVerify category code.
protoVersionnumberAlways 3.

Example Payload

roomVerify.json
{ "event": "roomVerify", "data": { "type": "roomVerify", "verifyCode": 3, "protoVersion": 3 } }
smbBoardv3 (2026-06-07): SMB (small-business) board overlay.

Fields

NameTypeDescription
boardPayloadstringPacked board payload.
statusnumberBoard status.
protoVersionnumberAlways 3.

Example Payload

smbBoard.json
{ "event": "smbBoard", "data": { "type": "smbBoard", "boardPayload": "...", "status": 1, "protoVersion": 3 } }
pictionaryStartv3 (2026-06-07): drawing-game (Pictionary) round start.

Fields

NameTypeDescription
pictionaryIdstringRound identifier.
statusnumberLifecycle status code.
protoVersionnumberAlways 3.

Example Payload

pictionaryStart.json
{ "event": "pictionaryStart", "data": { "type": "pictionaryStart", "pictionaryId": "7648...", "status": 1, "protoVersion": 3 } }
cohostSettingsUpdatev3 (2026-06-07): cohost settings updated (slot count, layout, permissions).

Fields

NameTypeDescription
settingsPayloadstringPacked settings payload.
statusnumberUpdate status.
protoVersionnumberAlways 3.

Example Payload

cohostSettingsUpdate.json
{ "event": "cohostSettingsUpdate", "data": { "type": "cohostSettingsUpdate", "settingsPayload": "...", "status": 1, "protoVersion": 3 } }
giftCollectionUpdatev3 (2026-06-07): host curated gift set changed.

Fields

NameTypeDescription
collectionIdstringGift collection identifier.
statusnumberUpdate status.
protoVersionnumberAlways 3.

Example Payload

giftCollectionUpdate.json
{ "event": "giftCollectionUpdate", "data": { "type": "giftCollectionUpdate", "collectionId": "76...", "status": 1, "protoVersion": 3 } }
gameAutoPostNoticev3 (2026-06-07): notice posted automatically by an in-room mini-game.

Fields

NameTypeDescription
noticeTextstringNotice text shown to viewers.
gameIdstringOriginating game identifier.
protoVersionnumberAlways 3.

Example Payload

gameAutoPostNotice.json
{ "event": "gameAutoPostNotice", "data": { "type": "gameAutoPostNotice", "noticeText": "New high score!", "gameId": "106", "protoVersion": 3 } }
subPinv3 (2026-06-07): a comment pinned by a paid subscriber via the sub-only pin slot.

Fields

NameTypeDescription
pinnedTextstringThe pinned comment text.
userWebcastUserSubscriber who pinned the comment.
protoVersionnumberAlways 3.

Example Payload

subPin.json
{ "event": "subPin", "data": { "type": "subPin", "pinnedText": "Good luck!", "user": { "uniqueId": "vipfan" }, "protoVersion": 3 } }
toastv3 (2026-06-07): generic toast popup pushed by the room (system messages, hints).

Fields

NameTypeDescription
toastTextstringToast body text.
durationMsnumberHow long to display, in milliseconds.
protoVersionnumberAlways 3.

Example Payload

toast.json
{ "event": "toast", "data": { "type": "toast", "toastText": "Connected to LIVE", "durationMs": 3000, "protoVersion": 3 } }
gapHighlightPushGuidev3 (2026-06-07): first-render hint shown to a viewer during a content gap.

Fields

NameTypeDescription
guideKeystringIdentifier for the guide template TikTok wants rendered.
protoVersionnumberAlways 3.

Example Payload

gapHighlightPushGuide.json
{ "event": "gapHighlightPushGuide", "data": { "type": "gapHighlightPushGuide", "guideKey": "ttlive_push_guide_v1", "protoVersion": 3 } }
karaokeReqv3 (2026-06-07): viewer queued / requested a karaoke track on the host's karaoke widget.

Fields

NameTypeDescription
trackIdstringIdentifier of the requested track.
trackNamestringDisplay name of the requested track.
statusnumberQueue / request status.
protoVersionnumberAlways 3.

Example Payload

karaokeReq.json
{ "event": "karaokeReq", "data": { "type": "karaokeReq", "trackId": "12345", "trackName": "Bohemian Rhapsody", "status": 1, "protoVersion": 3 } }

Live Gifter Firehose

Single WebSocket that streams gift events from every live TikTok creator we monitor, globally, on one connection. Not a per-creator subscription. Tracking 1, 100, or 10,000 concurrent live creators consumes the same slot count: one.

Gated to Global Agency tier. For agencies, leaderboard tooling, gifter intel platforms, region/league cutoff trackers, mass tip-jar surfaces. See pricing.

Endpoint

endpoint.txt
wss://api.tik.tools/firehose/gifters?apiKey=YOUR_KEY

How it works

Server-side, every gift event from every monitored LIVE is published to a shared pub/sub channel. Your WebSocket subscribes to that channel and the server fans every matching event to you in real time. No per-creator WebSocket. No per-creator REST call. No watchlist registration. The firehose ingests two upstream channels and dedupes cross-channel duplicates within a 60-second window so you never receive the same gift twice.

Query parameters

ParamTypeDefaultDescription
apiKeystringrequiredYour API key. Tier must be agency_global.
modeenumglobalglobal | region | league
regionstring-Region code (e.g. KR+, US+, DE+, BK). Required when mode=region or mode=league.
leaguestring-League class (e.g. A1, B2, P1). Required when mode=league. Filter applies to whale-tier events (≥30k diamonds) where league metadata is available.
min_diamondsint1Skip events below this diamond count. Lower the floor for full-coverage tracking, raise for whale-only alerting.

Event shape

gifter_alert.json
{
  "type": "gifter_alert",
  "ts": "2026-06-07T18:33:07.829Z",
  "gifter": {
    "username": "lajan.eli",
    "displayName": "lajan.eli",
    "isAnonymous": false
  },
  "creator": {
    "uniqueId": "globalboy_ry"
  },
  "gift": {
    "name": "Meteor Shower",
    "totalDiamonds": 3000
  },
  "region": "US+",
  "classType": null
}

Filter updates mid-stream

Send a JSON frame to switch filter without reconnecting:

update-filter.json
{
  "type": "update_filter",
  "mode": "region",
  "region": "KR+",
  "min_diamonds": 1000
}

Capacity model

The Firehose is a fan-out stream. Your connection slot count is fixed at one regardless of how many creators are live. This sits outside the per-creator concurrent-WS budget shown in Rate Limits — the Firehose slot does not subtract from your 500 concurrent-WS Global Agency allowance.

Example client

firehose-client.mjs
// firehose-client.mjs
// Node 22+. One WS = every live creator globally.
import WebSocket from 'ws'

const KEY = process.env.TIKTOOL_KEY
const url = 'wss://api.tik.tools/firehose/gifters'
  + '?apiKey=' + KEY
  + '&mode=region&region=KR+'
  + '&min_diamonds=1000'

const ws = new WebSocket(url)
ws.on('open', () => console.log('Firehose subscribed'))
ws.on('message', (raw) => {
  const e = JSON.parse(raw.toString())
  if (e.type !== 'gifter_alert') return
  console.log(`[${e.region}] ${e.gifter.username} → @${e.creator.uniqueId} ` +
    `${e.gift.name} (${e.gift.totalDiamonds}\u{1F48E})`)
})
ws.on('close', (code) => console.log('Closed', code))

Rate Limits

Rate limits are applied per API key. The per-minute throttle is a sliding window; daily counters reset at 00:00 UTC. The canonical, always-current feature matrix lives on the pricing page.

TierAPI Req/minAPI Req/dayConcurrent WSWS Max DurationBulk Check
Sandbox5 (300/h)2,50012h-
Basic6010,000208h10/req
Pro30075,0005012h50/req
Ultra1,000300,00025024h100/req
Global Agency5,0001,000,00050024h200/req

Rate limit headers are included in every response:

Headers
X-RateLimit-Limit: 30
X-RateLimit-Remaining: 28
X-RateLimit-Reset: 1234567890
Daily Limit Reset

All daily counters - WebSocket connection attempts and feed discovery calls - reset at midnight UTC (00:00 UTC) every day. The dashboard reflects the same UTC boundary in real time.

Managed Infrastructure - No Setup Required

In Relayed mode, all upstream sessions are handled by the TikTools managed edge. Your application connects to a single, stable endpoint: api.tik.tools.

Your API key limit (50 for Pro, 500 for Ultra) is the only cap that applies. No infrastructure setup needed on your end.

WebSocket Close Codes

Custom close codes used when the WebSocket connection is terminated.

CodeNameDescription
1000NORMALNormal closure
4005STREAM_ENDThe live stream ended
4006NO_MESSAGES_TIMEOUTNo messages received for 60 seconds
4400INVALID_OPTIONSMissing or invalid connection parameters
4401INVALID_AUTHInvalid API key or JWT
4403NO_PERMISSIONJWT does not allow access to this creator
4404NOT_LIVEThe user is not currently live
4429TOO_MANY_CONNECTIONSExceeded WebSocket connection limit
4500TIKTOK_CLOSEDUpstream TikTok connection closed
4555MAX_LIFETIMEConnection exceeded 8-hour max lifetime
4556WEBCAST_FETCH_ERRORFailed to fetch webcast data

Live Captions

Real-time speech-to-text transcription for TikTok LIVE streams. AI-powered with speaker diarization, multi-language support, and sub-second latency.

NEWNo-code OBS overlay - get a single URL to paste into Browser Source for real-time captions on your stream->
WebSocketMulti-LanguageSpeaker DiarizationTranslation

Connection

WebSocket URL
wss://api.tik.tools/captions?uniqueId=USERNAME&apiKey=YOUR_KEY&language=en&translate=en&diarization=true&max_duration_minutes=60

Query Parameters

NameTypeRequiredDescription
uniqueIdstringYesTikTok username to caption
apiKeystringYesYour API key
languagestringNoSource language hint (empty = auto-detect)
translatestringNoTarget translation language code. Only ONE language per session (multi-translate rejected)
diarizationbooleanNoEnable speaker identification (default: true)
max_duration_minutesnumberNoAuto-disconnect after N minutes (default: 60, max: 300). Forces reconnect to prevent runaway sessions.

Events

TypeFieldsDescription
captiontext, speaker, isFinal, languageReal-time caption text (partial and final)
translationtext, speaker, targetLanguageTranslated caption text
statusstatus, messageSession status updates (connecting, transcribing, ended)
creditstotal, used, remainingCredit balance update (sent every 30s)
credits_lowremaining, percentageLow credit warning (≤20% remaining)
errorcode, messageError event

Example Payload

caption event
{ "type": "caption", "text": "Hello everyone, welcome to my stream!", "speaker": "Speaker 1", "isFinal": true, "language": "en" }

Code Examples

Live Captions - Node.js
Node.js
Python
cURL
import WebSocket from 'ws';

const ws = new WebSocket(
  'wss://api.tik.tools/captions?uniqueId=streamer&apiKey=YOUR_KEY&translate=en&max_duration_minutes=120'
);

ws.on('message', (data) => {
  const msg = JSON.parse(data.toString());
  switch (msg.type) {
    case 'caption':
      const prefix = msg.speaker ? `[${msg.speaker}] ` : '';
      console.log(`${prefix}${msg.text}${msg.isFinal ? ' ✓' : '...'}`);
      break;
    case 'translation':
      console.log(`  → ${msg.text}`);
      break;
    case 'credits':
      console.log(`Credits: ${msg.remaining}/${msg.total} min remaining`);
      break;
    case 'status':
      console.log(`Status: ${msg.status}`);
      break;
  }
});

SDK Usage

SDK - Node.js
import { TikTokCaptions } from '@tiktool/live';

const captions = new TikTokCaptions({
  apiKey: process.env.TIKTOOL_API_KEY,
  uniqueId: 'streamer_name',
  translate: 'en',
  diarization: true,
});

captions.on('caption', (event) => {
  console.log(`[${event.speaker}] ${event.text}`);
});

captions.on('translation', (event) => {
  console.log(`  → ${event.text}`);
});

captions.on('credits', (event) => {
  console.log(`${event.remaining}/${event.total} min remaining`);
});

captions.on('credits_low', (event) => {
  console.warn(`Low credits! ${event.remaining} min left`);
});

await captions.start();
// captions.stop() to end

Pricing - Subscription Plans

Captions are sold as recurring subscription plans by hours of stream time. 1 hour = 60 minutes of transcription or translation in one target language. No API key required.

PlanWeeklyMonthlyPer-min (monthly)
Casual12h / $760h / $29$0.0081
Pro30h / $15140h / $59$0.0070
Extreme60h / $29260h / $99$0.0064
Captions are bundled with every paid API plan

Basic ships 12h/wk - 60h/mo. Pro 30h/wk - 140h/mo. Ultra 60h/wk - 260h/mo. Global Agency 120h/wk - 500h/mo. Hours reset every renewal and do not roll over.

Auto-renew + early-renewal on exhaust

Plans auto-renew weekly or monthly. If you run out of hours mid-period, the next renewal fires early so captions never stop. Cancel any time on /billing.

Try it live

See captions in action on a real TikTok LIVE stream at tik.tools/captions

Unreal Engine Plugin

Native Unreal Engine plugin with full Blueprint and C++ support. Access TikTok LIVE data directly inside your UE project - no HTTP calls, no external processes.

UE 5.4+Blueprint ReadyC++ API15+ Events

Installation

Project Structure
YourProject/
├── Plugins/
│   └── TikToolLive/
│       ├── TikToolLive.uplugin
│       └── Source/
│           └── TikToolLive/
│               ├── Public/
│               │   ├── TikToolLiveSubsystem.h
│               │   ├── TikToolSettings.h
│               │   └── TikToolTypes.h
│               └── Private/
│                   └── TikToolLiveSubsystem.cpp
  1. Copy the TikToolLive folder into your project's Plugins/ directory
  2. Regenerate project files (right-click .uproject → Generate Visual Studio project files)
  3. Open the editor → Edit → Plugins → verify TikToolLive is enabled

Project Settings

Configure via Edit → Project Settings → Plugins → TikTool Live:

SettingTypeDefaultDescription
ApiKeyFString""Your TikTool API key
DefaultUniqueIdFString""TikTok username (without @)
bAutoConnectboolfalseConnect automatically on game start
bAutoReconnectbooltrueReconnect on disconnection
MaxReconnectAttemptsint325Max reconnect retries
HeartbeatIntervalfloat30.0Seconds between heartbeats
bDebugLoggingboolfalseEnable verbose log output

Blueprint Quick Start

Zero C++ required. Three steps to receive live events in any Blueprint:

  1. Search for "Get TikTool Live Subsystem" from any Blueprint node graph
  2. Call Connect (or enable Auto Connect in Project Settings)
  3. Bind to events: drag from the subsystem → Assign OnChatEvent, Assign OnGiftEvent, etc.

Connection API

FunctionReturnsDescription
Connect(UniqueId)voidConnect to a TikTok LIVE stream. Uses DefaultUniqueId if empty.
Disconnect()voidClose the WebSocket connection
IsConnected()boolCheck if currently connected
GetRoomId()FStringGet the current room ID
GetViewerCount()int32Current concurrent viewer count
GetEventCount()int32Total events received this session

Events

All events are BlueprintAssignable multicast delegates. Bind in Blueprint (Assign node) or C++ (AddDynamic):

DelegatePayloadDescription
OnChatEventFTikToolChatEventChat message received
OnGiftEventFTikToolGiftEventGift sent by viewer
OnLikeEventFTikToolLikeEventLikes received
OnMemberEventFTikToolMemberEventViewer joined stream
OnFollowEventFTikToolFollowEventNew follower
OnShareEventFTikToolShareEventStream shared
OnSubscribeEventFTikToolSubscribeEventSubscription
OnViewerCountUpdateFTikToolViewerEventViewer count changed
OnBattleUpdateFTikToolBattleEventBattle status update
OnEmoteChatEventFTikToolEmoteChatEventEmote in chat
OnQuestionEventFTikToolQuestionEventQ&A question
OnEnvelopeEventFTikToolEnvelopeEventTreasure box / envelope
OnStreamEnd-Stream ended
OnConnected-Successfully connected
OnDisconnected-Connection lost

C++ Example

MyChatActor.cpp - C++
#include "TikToolLiveSubsystem.h"

void AMyChatActor::BeginPlay()
{
    Super::BeginPlay();
    auto* TikTool = GetGameInstance()->GetSubsystem<UTikToolLiveSubsystem>();
    if (!TikTool) return;

    // Bind to chat events
    TikTool->OnChatEvent.AddDynamic(this, &AMyChatActor::HandleChat);
    TikTool->OnGiftEvent.AddDynamic(this, &AMyChatActor::HandleGift);

    // Connect (uses DefaultUniqueId from Project Settings)
    TikTool->Connect();
}

void AMyChatActor::HandleChat(const FTikToolChatEvent& Event)
{
    UE_LOG(LogTemp, Log, TEXT("%s: %s"),
        *Event.User.Nickname, *Event.Comment);
}

void AMyChatActor::HandleGift(const FTikToolGiftEvent& Event)
{
    UE_LOG(LogTemp, Log, TEXT("%s sent %s x%d"),
        *Event.User.Nickname, *Event.GiftName, Event.RepeatCount);
}