npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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

Readme

@tiktool/live

Connect to any TikTok LIVE stream in 4 lines of code.

npm version License: MIT Node.js TypeScript

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/live

Get 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 ws for 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_credentials call 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-agent

Mode 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_idroom_id | | /webcast/room_info | POST | sandbox+ | unique_idroom_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 userIdunique_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&region=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 battleItemCard event — x2/x3 boosters, gloves, mist, thunder, extra-time, match-guide. Includes iconUrl, iconKey, accentColor for drop-in overlay use.
  • NEW BattleArmiesEvent.hosts[] — multi-guest PK breakdown with per-host contributors[] sorted MVP first.
  • NEW matchId, sessionId, serverTsMs, sessionTag, secsRemaining fields on BattleArmiesEvent.
  • Verified live against multi-guest battles 2026-05-19.

License

MIT © tiktool