You are building an influencer analytics product on top of TikLiveAPI, and you have one TikTok-shaped decision to make before you write a single migration: do all of your tenants share one TikLiveAPI key that you own, or does every tenant bring their own? That single fork shapes your pricing page, your blast radius during incidents, your GDPR posture, and the rate-limit code you ship in week two.
This guide walks the three viable architectures, the data-isolation patterns that map to each, the rate-limit fairness code you need when you absorb the key, the onboarding flow when tenants bring their own, and the Postgres schema that meters everything. Throughout, the upstream is TikLiveAPI at https://api.tikliveapi.com, authenticated with the X-Api-Key header.
You can serve N tenants with one TikLiveAPI key, with N TikLiveAPI keys, or with a mix. The three patterns are not equivalent. They differ in who absorbs credit cost, who eats a rate-limit cap, and who has to log in to profile when something breaks.
You hold one TikLiveAPI key. Every tenant request you proxy upstream is stamped with a tenant_id column on the row you persist. You absorb the credit cost upstream and charge tenants per seat or per workspace.
import httpx, os
TIKLIVE = "https://api.tikliveapi.com"
KEY = os.environ["TIKLIVE_KEY"] # one platform-wide key
async def fetch_user_info(tenant_id: str, username: str) -> dict:
headers = {"X-Api-Key": KEY}
async with httpx.AsyncClient(timeout=15) as c:
r = await c.get(f"{TIKLIVE}/userinfo-by-username/",
params={"username": username},
headers=headers)
r.raise_for_status()
body = r.json()
# response shape: { "user": {...uniqueId, secUid...}, "stats": {...followerCount...} }
await persist_user_snapshot(tenant_id, body["user"], body["stats"])
return body
Pros: one onboarding step (tenant signs up, you start working), simple seat pricing, you pre-buy credits in bulk on pricing and capture the margin. Cons: a single noisy tenant burns the shared rate-limit budget, and one suspended key takes down every customer. You also become the data controller for everything you fetch, not the processor.
Every tenant registers their own TikLiveAPI account, buys their own credits, and pastes their X-Api-Key into your settings page. You proxy with their key. They see usage on their own TikLiveAPI dashboard and you never touch their credit balance.
async def fetch_post_detail(tenant_id: str, url: str) -> dict:
key = await get_tenant_key(tenant_id) # AES-decrypted on read
headers = {"X-Api-Key": key}
async with httpx.AsyncClient(timeout=15) as c:
r = await c.get(f"{TIKLIVE}/post-detail/",
params={"url": url},
headers=headers)
if r.status_code == 401:
await mark_key_invalid(tenant_id)
raise TenantKeyInvalid()
body = r.json()
# flat snake_case response with id, play, wmplay, hdplay, music_info, author{}
return body
Pros: zero cost variance, no rate-limit cross-pollution, GDPR-clean processor role. Cons: your activation funnel now has a TikLiveAPI signup wedged into it, and support tickets often start with "my key returned 401."
Free and trial tenants use your shared platform key with a hard daily quota. Paid tenants bring their own key from register. The promotion flow on upgrade swaps the key reference in the tenants table. Most B2B SaaS in this space land here because it preserves a frictionless trial without making the founder eat unbounded credit cost for tire-kickers.
Pick by tenant scale, not by religion.
tenant_id, Postgres RLS policies enforce isolation. Cheap, queryable across tenants for your own analytics, fine for any tenant that does not contractually demand otherwise.For 95% of TikLiveAPI-powered analytics products under 1,000 tenants, RLS is correct. Resist the urge to over-isolate.
CREATE POLICY tenant_isolation ON tiktok_user_snapshots
USING (tenant_id = current_setting('app.tenant_id')::uuid);
ALTER TABLE tiktok_user_snapshots ENABLE ROW LEVEL SECURITY;
Set app.tenant_id at the start of every request inside your connection pool's after_connect hook and Postgres does the rest.
If you ship Pattern A or the free tier of Pattern C, you need a per-tenant token bucket. A Redis-backed bucket with Lua atomicity keeps it honest under concurrency.
import redis.asyncio as redis
LUA = """
local key = KEYS[1]
local cap = tonumber(ARGV[1])
local refill = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local bucket = redis.call('HMGET', key, 'tokens', 'ts')
local tokens = tonumber(bucket[1]) or cap
local ts = tonumber(bucket[2]) or now
tokens = math.min(cap, tokens + (now - ts) * refill)
if tokens < 1 then
redis.call('HMSET', key, 'tokens', tokens, 'ts', now)
return 0
end
redis.call('HMSET', key, 'tokens', tokens - 1, 'ts', now)
return 1
"""
r = redis.from_url(os.environ["REDIS_URL"])
take = r.register_script(LUA)
async def allow(tenant_id: str, cap=600, refill=10) -> bool:
import time
ok = await take(keys=[f"tb:{tenant_id}"], args=[cap, refill, int(time.time())])
return ok == 1
Cap of 600 with refill of 10 tokens/sec gives roughly 36,000 calls/hour per tenant, well clear of /user-posts/ or /post-comments/ spikes without letting one tenant starve the others.
When the tenant pastes a key, do not just save it. Validate it.
GET /userid/?username=tiktok. Cheap, returns the flat {"id":"107955"} shape, confirms auth.play, wmplay, and hdplay, you have download tier access.CREATE TABLE tenants (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
name text NOT NULL,
plan text NOT NULL CHECK (plan IN ('free','pro','enterprise')),
key_mode text NOT NULL CHECK (key_mode IN ('shared','byo')),
tiklive_key_enc bytea, -- AES-GCM, NULL when shared
tiklive_key_last4 char(4),
monthly_quota int NOT NULL DEFAULT 10000,
created_at timestamptz DEFAULT now()
);
CREATE TABLE tenant_users (
tenant_id uuid REFERENCES tenants(id) ON DELETE CASCADE,
user_id uuid NOT NULL,
role text NOT NULL CHECK (role IN ('owner','admin','member')),
PRIMARY KEY (tenant_id, user_id)
);
CREATE TABLE usage_events (
id bigserial PRIMARY KEY,
tenant_id uuid NOT NULL REFERENCES tenants(id),
endpoint text NOT NULL, -- '/userinfo-by-username/' etc.
status int NOT NULL,
credits_charged int NOT NULL,
request_id text,
occurred_at timestamptz NOT NULL DEFAULT now()
) PARTITION BY RANGE (occurred_at);
CREATE INDEX ON usage_events (tenant_id, occurred_at DESC);
CREATE TABLE tiktok_user_snapshots (
tenant_id uuid NOT NULL,
sec_uid text NOT NULL,
unique_id text NOT NULL,
follower_count int,
following_count int,
heart_count bigint,
video_count int,
captured_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (tenant_id, sec_uid, captured_at)
);
Partition usage_events monthly. Aggregate into usage_daily_rollup for billing so you never scan the partition for invoice math.
+----------------+ +---------------------+
| Tenant browser | -----> | Your edge (Cloudflare)|
+----------------+ +----------+----------+
|
v
+----------+----------+
| API gateway |
| - auth tenant |
| - set app.tenant_id|
+----------+----------+
|
+----------------------+----------------------+
| | |
v v v
+-----+------+ +------+------+ +------+------+
| Redis | | Postgres | | Worker pool |
| token bkts | | RLS, usage | | proxies to |
+------------+ +-------------+ | TikLiveAPI |
+------+------+
|
v
X-Api-Key:
https://api.tikliveapi.com
Minimal Terraform for the worker pool's secret store:
resource "aws_secretsmanager_secret" "tiklive" {
name = "tiklive/platform-key"
}
resource "aws_kms_key" "byo_keys" {
description = "Envelope key for tenant TikLiveAPI keys"
enable_key_rotation = true
}
A two-founder team runs an influencer brief tool. 200 active tenants, mostly agencies tracking 50-500 creators each. They run Pattern C: free tier and 14-day trials get the shared key with a 5,000-call/day cap, paid plans paste their own key from TikLiveAPI.
Their nightly refresh hits /userinfo-by-username/ for every tracked creator, then /user-posts/ with count=30 paginated by cursor. Twice a week they refresh /user-followers/, where pagination uses the time param and the response carries the matching top-level time key. They also call /user-following/ for outbound graph data, careful to read the followings (plural with trailing s) array. When agencies pull comments, they parse id from each item in /post-comments/ (not comment_id).
Average load: 7.5M upstream calls/month at peak, distributed via Redis token buckets at 600 cap / 10 refill per tenant. P50 upstream latency around 750ms, and they cache user snapshots for 6 hours to avoid burning credits on dashboard refreshes. Founders test new endpoints in the playground before writing client code.
Under GDPR your role depends on who decides what is fetched.
tiktok_user_snapshots rows for the data subject, and document that the upstream TikLiveAPI service is stateless on your behalf.Can I rotate a tenant's BYO key without downtime? Yes. Store keys versioned; when the tenant submits a new one, validate it with a /userid/ call before swapping the active pointer. Old in-flight requests finish with the old key.
How do I bill fairly when one endpoint costs more credits than another? Pull the credits_charged value from your own response wrapper after each upstream call and write it into usage_events. Invoice on the rollup, not on raw call count.
What about regional residency for EU customers? RLS plus an EU-resident Postgres cluster covers most cases. The upstream API is stateless from your DB's perspective; what matters is where snapshots are persisted.
Should I cache TikLiveAPI responses? Yes, with short TTLs (5-15 minutes) for user info and posts, longer (24 hours) for music and challenge metadata. Never cache /download-video/ URLs across requests; they expire.
Where do I start if I am pre-launch? Pattern A with hard per-tenant caps, RLS in one Postgres, Redis token buckets. Migrate paid tenants to BYO once you have 50+ tenants or one customer asks the suspension question. Read blog for migration notes and contact support before going live with shared-key billing at scale.
Ready to put what you read into code? Try our endpoints live or grab the full reference.