Developer Docs
SDKs
The official JavaScript and Python clients.
Two official clients wrap the same /v1 contract and never strip the provenance envelope. Both are typed; pick the language and stay on it.
| SDK | Package | Version | Targets |
|---|---|---|---|
| JavaScript / TypeScript | @toktik/sdk-js | 0.2.0 | v1 |
| Python | toktik | 0.1.0 | v1 |
JavaScript / TypeScript
@toktik/sdk-js — a typed REST client plus a realtime LIVE-event client that owns its own token lifetime and reconnection.
npm i @toktik/sdk-jsInitialise
import { TokTikClient } from "@toktik/sdk-js";
const client = new TokTikClient({ apiKey: process.env.TOKTIK_API_KEY! });Call the REST API
const board = await client.rankings.official({ board: "hourly", region: "VN" });
console.log(board.data.board); // the ranked rows
console.log(board.provenance); // freshness, source, coverageStatus — never stripped
const sessions = await client.live.listSessions({ limit: 20 });Realtime
import { TokTikClient } from "@toktik/sdk-js";
const client = new TokTikClient({ apiKey: process.env.TOKTIK_API_KEY! });
const stream = await client.live.stream(["@creator_handle"], {
onStatus: (s) => console.log("status", s.creatorId, s.status), // queued → active
onEvent: (frame) => {
if (frame.event === "gift") {
const g = frame.data.decoded;
if (g.repeatEnd) console.log(g.gift?.giftName, (g.gift?.diamondCount ?? 0) * g.repeatCount);
}
},
onError: (e) => console.error(e),
onReconnect: (attempt) => console.warn("reconnected", attempt)
});
// later: await stream.close();Errors & retries
import { TokTikClient, TokTikApiError } from "@toktik/sdk-js";
try {
await client.creators.get("someone");
} catch (err) {
if (err instanceof TokTikApiError) {
if (err.isPaymentRequired) {/* 402 — top up credits */}
else if (err.status === 403) {/* missing scope */}
else if (err.retryable) {/* 429 / 5xx — back off and retry */}
}
}Python
toktik — the REST client is dependency-free (standard-library HTTP); the realtime client is asyncio and needs the realtime extra.
pip install toktik # REST only — zero dependencies
pip install "toktik[realtime]" # adds websockets for the realtime clientInitialise
from toktik import TokTikClient
client = TokTikClient(api_key="ttk_live_...")Call the REST API
board = client.rankings.official(board="hourly", region="VN")
print(board["data"]["board"]) # the ranked rows
print(board["provenance"]) # freshness, source, coverageStatus
sessions = client.live.list_sessions(limit=20)Realtime
import asyncio
from toktik import TokTikClient
async def main():
client = TokTikClient(api_key="ttk_live_...")
async for frame in await client.live.stream(["@creator_handle"]):
if frame.type == "status":
print("status", frame.creator_id, frame.status)
elif frame.type == "event" and frame.event == "gift":
g = frame.data["decoded"]
if g.get("repeatEnd"):
print(g["gift"]["giftName"], g["gift"]["diamondCount"] * g["repeatCount"])
asyncio.run(main())Errors & retries
from toktik import TokTikApiError
try:
client.creators.get("someone")
except TokTikApiError as err:
if err.is_payment_required: # 402 — top up credits
...
elif err.is_forbidden: # 403 — missing scope
...
elif err.retryable: # 429 / 5xx — back off and retry
...Namespaces
Both clients expose the same namespaces over the same scopes:
| Namespace | JavaScript | Python | Scope |
|---|---|---|---|
rankings | official, derived | official, derived | rank:read |
live | listSessions, getSession, creatorPerformance, streamToken, stream | list_sessions, get_session, creator_performance, stream_token, stream | live:read / live:stream |
creators | list, get, changes, analysis, following, followers | list, get, changes, analysis, following, followers | creator:read |
content | creatorVideos, video, videoComments | creator_videos, video, video_comments | content:read |
gifters | list, get, forCreator | list, get, for_creator | gifter:read |
trends | list | list | trend:read |
exports | list, create, get, download | list, create, get, download | export |
account | entitlements, usage | entitlements, usage | keys:manage |
Recipes
Common tasks, in both languages. Each is a real call sequence against the shipped surface.
Poll a leaderboard every hour and store it
A cron job that pulls the official board and persists the rows with their provenance.
const { data, provenance } = await client.rankings.official({ board: "hourly", region: "VN" });
await db.saveBoard(data.board, provenance.observedAt);Watch one creator's gifts in realtime
Subscribe to a single creator and total each finished gift streak.
const stream = await client.live.stream(["@creator_handle"], {
onEvent: (f) => {
if (f.event === "gift" && f.data.decoded.repeatEnd) {
const g = f.data.decoded;
console.log(g.gift?.giftName, (g.gift?.diamondCount ?? 0) * g.repeatCount);
}
}
});Export sessions to CSV
Kick off a bulk export, poll until it is ready, then download the CSV text.
const job = await client.exports.create({ dataset: "live_sessions", format: "csv" });
// poll client.exports.get(job.id) until status is ready, then:
const csv = await client.exports.download(job.id); // CSV text, not JSONVersioning
Both SDK lines target the v1 API. The API is versioned in its path (/v1) and only ever grows compatibly within a major — see Versioning. Pin the SDK version in your lockfile; upgrading within the v1 line is additive.