Multi-Tenant SaaS Architecture Patterns on TikLiveAPI

Published on May 29, 2026

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.

The Core Question

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.

Pattern A: Shared Key, Tenant Column

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.

Pattern B: Tenant-Owned Key (BYO)

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."

Pattern C: Hybrid

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.

Tradeoffs at a Glance

  • Pricing simplicity: A wins. Seat pricing is legible. B forces you to explain that customers also pay TikLiveAPI.
  • Blast radius: B wins. One tenant cannot starve another. In A, you need quotas (below) or one runaway scraper ruins the day.
  • Cost predictability: B wins. A turns your gross margin into a function of tenant behavior.
  • Customer concern about key suspension: Real and rational under A. Enterprise prospects will ask: "what happens if your TikLiveAPI account gets paused?" Pattern B sidesteps the question entirely; Pattern C lets you answer "your production tier owns its own key."
  • Time to first value: A wins. New tenant sees a dashboard in 30 seconds. B adds 3-10 minutes of TikLiveAPI signup.

Data Isolation Patterns

Pick by tenant scale, not by religion.

  • Row-level security (RLS), 1 to ~500 tenants: single database, single schema, every table carries 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.
  • Schema-per-tenant, ~500 to ~5,000 tenants: single database, one Postgres schema per tenant. Backups, restores, and per-tenant index tuning get easier. Migrations get worse.
  • Database-per-tenant, enterprise tier only: separate logical DB (or cluster) per tenant. Reserve for customers paying enough to justify the operational tax, or for those with regional residency demands.

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.

Rate-Limit Fairness with a Shared Key

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.

BYO Onboarding Flow

When the tenant pastes a key, do not just save it. Validate it.

  1. Test call: fire GET /userid/?username=tiktok. Cheap, returns the flat {"id":"107955"} shape, confirms auth.
  2. Scope check: hit one endpoint per category your product uses. If /post-detail/ returns the snake_case body with play, wmplay, and hdplay, you have download tier access.
  3. Credit floor: warn the tenant if their TikLiveAPI balance is below your minimum recommended threshold.
  4. Error messaging: a 401 means the key is wrong; a 402-shaped depletion means they need to top up at pricing; a 429 from your bucket is different from a 429 upstream, and the UI should say so.

Billing Models that Map to Credit Consumption

  • Flat seat fee, you absorb credits (Pattern A): works under ~50k calls/seat/month. Above that, your unit economics break.
  • Seat fee + metered overage: include 10k calls/seat/month, bill $X per additional 1k. Maps cleanly to the profile usage chart.
  • Pure usage: $Y per 1k tracked profiles refreshed daily. Predictable on your side, opaque on theirs.
  • BYO + platform fee (Pattern B): flat $Z/month for the dashboard, customer pays TikLiveAPI directly. Highest gross margin, lowest support burden.

Tenant + Usage Metering Schema

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.

Reference Architecture

+----------------+        +---------------------+
| 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
}

Reference Example: Mid-Tier Influencer Analytics, 200 Tenants

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.

Compliance: GDPR Roles

Under GDPR your role depends on who decides what is fetched.

  • Pattern B (BYO): the tenant is the controller (they choose which TikTok handles to track), you are the processor. A DPA between you and tenant is mandatory; TikLiveAPI is your sub-processor. List it in the sub-processor schedule.
  • Pattern A (shared key): you are the controller for the upstream relationship and a processor for the tenant. Heavier compliance, since you now decide retention upstream.
  • Both patterns: public TikTok profile data is still personal data. Honor erasure requests by deleting tiktok_user_snapshots rows for the data subject, and document that the upstream TikLiveAPI service is stateless on your behalf.

FAQ

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.

Build with the TikTok API

Ready to put what you read into code? Try our endpoints live or grab the full reference.

Open Playground Read Documentation