@tiktool/live
v2.12.1
Published
TikTok LIVE API Client — Real-time chat, gifts, viewers, PK battles with MVP breakdown, x2/x3 boosters, gloves, mist, match-guide & 20+ event types from any TikTok livestream. Direct WebSocket connection.
Maintainers
Keywords
Readme
@tiktool/live
Connect to any TikTok LIVE stream in 4 lines of code.
Real-time chat, gifts, viewers, PK battles with MVP breakdown, x2/x3 boosters, gloves, mist, match-guide & 20+ event types from any TikTok livestream.
Quick Start · Events · PK Battles · API · Rate Limits · Get API Key
⚡ Quick Start
npm install @tiktool/liveGet your free API key at tik.tools
import { TikTokLive } from '@tiktool/live';
const live = new TikTokLive({
uniqueId: 'tv_asahi_news',
apiKey: 'YOUR_API_KEY',
});
live.on('chat', e => console.log(`${e.user.uniqueId}: ${e.comment}`));
live.on('gift', e => console.log(`${e.user.uniqueId} sent ${e.giftName} (${e.diamondCount} diamonds)`));
live.on('member', e => console.log(`${e.user.uniqueId} joined`));
live.on('roomUserSeq', e => console.log(`Viewers: ${e.viewerCount}`));
await live.connect();How It Works
Your App tik.tools TikTok
+-----------+ +--------------+ +--------------+
| -+-- sign_url --> Signs URL | | |
| Your <-+-- X-Bogus --| with params | | TikTok |
| Code | | | | WebSocket |
| -+------------ Connect directly ---------->| Server |
| <-+------------ Live events (protobuf) <---| |
+-----------+ +--------------+ +--------------+
^ Only interaction ^ Direct from
with our server YOUR IP- Your app connects directly to TikTok from your IP address
- The sign server only generates cryptographic signatures (requires API key)
- TikTok never sees the sign server
- Built-in protobuf parser, zero external dependencies (only
wsfor WebSocket)
Connection Modes
There are two ways to receive TikTok LIVE events from tik.tools. Pick based on scale and IP-exposure tolerance.
Mode 1 — Direct (default, this SDK)
Your App ──signs URL via──▶ tik.tools ──returns signed URL──▶ Your App
Your App ───────────────── WS direct to TikTok ───────────────▶ TikTok
Your App ◀────────────────── live events ────────────────────── TikTok- The signed WebSocket is opened from your runtime. TikTok sees the network identity of the host that runs the SDK.
- Lowest per-stream cost — only the initial
ws_credentialscall is billed against your API key. - Suited to low- to mid-volume workloads from a single host.
- High-volume setups should prefer Mode 2 for predictable, centrally-managed egress.
const live = new TikTokLive({
uniqueId: 'creator_username',
apiKey: 'YOUR_TIKTOOLS_KEY',
// Optional: route outbound WS + HTTP through a corporate / egress gateway
// proxy: 'http://USER:[email protected]:port',
});If you set the optional proxy field, install the standard agent:
npm install https-proxy-agentMode 2 — Relayed (via TikTools managed edge)
Your App ◀──── wss://api.tik.tools/?... ──── tik.tools
│
│ managed edge
▼
TikTok- The TikTools service handles the upstream TikTok session and forwards decoded events over a single WebSocket to your client.
- Single, stable egress — your application connects only to
api.tik.tools. - Recommended for production scale, multi-tenant deployments, or any environment that already centralizes outbound traffic.
Easiest — same SDK, one option:
import { TikTokLive } from '@tiktool/live';
const live = new TikTokLive({
uniqueId: 'creator_username',
apiKey: 'YOUR_KEY',
mode: 'relayed', // ← that's it
});
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();Event names + payload shapes are identical to Direct mode. Switch back and forth by toggling mode.
Advanced — raw WebSocket (no SDK, your own client):
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());
if (msg.event === 'chat') console.log(msg.data.user.uniqueId, msg.data.comment);
if (msg.event === 'gift') console.log(msg.data.user.uniqueId, msg.data.giftName);
if (msg.event === 'battleArmies') console.log(msg.data);
});Use this when integrating from a non-Node runtime (Python, Go, Bun, browser) or when you want absolute control over the connection.
Side-by-side
| Aspect | Direct (Mode 1) | Relayed (Mode 2) |
|---|---|---|
| WebSocket opens from | Your runtime | TikTools managed edge |
| Setup | new TikTokLive(...) | new TikTokLive({ ..., mode: 'relayed' }) |
| Egress | Your host's network | Single endpoint: api.tik.tools |
| Best for | Low- to mid-volume | Production scale, multi-tenant |
Events
Listening
live.on('chat', (event) => {
event.user.uniqueId // string
event.comment // string
});
live.on('event', (event) => {
console.log(event.type, event);
});Reference
| Event | Type | Description | Fields |
|-------|------|-------------|--------|
| chat | ChatEvent | Chat message (with inline emotes) | user, comment, emotes |
| member | MemberEvent | User joined | user, action |
| like | LikeEvent | User liked | user, likeCount, totalLikes |
| gift | GiftEvent | Gift sent | user, giftId, giftName, diamondCount, repeatCount, combo, groupId |
| social | SocialEvent | Follow / Share | user, action |
| roomUserSeq | RoomUserSeqEvent | Viewer count | viewerCount, totalViewers |
| battle | BattleEvent | PK Battle start / status | battleId, status, battleDuration, teams |
| battleArmies | BattleArmiesEvent | PK score update + per-host MVP breakdown | battleId, teams, hosts[], secsRemaining, serverTsMs, matchId, sessionId |
| battleItemCard | BattleItemCardEvent | x2/x3 boosters, gloves, mist, thunder, extra-time, match-guide | effect, multiplier, senderUserId, iconUrl, iconKey, accentColor, … |
| subscribe | SubscribeEvent | New subscriber | user, subMonth |
| emoteChat | EmoteChatEvent | Emote in chat | user, emoteId, emoteUrl |
| envelope | EnvelopeEvent | Treasure chest | diamondCount |
| question | QuestionEvent | Q&A question | user, questionText |
| control | ControlEvent | Stream control | action (3 = ended) |
| room | RoomEvent | Room status | status |
| liveIntro | LiveIntroEvent | Stream intro | roomId, title |
| rankUpdate | RankUpdateEvent | Hourly / weekly rank | rankType, rankList |
| linkMic | LinkMicEvent | Link Mic guest action | action, users |
| unknown | UnknownEvent | Unrecognized | method |
Connection Events
| Event | Callback | Description |
|-------|----------|-------------|
| connected | () => void | Connected to stream |
| disconnected | (code, reason) => void | Disconnected |
| roomInfo | (info: RoomInfo) => void | Room info |
| error | (error: Error) => void | Error |
🥊 PK Battles
This SDK fully parses TikTok PK (Player-vs-Killer) battle protobufs, including multi-guest matches with up to 4 hosts per side and per-gifter MVP scores.
battleArmies — score updates during a PK
Emitted every few seconds while a PK is active. The new hosts[] array gives
you the per-host breakdown with each gifter's contribution sorted MVP
first (highest score → lowest):
live.on('battleArmies', (e) => {
console.log(`PK ${e.battleId} — ${e.secsRemaining}s remaining`);
for (const host of e.hosts ?? []) {
console.log(` Host ${host.hostUserId} — total ${host.teamTotalScore}`);
// contributors[0] = MVP (highest gifter)
for (const [i, c] of host.contributors.slice(0, 3).entries()) {
console.log(` #${i + 1} ${c.nickname || c.userId}: ${c.score}`);
}
}
});Per-host fields:
| Field | Type | Description |
|-------|------|-------------|
| hostUserId | string | TikTok userId of this side's host |
| teamTotalScore | number | Total diamonds for this side |
| teamIdx | number | Side index (0 = left, 1 = right, …) |
| contributors[] | BattleContributor[] | Per-gifter breakdown, sorted MVP first |
Per-contributor fields:
| Field | Type | Description |
|-------|------|-------------|
| userId | string | Gifter's TikTok userId |
| score | number | Diamond score contributed |
| nickname | string | Display nickname (may be empty) |
Timer & match identity:
| Field | Type | Description |
|-------|------|-------------|
| battleId | string | Battle session tag |
| matchId | string | Stable match ID across multi-round PK |
| sessionId | string | Per-round session ID |
| secsRemaining | number | Seconds left, computed from TikTok server clock (no VPS drift) |
| serverTsMs | number | TikTok server-side ms epoch at frame emit |
| durationSec | number | Total battle duration |
| endTimeMs | number | Battle end ms epoch (alias for battleStartMs + duration*1000) |
battleItemCard — boosters / power-ups during a PK
Fired when a gifter activates a special card: x2/x3 multipliers, gloves (crit), mist, thunder, extra-time, or match-guide. Includes the card art URL straight from TikTok's CDN — drop it into an OBS overlay as-is.
live.on('battleItemCard', (e) => {
console.log(`${e.senderNickname} activated ${e.effect}`);
if (e.multiplier > 0) {
console.log(` x${e.multiplier} multiplier for ${e.durationSec}s`);
}
// Use straight in an <img> tag — TikTok CDN
console.log(` icon: ${e.iconUrl}`);
console.log(` accent: ${e.accentColor}`);
});Fields:
| Field | Type | Description |
|-------|------|-------------|
| battleId | string | The PK this card belongs to |
| cardType | number | 2=gloves, 3=mist, 4=match_guide, 11=x3, … |
| effect | string | 'gloves' | 'mist' | 'booster_x2' | 'booster_x3' | 'match_guide' | 'thunder' | 'extra_time' | raw key |
| effectKey | string | Raw TikTok resource key |
| multiplier | number | 2 or 3 for boosters, else 0 |
| senderUserId | string | Gifter who activated it |
| senderNickname | string | Display name of the sender |
| senderUniqueId | string | Username (@handle) of the sender |
| senderAvatarUrl | string | Sender's avatar (CDN URL) |
| activatedAtSec | number | Unix seconds when activated |
| durationSec | number | Active duration |
| endsAtSec | number | Unix seconds when it ends |
| commentTemplate | string | e.g. "{0:user} sent 1 magic mist" |
| iconUrl | string | Full TikTok CDN URL for the card art (webp/jpeg) |
| iconKey | string | Short id, e.g. card_mist_v3, card_crit_v3, top3_buffer |
| accentColor | string | Hex color, e.g. #BCD9E0 (mist blue), #E0D4BC (gloves tan) |
battle — PK lifecycle events
Fired on start / status change / end. Use for PK ON/OFF banners.
live.on('battle', (e) => {
// status: 1=ACTIVE, 2=STARTING, 3=ENDED, 4=PREPARING
if (e.status === 1) console.log('PK started');
if (e.status === 3) console.log('PK ended');
});API Reference
new TikTokLive(options)
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| uniqueId | string | — | TikTok username (without @) |
| apiKey | string | — | Required. API key from tik.tools |
| signServerUrl | string | https://api.tik.tools | Sign server URL |
| autoReconnect | boolean | true | Auto-reconnect on disconnect |
| maxReconnectAttempts | number | 5 | Max reconnect attempts |
| heartbeatInterval | number | 10000 | Heartbeat interval (ms) |
| roomId | string | — | Pre-resolved room ID (skips page fetch when paired with sessionId) |
| sessionId | string | — | Pre-resolved ttwid cookie (skips page fetch when paired with roomId) |
| proxy | string | — | HTTP(S) proxy URL for Direct mode (e.g. http://USER:PASS@host:port). Requires https-proxy-agent. |
| mode | 'direct' \| 'relayed' | 'direct' | Connection mode. See Connection Modes. |
| debug | boolean | false | Debug logging |
Methods
| Method | Returns | Description |
|--------|---------|-------------|
| connect() | Promise<void> | Connect to livestream |
| disconnect() | void | Disconnect |
| getStreamUrl() | Promise<StreamInfo> | Get FLV/HLS playback URLs for the live video |
| getUserProfile(uniqueId?) | Promise<UserProfile> | Resolve any TikTok username → numeric id, secUid, nickname, bio, avatars, follower stats |
| connected | boolean | Connection status |
| eventCount | number | Total events received |
| roomId | string | Current room ID |
Static methods (no connection required)
// Get numeric TikTok user ID + full profile for any @username (Pro tier+)
const profile = await TikTokLive.getUserProfile({
uniqueId: 'dalga.ahmedov',
apiKey: 'YOUR_KEY',
});
profile.id; // "7355610677036581896" ← numeric streamer ID
profile.nickname; // "🤍DENIZ🖤🌊"
profile.secUid; // signed TikTok user identifier
profile.stats.followerCount; // 7945
profile.avatarLarger; // CDN URL
// Get FLV/HLS playback URLs for any creator
const stream = await TikTokLive.getStreamUrl({ uniqueId, apiKey });REST endpoints (companion to the WS SDK)
For lookups outside the live event stream, hit the sign server directly with your API key:
| Endpoint | Method | Tier | Use case |
|----------|--------|------|----------|
| /webcast/room_id | POST | sandbox+ | unique_id → room_id |
| /webcast/room_info | POST | sandbox+ | unique_id → room_id + alive + title |
| /webcast/check_alive | GET/POST | sandbox+ | Is room_id currently live? |
| /webcast/bulk_live_check | POST | basic+ | Batch check up to 500 users in one call |
| /webcast/live_status | GET | sandbox+ | unique_id → live snapshot incl. viewer count |
| /webcast/user_profile | GET | pro+ | unique_id → numeric id, secUid, nickname, bio, avatars, follower stats |
| /webcast/resolve_user_ids | POST | sandbox+ | Batch numeric userId → unique_id (reverse of user_profile) |
| /webcast/rankings | GET | sandbox+ | Top gifters / hourly rank for a room |
| /webcast/room_video | POST | basic+ | Get HLS / FLV stream URLs |
| /webcast/ws_credentials | POST | sandbox+ | Get signed WS URL + ttwid (used by Direct mode under the hood) |
Need the streamer's numeric TikTok ID for a username?
curl -H "X-Api-Key: YOUR_KEY" \
"https://api.tik.tools/webcast/user_profile?unique_id=anyuser"Returns full profile JSON. Pro tier and above. Cached server-side for 24h — repeated lookups are free and instant.
Rate Limits
All API requests require an API key. Get yours at tik.tools.
| Tier | Price | Requests/day | WS Connections | Notes | |------|-------|--------------|----------------|-------| | Community | Free forever | 2,500 | 15 (2h per WS) | Masked leaderboards. No datacenter proxies — calls run from your own IP. | | Pro | from $59/mo | 75,000 | 50 (8h) | Unmasked leaderboards · CAPTCHA Solver · Feed Discovery · 5 AI caption streams · priority routing | | Ultra | from $219/mo | 300,000 | 250 (8h) | Unmasked leagues · 20 AI caption streams · 99.5% uptime SLA · priority chat support | | Global Agency | $549/mo | 300,000 | 500 + Firehose | Everything in Ultra + Live Gifter Firehose WS (region/league/global + min-diamond filters) + VIP Telegram alerts + VIP Web Vault |
The SDK calls the sign server once per connection, then stays connected via WebSocket. The free Community tier caps each WebSocket at 2 hours and is sufficient for most use cases.
Live Gifter Firehose — Global Agency
Real-time gift event stream from Dragonfly fan-out. Filter by region, league,
or globally; cap by minimum diamond threshold. Mid-stream filter updates
supported via update_filter frame.
const ws = new WebSocket(
`wss://api.tik.tools/firehose/gifters?apiKey=${KEY}&mode=region®ion=US%2B&min_diamonds=1000`
)
ws.on('message', (raw) => {
const evt = JSON.parse(raw)
// evt: { type:'gifter_alert', ts, gifter, creator, gift, region }
})Examples
Chat Bot
import { TikTokLive } from '@tiktool/live';
const live = new TikTokLive({
uniqueId: 'streamer_name',
apiKey: 'YOUR_API_KEY',
});
live.on('chat', (e) => {
if (e.comment.toLowerCase() === '!hello') {
console.log(`Hello, ${e.user.nickname}!`);
}
});
live.on('gift', (e) => {
if (e.repeatEnd) {
console.log(`${e.user.uniqueId} sent ${e.repeatCount}x ${e.giftName} (${e.diamondCount * e.repeatCount} diamonds)`);
}
});
await live.connect();Live PK Battle Dashboard
import { TikTokLive } from '@tiktool/live';
const live = new TikTokLive({ uniqueId: 'streamer_name', apiKey: 'YOUR_API_KEY' });
live.on('battle', e => {
if (e.status === 1) console.log('🥊 PK STARTED');
if (e.status === 3) console.log('🏁 PK ENDED');
});
live.on('battleArmies', e => {
console.log(`\n⏱ ${e.secsRemaining}s remaining`);
for (const host of e.hosts ?? []) {
const top = host.contributors[0];
console.log(` ${host.hostUserId}: ${host.teamTotalScore} ` +
`(MVP: ${top?.nickname || top?.userId || '—'} ${top?.score ?? 0})`);
}
});
live.on('battleItemCard', e => {
const tag = e.multiplier > 0 ? `x${e.multiplier} BOOSTER` : e.effect.toUpperCase();
console.log(`💥 ${e.senderNickname} → ${tag} (${e.durationSec}s)`);
});
await live.connect();OBS Overlay
import { TikTokLive } from '@tiktool/live';
import { WebSocketServer } from 'ws';
const wss = new WebSocketServer({ port: 8080 });
const live = new TikTokLive({
uniqueId: 'streamer_name',
apiKey: 'YOUR_API_KEY',
});
live.on('event', (event) => {
for (const client of wss.clients) {
client.send(JSON.stringify(event));
}
});
await live.connect();
console.log('Forwarding events to ws://localhost:8080');HLS / FLV Playback URL
const info = await live.getStreamUrl();
console.log('HLS:', info.hlsPullUrl);
console.log('FLV:', info.flvPullUrl);
// Or pick a specific quality
console.log('FULL_HD1 HLS:', info.streamUrls.FULL_HD1?.hls);TypeScript
Full TypeScript support with type inference:
import {
TikTokLive,
ChatEvent,
GiftEvent,
BattleArmiesEvent,
BattleItemCardEvent,
BattleHost,
BattleContributor,
} from '@tiktool/live';
const live = new TikTokLive({
uniqueId: 'username',
apiKey: 'YOUR_API_KEY',
});
live.on('chat', (event: ChatEvent) => {
const username: string = event.user.uniqueId;
const message: string = event.comment;
});
live.on('gift', (event: GiftEvent) => {
const diamonds: number = event.diamondCount;
const isCombo: boolean = event.combo;
});
live.on('battleArmies', (event: BattleArmiesEvent) => {
for (const host of event.hosts ?? []) {
const mvp: BattleContributor | undefined = host.contributors[0];
if (mvp) console.log(mvp.nickname, mvp.score);
}
});
live.on('battleItemCard', (event: BattleItemCardEvent) => {
if (event.multiplier > 0) {
console.log(`x${event.multiplier} buff active for ${event.durationSec}s`);
}
});Changelog
2.8.0 (2026-05-19)
- NEW
battleItemCardevent — x2/x3 boosters, gloves, mist, thunder, extra-time, match-guide. IncludesiconUrl,iconKey,accentColorfor drop-in overlay use. - NEW
BattleArmiesEvent.hosts[]— multi-guest PK breakdown with per-hostcontributors[]sorted MVP first. - NEW
matchId,sessionId,serverTsMs,sessionTag,secsRemainingfields onBattleArmiesEvent. - Verified live against multi-guest battles 2026-05-19.
License
MIT © tiktool
