TypeScript SDK
Zero-dependency TypeScript and JavaScript client built on Web-standard fetch, FormData and Blob: runs on Node 18+, Bun, Deno, Cloudflare Workers and Next.js Edge, with immutable matrix scoping, async pagination, multipart publishing and decrypt inspectors.
Installation & runtimes
Ships dual ESM + CommonJS bundles with strict .d.ts declarations and zero runtime dependencies. Runs unmodified on Node.js 18+, Bun, Deno, Cloudflare Workers, and Next.js Server Actions / Edge Runtime:
npm install tikultraQuickstart & credit metadata
Instantiate TikUltra with an explicit apiKey or let it read process.env.TIKULTRA_KEY (or Deno.env). Every call resolves to a typed APIResponse<T> carrying res.data, res.creditsCost, res.creditsRemaining, res.creditsTier and res.requestId:
1import { TikUltra } from 'tikultra'
2
3const client = new TikUltra({ apiKey: process.env.TIKULTRA_KEY, os: 'ios', appFlavour: 'tiktok', region: 'US' })
4
5const [video, profile, search, pdp] = await Promise.all([
6 client.video.get({ aweme_id: '7707063215498399006' }),
7 client.user.profile({ username: 'tiktok' }),
8 client.search.query({ keyword: 'mechanical keyboards', count: 10 }),
9 client.shop.pdp({ product_id: '1729487391071113352' }),
10])
11
12console.log(video.data.aweme_id, profile.data.user?.unique_id, search.data.items?.length)
13console.log({ cost: pdp.creditsCost, remaining: pdp.creditsRemaining, requestId: pdp.requestId })
Namespaces map 1:1 to gateway domains: client.video, client.user, client.feed, client.search, client.tako, client.creator, client.douyin, client.shop, client.live, client.series, client.music, client.hashtag, client.account, client.devices, client.crypto and client.jobs, plus top-level client.resolve(...), client.idConverter(...), client.translate(...) and client.request(...).
Immutable matrix scoping & per-call overrides
client.withMatrix(...) clones a lightweight child client bound to any of the 8 app flavours and 95+ countries (84 cities) without mutating the parent instance, while { matrix } overrides individual calls:
1import { TikUltra } from 'tikultra'
2
3const client = new TikUltra({ apiKey: process.env.TIKULTRA_KEY, os: 'ios', appFlavour: 'tiktok', region: 'US' })
4
5// Immutable child client pinned to Android TikTok Shop in London
6const ukShop = client.withMatrix({ os: 'android', appFlavour: 'shop', region: 'GB', city: 'london' })
7const ukCatalog = await ukShop.shop.search({ keyword: 'ceramide moisturizer', count: 20 })
8
9// Per-call matrix override on a single request without creating a child client
10const jpFeed = await client.request('GET', '/v1/feed/persona-fyp', {
11 params: { niche: 'specialty coffee', count: 12 },
12 matrix: { os: 'ios', appFlavour: 'tiktok', region: 'JP', city: 'tokyo' },
13})
14console.log(ukCatalog.creditsCost, jpFeed.data.items?.length)
Async generator pagination
client.paginate(...) returns an AsyncGenerator<APIResponse> over any paginated GET or POST route, advancing cursor automatically until has_more is false or maxPages is reached:
1import { TikUltra } from 'tikultra'
2
3const client = new TikUltra({ apiKey: process.env.TIKULTRA_KEY })
4
5for await (const page of client.paginate('GET', '/v1/video/comments', { params: { aweme_id: '7705852133855161377', count: 50 }, maxPages: 10 })) {
6 for (const comment of page.data.comments ?? page.data.items ?? []) {
7 console.log(comment.cid, comment.text)
8 }
9 console.log({ cursor: page.cursor, hasMore: page.hasMore, creditsCost: page.creditsCost })
10}
Multipart video & slideshow publishing
client.account.uploadVideo and client.account.uploadSlideshow accept Web Blob, File, Uint8Array, ArrayBuffer, { filename, data, contentType } objects, or Node.js file paths:
1import { TikUltra } from 'tikultra'
2
3const client = new TikUltra({ apiKey: process.env.TIKULTRA_KEY })
4
5const videoPost = await client.account.uploadVideo({
6 account_id: 'acc_creator_01',
7 file: './launch.mp4', // or Blob / File / Uint8Array on Edge & Workers
8 caption: 'Shipping the TypeScript SDK today #typescript #devtools',
9 visibility: 'public',
10})
11
12const carousel = await client.account.uploadSlideshow({
13 account_id: 'acc_creator_01',
14 files: [new Blob([slide1Bytes], { type: 'image/jpeg' }), './slide_2.jpg'],
15 title: 'Edge latency benchmarks',
16 caption: 'Zero dependencies across Node, Bun, Deno & Workers',
17})
18console.log(videoPost.data.aweme_id, carousel.data.aweme_id)
Decrypt inspectors & typed errors
client.crypto includes first-class helpers for all 8 protocol inspection primitives (mssdkDecrypt, xArgusDecrypt, xLadonDecrypt, xGorgonDecrypt, xCylonsDecrypt, ttencryptDecrypt, zstdDecompress, protobufDecode), and retries 429/5xx automatically before throwing typed error classes:
1import {
2 AuthenticationError,
3 InsufficientCreditsError,
4 RateLimitError,
5 SessionExpiredError,
6 TikUltra,
7 UpstreamError,
8} from 'tikultra'
9
10const client = new TikUltra({ apiKey: process.env.TIKULTRA_KEY, maxRetries: 3 })
11
12try {
13 const argus = await client.crypto.xArgusDecrypt({ x_argus: process.env.CAP_X_ARGUS!, os: 'android' })
14 const ladon = await client.crypto.xLadonDecrypt({ x_ladon: process.env.CAP_X_LADON! })
15 const gorgon = await client.crypto.xGorgonDecrypt({ x_gorgon: process.env.CAP_X_GORGON! })
16 const cylons = await client.crypto.xCylonsDecrypt({ x_cylons: process.env.CAP_X_CYLONS! })
17 const mssdk = await client.crypto.mssdkDecrypt({ operation: 'report', payload_base64: process.env.CAP_MSSDK! })
18 const ttenc = await client.crypto.ttencryptDecrypt({ ciphertext_base64: process.env.CAP_TTENC! })
19 const uzstd = await client.crypto.zstdDecompress({ data_base64: process.env.CAP_ZSTD! })
20 const proto = await client.crypto.protobufDecode({ hex: uzstd.data.hex })
21 console.log(argus.data, ladon.data, gorgon.data, cylons.data, mssdk.data, ttenc.data, proto.data.fields)
22} catch (err) {
23 if (err instanceof RateLimitError) console.error('Retry after (s):', err.retryAfter, err.requestId)
24 else if (err instanceof InsufficientCreditsError) console.error('Balance exhausted:', err.code)
25 else if (err instanceof SessionExpiredError) console.error('Re-import session:', err.requestId)
26 else if (err instanceof AuthenticationError) console.error('Invalid API key:', err.status)
27 else if (err instanceof UpstreamError) console.error('Upstream 5xx (0 credits):', err.requestId)
28}