toktik — Python SDK for the TokTik Developer API
Typed Python client for the TokTik Developer API: the full REST data-plane plus a realtime
(asyncio) LIVE-event client. Feature parity with @toktikhq/sdk-js; the contract source
of truth is @v2/contracts / the served GET /openapi.json.
from toktik import TokTikClient
client = TokTikClient(api_key="ttk_live_...")
board = client.rankings.official(board="hourly", region="VN")
print(board["provenance"]) # the envelope is never stripped
import asyncio
from toktik import TokTikClient, EventFrame
async def main():
client = TokTikClient(api_key="ttk_live_...")
async for frame in await client.live.stream(["@creator"]):
if isinstance(frame, EventFrame):
print(frame.event, frame.data) # chat / gift / like / ...
asyncio.run(main())
Install
pip install toktik # REST only — zero dependencies
pip install "toktik[realtime]" # adds `websockets` for the realtime client
The REST client speaks HTTP through the standard library, so the data-plane surface pulls in nothing.
The realtime client needs a WebSocket implementation; installing the realtime extra provides it, or
you can pass your own transport (RealtimeStream(connect=...)).
Design
- Provenance is never stripped. Data methods return the parsed JSON envelope unchanged —
{"data": ..., "provenance": {...}}. For observed (rather than officially published) data, that context is part of the answer. The two documented exceptions match the JS SDK:exports.downloadreturns CSV text, andaccount.usageis returned directly (no envelope). - Errors are surfaced, not swallowed. A non-2xx raises
TokTikApiErrorcarryingstatus,code,request_idand helpers (is_payment_requiredfor 402,is_forbiddenfor 403,is_rate_limitedfor 429,retryable). Distinguishing them is the whole point: 402 means buy credits, 403 means the key lacks the scope, 429 means back off. - Argument names are snake_case, mapped to the server's wire names. Every route's Fastify schema
sets
additionalProperties: false, so a wrong query key is a hard 400. The mapping is transcribed from the routes, and a CI parity test asserts every documented data-plane path is covered. - The realtime client owns token lifetime, reconnection and resume. A fresh handshake token is
minted per connection (expiry becomes a reconnect, not a dead socket); reconnects use exponential
backoff with full jitter; and because the gateway keeps no per-connection memory, subscriptions are
re-sent on every open.
queued/active/offline/unavailableare reported honestly.
REST surface
| Namespace | Methods | Scope |
|---|---|---|
client.rankings |
official(board, region, limit), movers(board, region, limit), history(board, region, limit, cursor, league_tier), regions(), games(region) |
rank:read |
client.live_pro |
directory(region, live_only, limit), regions() — the LIVE Pro creator directory |
live-pro:read |
client.recruit |
list(region, exclude_live_pro, new_only, min_followers, max_coins_30d, min_score_today, sort, limit, cursor), mark(creator_uid, status, note), unmark(creator_uid), marks(status, limit, cursor) |
rank:read (pool), watchlist:manage (marks) |
client.live |
creator_performance, stream_token, stream |
live:read / live:stream |
client.creators |
list, get, changes, analysis, following, followers |
creator:read |
client.content |
creator_videos, video, video_comments |
content:read |
client.gifters |
list, get, for_creator |
gifter:read |
client.agencies |
list(region, q, service_type, public_profile_only, sort, limit, offset), get(agency_id), regions() — contact details only on the Agency plan |
agencies:read |
client.trends |
list(region, type) |
trend:read |
client.exports |
list, create, get, download |
export |
client.account |
entitlements, usage |
keys:manage |
Realtime
await client.live.stream(creator_ids) returns a connected RealtimeStream — an async iterator of
frames:
StatusFrame(creator_id, status, room_id, reason)— subscription lifecycle (queued→active→…, oroffline).EventFrame(event, event_id, creator_id, room_id, sequence, room_state, data, provenance)— a LIVE event (chat,gift,like,member,roomUser,social,control,envelope,goodyBag,unknown).ErrorFrame(code, message, retryable)— a server-side subscription error.
subscribe(id) / unsubscribe(id) change the set mid-stream; aclose() (or async with) stops for
good and cancels reconnection.
The token is minted from platform-api; its wsUrl points at the API host, which nginx routes to the
gateway in production. Driving a local gateway directly, mint the token yourself and pass the
gateway URL — see examples/realtime_demo.py.
Development
python -m venv .venv && . .venv/bin/activate
pip install -e ".[dev]"
python -m unittest discover -s tests # 51 tests, no network
mypy && ruff check src tests
The OpenAPI parity snapshot is generated from @v2/contracts (regenerated + diffed in CI):
npm run build --workspace @v2/contracts
node packages/sdk-python/scripts/gen_openapi_snapshot.mjs
0.3.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.3.0
Adds client.live_pro (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, exclude_live_pro, new_only, min_followers, max_coins_30d). Additive, no breaking change.
0.2.0 (breaking)
live.list_sessions and live.get_session were removed — GET /v1/live/sessions answers 410 endpoint_removed
since 2026-09-24. exports.create now requires dataset and takes every real export: rankings (board, region,
optional game / period), gifters (range_days, optional region / segment), creator_roster, and
creator_gifters (creator_unique_id, optional window). live_sessions is no longer a dataset.
dataset="agencies" exports TikTok's LIVE agency directory (optional region / query); its contact_* columns exist only on the Agency plan.
Publishing
Releases are tag-driven through .github/workflows/sdk-release.yml:
git tag sdk-python-v0.3.1
git push origin sdk-python-v0.3.1
The release job builds and checks both the sdist and wheel, installs the wheel into a clean virtual
environment, and publishes with PyPI Trusted Publishing. The PyPI project must trust this repository,
that workflow, and the pypi GitHub environment before the first release.
Known scope limits
- Response bodies are typed
dict(JsonDict), not generated models. The shapes live in@v2/contracts(TypeScript); generating Pydantic models from them was deliberately deferred to avoid silent drift. Method arguments and realtime frames are typed. - Sync REST only. An
asyncioREST client is not yet provided; the realtime client is async, and it mints its token off the event loop viaasyncio.to_thread.
Metadata
Release files for toktik 0.3.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| toktik-0.3.1.tar.gz | 31.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| toktik-0.3.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 54.8 kB
Release files / toktik-0.3.1.tar.gz
| Download URL | toktik-0.3.1.tar.gz |
|---|---|
| Size | 31.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ac1fbed45fdb40aea5fa529f6e4e01c7f6bc30e3b0e2f5818ce886fa6b8391b1
|
|
BLAKE2b-256 checksum How to use checksums |
af4c76946eb22d027a4aec200ca8730760a27452a2e3728b97b00a79edf1c908
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.6
|
Release files / toktik-0.3.1-py3-none-any.whl
| Download URL | toktik-0.3.1-py3-none-any.whl |
|---|---|
| Size | 23.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6ebad7ef1ed08b1f7ad41d78c1cfe5916c3a1c7a8ff84e3963d2cdf24d064714
|
|
BLAKE2b-256 checksum How to use checksums |
3f5afe33fb085005dcbec4804071729d0f5313b8db121adb76236392c87eb7da
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.6
|