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

@toktikhq/sdk-js

v0.5.0

Published

Typed JavaScript client for the TokTik Developer API (REST + realtime LIVE events)

Readme

TokTik JavaScript SDK

Typed client for the TokTik Developer API — REST data-plane + realtime LIVE event stream.

npm i @toktikhq/sdk-js

Quickstart

import { TokTikClient } from "@toktikhq/sdk-js";

const client = new TokTikClient({ apiKey: process.env.TOKTIK_API_KEY! });

// REST — provenance-enveloped responses expose `{ data, provenance }`, unchanged.
const creators = await client.creators.list({ q: "cooking", limit: 10 });
console.log(creators.provenance.freshness, creators.data.results);

const board = await client.rankings.official({ board: "hourly", region: "VN" });
console.log(board.provenance.freshness, board.data.board.entries);

// Realtime — three lines to a live event stream.
const stream = await client.live.stream(["@some.creator"], {
  onEvent: (frame) => console.log(frame.event, frame.data),
  onStatus: (frame) => console.log(frame.creatorId, "→", frame.status)
});

The client stays thin: it sends your bearer key only to the configured API origin and leaves REST retries, caching, and key storage to the calling application. (The realtime stream is the one exception — reconnection there is not optional, see below.)

Provenance is part of the answer

Most data methods return { data, provenance } and the SDK never strips the envelope. Much of this data is observed, not officially published, so freshness (near_realtime / stale / historical), source and coverageStatus tell you how much to trust a given answer.

A few shapes deliberately differ, matching the server: exports return the job / job list directly, account.usage() returns its own { period, balance, items } shape, and exports.download() returns the CSV as a string, not JSON. Everything else is enveloped.

Realtime

client.live.stream(creatorIds, handlers) handles the parts that are easy to get wrong:

  • Token lifetime — mints a short-lived handshake token per connection (via POST /v1/live/stream/token), so an expiry mid-stream costs a reconnect, not a dead socket.
  • Credential transport — the token travels in the Sec-WebSocket-Protocol subprotocol, never the URL, where it would leak into access logs and browser history.
  • Reconnect — exponential backoff with full jitter and a bounded ceiling, so a fleet does not stampede a restarted gateway.
  • Resume — the gateway keeps no per-connection memory, so the SDK re-subscribes everything after a reconnect. Subscribe/unsubscribe issued while disconnected is replayed on the next open.
const stream = await client.live.stream(["creator.one"], {
  onEvent: (f) => console.log(f.event, f.sequence, f.data),
  onStatus: (f) => console.log(f.status, f.reason),      // queued | active | offline | unavailable
  onError: (e) => console.error(e),
  onReconnect: () => console.log("resumed")
});

stream.subscribe("creator.two");
stream.unsubscribe("creator.one");
stream.close();                                          // final — stops reconnecting

Statuses are honest: a creator whose room is still being admitted/acquired reports queued, not active. active means events are actually flowing.

Node

Node 22+ has a global WebSocket, so nothing extra is needed. On older runtimes pass a factory:

import WebSocket from "ws";
client.live.stream(ids, handlers, {
  socketFactory: (url, token) => new WebSocket(url, ["bearer", token]) as any
});

The two subprotocol values must be the literal bearer followed by the token — the gateway selects and echoes the marker, and a client that offers subprotocols closes the socket if the server selects none.

Pending (202) answers

A creator we hold nothing fresh for is resolved from TikTok on demand, so creators.get, content.creatorStories, creators.following / followers, content.videoComments and live.playback can answer 202 "not ready yet": { status: "resolving" | "collecting", retryAfterSeconds, … } with no data. It is a success, not an error (nothing throws), and it is never billed. The SAME call returns the real envelope once the read lands; while TikTok's read is failing the server holds it for up to 120 s per call.

Since 0.5.0 these methods are typed as a union with that pending body, so TypeScript makes you handle it. Narrow with isPending, or let waitUntilReady poll:

import { isPending, TokTikPendingTimeoutError, waitUntilReady } from "@toktikhq/sdk-js";

const answer = await client.creators.get("someone");
if (isPending(answer)) {
  console.log(`check again in ${answer.retryAfterSeconds} s`);
} else {
  console.log(answer.data.creator.followerCount);
}

// Honours retryAfterSeconds (clamped to 1–120 s), gives up after 3 minutes (maxWaitMs), stops on `signal`.
try {
  const profile = await waitUntilReady(() => client.creators.get("someone"));
  console.log(profile.data.creator.followerCount); // no narrowing needed
} catch (error) {
  if (error instanceof TokTikPendingTimeoutError) error.retryAfterSeconds; // still not ready
}

A non-2xx answer inside waitUntilReady still throws TokTikApiError unchanged. TokTikPendingTimeoutError is deliberately not a TokTikApiError (the server answered 202 every time). After a timeout, calling the method again later is safe — but a manual retry that lands the read may be billed like any delivered read.

Errors

Failures (non-2xx answers) throw TokTikApiError, which keeps the status distinguishable rather than collapsing it. A 202 "not ready yet" is not a failure — see "Pending (202) answers" above.

try {
  await client.creators.get("someone");
} catch (error) {
  if (error instanceof TokTikApiError) {
    error.isUnauthorized;    // 401 — bad or expired key
    error.isForbidden;       // 403 — key lacks the scope this resource is sold under
    error.isPaymentRequired; // 402 — out of credits; retrying will not help
    error.isRateLimited;     // 429
    error.retryable;         // 429 + 5xx only
    error.requestId;         // quote this to support
  }
}

Coverage

| Namespace | Scope | Endpoints | |---|---|---| | client.live | live:read, live:stream | creator performance, stream token, stream() | | client.rankings | rank:read | official({ board }), movers({ board }), history({ board }), regions(), games({ region }) | | client.livePro | live-pro:read | directory({ region }), regions() — the LIVE Pro creator directory | | client.recruit | rank:read (pool), watchlist:manage (marks) | list({ region, excludeLivePro, … }), mark(creatorUid, { status, note }), unmark(creatorUid), marks() | | client.creators | creator:read | list, get, changes, analysis, following, followers | | client.content | content:read | creator videos (no params), video, video comments | | client.gifters | gifter:read | list, get, for creator | | client.agencies | agencies:read | list, get, regions — TikTok's LIVE agency directory; contact details only on the Agency plan | | client.trends | trend:read | list | | client.exports | export | list, create, get, download | | client.account | keys:manage | entitlements, usage |

Option names mirror the server schemas exactly. Those routes declare additionalProperties: false, so an invented parameter is a hard 400 — not an ignored extra.

The pre-D3.1 method listCreatorPerformance still works and is marked deprecated; it forwards to client.live.creatorPerformance.

0.5.0 (breaking types, same runtime): the 202-capable methods (creators.get, creators.following / followers, content.creatorStories, content.videoComments, live.playback) are typed as a union with their pending body, so code that read .data without narrowing no longer compiles. Adds isPending, waitUntilReady, TokTikPendingTimeoutError, PendingResult and LivePlaybackPending (CR.9). What the methods return at runtime is unchanged.

0.4.1: recruit.list({ country }) and the recruit export accept country (the creator's RESOLVED country — what a Recruit country card groups by; region stays the board bucket). Additive, no breaking change.

0.4.0: adds client.livePro (directory, regions — LP.4/LP.7) and client.recruit (list, mark, unmark, marks — the recruiting pool + your workspace's marks, EC.1/EC.2); exports.create takes the new recruit dataset with its filters (region, excludeLivePro, newOnly, minFollowers, maxCoins30d). Additive, no breaking change.

0.3.0 (breaking): live.listSessions, live.getSession and the deprecated listLiveSessions / getLiveSession were removed — GET /v1/live/sessions is no longer part of the API. exports.create now takes the full export request: rankings (board + region), gifters (rangeDays, optional region / segment), creator_roster, creator_gifters (creatorUniqueId, optional window); live_sessions is gone.

Agencies: client.agencies.list({ region: "MENA", q: "lumio", sort: "size", limit: 24 }), client.agencies.get(agencyId) and client.agencies.regions() read TikTok's LIVE agency directory. region is TikTok's own agency region bucket (MENA, US+), sizes are TikTok's published bands, and get() carries the agency's email / phone / address (contact, public on every plan) plus its links when known. The directory is not exportable (dataset: "agencies" was removed in EX.7 and answers 400).

Publishing

The npm artifact is self-contained: @v2/contracts is a build-only workspace dependency and the public declaration graph is bundled into dist/index.d.ts. npm run test:package imports the built ESM entry point and type-checks a clean consumer whose only dependency is this SDK.

Releases are tag-driven through .github/workflows/sdk-release.yml:

git tag sdk-js-v0.5.0
git push origin sdk-js-v0.5.0

The npm package must have a Trusted Publisher configured for this repository and that workflow. The package remains ESM-only ("type": "module"); a dual ESM/CJS build is not currently provided.

Known scope limits

  • Alert-rule (webhook:manage) and API-key (keys:manage) management endpoints are sellable but intentionally out of scope here; only usage/entitlements are exposed.