Creator API

Read your saved TikTok creator pool, start discovery scans and download results over HTTP — the same things the Discord bot does, for scripts, spreadsheets and other tools.

Base URL: http://YOUR_SERVER:8787  ·  All responses are JSON unless you ask for CSV or text.

Quickstart

  1. In Discord, run /apikey create name:my-script. The bot replies privately with a key that starts with tts_. Copy it — it is shown only once.
  2. Check it works:
    curl -H "Authorization: Bearer tts_YOUR_KEY" http://YOUR_SERVER:8787/v1/me
  3. Get the 10 biggest US creators in your pool:
    curl -H "Authorization: Bearer tts_YOUR_KEY" \
      "http://YOUR_SERVER:8787/v1/creators?country=US&limit=10"
  4. Start a scan, then check on it with the id it returns:
    curl -X POST -H "Authorization: Bearer tts_YOUR_KEY" -H "Content-Type: application/json" \
      -d '{"country":"US","count":20,"categories":["gaming:fortnite"]}' \
      http://YOUR_SERVER:8787/v1/jobs

API keys

Keys are managed entirely from Discord. Only whitelisted users can make them.

CommandWhat it does
/apikey create name:<label>Makes a new key and shows it to you once (only you can see the reply). Up to 5 active keys per user.
/apikey listShows your active keys: ID, label, a short hint of the key, when it was made and last used. The super admin sees everyone's keys.
/apikey revoke key_id:<id>Stops a key immediately. Use key_id:all to stop every key you own. The super admin can revoke any key by ID.
A key acts as you. Scans started with it count against your daily data allowance and show up in /history and /jobs. If you are removed from the whitelist, all your keys stop working at once (they return 403).

The bot stores only a fingerprint (SHA-256 hash) of each key, so a lost key can't be recovered — revoke it and make a new one.

Authentication

Send the key on every request to a /v1 route (except /v1/health), in either header:

Authorization: Bearer tts_YOUR_KEY
X-API-Key: tts_YOUR_KEY

Never put the key in the URL. Keep it out of shared code and public repos.

Errors & limits

Every error has the same shape:

{ "error": { "code": "invalid_key", "message": "This API key is wrong or has been revoked." } }
StatusCodeMeaning
400invalid_parameter, invalid_bodyA query value or JSON field is wrong. The message says which.
401missing_key, invalid_keyNo key sent, or the key is wrong or revoked.
403not_whitelisted, forbiddenThe key's owner lost access, or you tried to act on someone else's job.
404not_foundNo such creator, job or route. Other users' jobs also return 404.
409queue_unavailable, not_activeThe scan queue is full, you already have a queued job, or the job you tried to stop has already finished.
429rate_limitedMore than 120 requests per minute on one key. Wait for the number of seconds in the Retry-After header.
500server_errorSomething broke on the server. Try again; if it keeps happening, check the bot logs.

Other limits are the same as in Discord: one scan runs at a time (others wait in a queue of up to 10), each user can have one queued scan, a scan has a 30-minute and per-scan data budget unless unlimited_passes is on, and each user has a daily data allowance that resets at Sydney midnight.

Account

GET/v1/health

Is the API up? No key needed.

{ "ok": true, "version": "1.0" }

GET/v1/me

The key you're using and whether its owner is the super admin.

{
  "key": { "id": "3f9a1c2b", "owner_id": "1444906668923027466", "name": "my-script",
           "hint": "tts_Ab3d…x9Qz", "created_at": "2026-11-05T09:12:44+00:00",
           "last_used_at": "2026-11-05T09:13:02+00:00", "revoked_at": null, "revoked_by": null },
  "is_admin": false
}

GET/v1/usage

Your scan data used today and what's left. All sizes are bytes.

{
  "usage": { "owner_id": "1444…", "day": "2026-11-05", "browser_bytes": 81234567,
             "country_bytes": 1203344, "total_bytes": 82437911 },
  "daily_limit_bytes": 5000000000,
  "scan_limit_bytes": 2500000000,
  "remaining_today_bytes": 4917562089
}

GET/v1/stats

Pool totals, the scan queue and whether the TikTok token is ready (the same numbers as /status).

{
  "pool": { "total": 10839, "eligible": 2210, "live_now": 14, "seen_live_7d": 640, "us": 3021,
            "by_status": { "new": 10500, "contacted": 120 }, "country_known": 5400,
            "country_unknown": 5439, "consistent_live": 310 },
  "queue": { "running": "a1b2c3d4e5f6", "waiting": 1 },
  "tiktok_token": { "present": true, "age_hours": 3.2 }
}

GET/v1/reference

Valid values for categories, leagues and sort.

{
  "categories": [ { "key": "gaming:fortnite", "label": "Fortnite" }, … ],
  "leagues": [ "D5", "D4", "D3", "D2", "D1", "C5", … , "A2", "A1" ],
  "sorts": [ "followers_desc", "followers_asc", "engagement_desc", … ]
}

Creators

The creator pool is shared — it's the same pool /pool shows.

GET/v1/creators

Search and page through saved creators. All filters are optional and combine with AND.

QueryTypeDescription
searchtextMatches username, display name or bio.
countryISO codeVerified account country, e.g. US.
post_countryISO codeWhere recent posts are mostly from (a hint, not the verified country).
min_followers, max_followersnumberFollower range.
live_onlytrue/falseOnly creators confirmed live in the last 15 minutes.
leaguetierOne league tier, e.g. C3.
statustextRecruitment state, e.g. new, shortlisted, contacted, do_not_contact.
nichetextMatches part of the niche label.
sorttextDefault followers_desc. See /v1/reference.
limit1–500Page size, default 50.
offsetnumberHow many to skip, default 0.
GET /v1/creators?country=US&min_followers=1000&live_only=true&limit=2

{
  "total": 14, "limit": 2, "offset": 0,
  "items": [ { "username": "examplecreator", "followers": 48200, "region": "US",
               "verified_live": true, "live_league": "C2", … }, … ]
}

To get everything, keep adding limit to offset until offset >= total, or use export.

GET/v1/creators/{username}

One creator plus their observed LIVE sessions (newest first, up to 20). The @ is optional and case doesn't matter.

{
  "creator": { "username": "examplecreator", … },
  "live_sessions": [ { "room_id": "7311…", "first_seen_at": "…", "last_seen_at": "…", "observed_days": 2 } ]
}

GET/v1/creators/export

Every creator matching the same filters as /v1/creators, in one download.

QueryDescription
formatcsv (default, opens in Excel/Sheets), txt (one username per line) or json.
curl -H "Authorization: Bearer tts_YOUR_KEY" -o us.csv \
  "http://YOUR_SERVER:8787/v1/creators/export?country=US&format=csv"

Scans

A scan is a job. Starting one puts it in the same queue as Discord scans; it runs in the background and saves results to the pool. Poll the job to see progress and fetch results when it's done.

Job state goes: queued → running → one of completed, stopped, failed, cancelled (stopped before it started) or interrupted (bot restarted — resume it with /resume in Discord). stopping means a stop was requested and it's finishing the current step.

POST/v1/jobs

Start a scan. Send a JSON body. Every field is optional. Returns 202 with the new job.

kind: "generate" (default) — same as /generate

FieldTypeDefaultDescription
countryISO code"US"Country the creators stream from. "" or "any" for any.
count1–100030Target number of qualifying creators. You may get fewer if country or activity proof is missing.
categorieslistallCategory keys, e.g. ["gaming:fortnite","lifestyle:chatting"]. See /v1/reference.
keywordslist[]Extra search keywords.
leagueslist[]Only keep these league tiers, e.g. ["C1","B5"].
min_followersnumber0Minimum followers.
max_followersnumbernoneMaximum followers.
fastbooltrueShort profile visits; league lookup only when filtering leagues.
post_countryISO codenoneKeep only creators whose recent posts are mostly from this country.
include_unknown_countryboolfalseAlso keep creators whose country couldn't be verified.
include_existingboolfalseInclude creators you've already been given before.
min_live_sessions0–10000Minimum distinct LIVE sessions seen (0 = off).
min_live_days0–window0Minimum distinct days seen live (0 = off).
activity_window_days1–9014How far back the two LIVE checks above look.
unlimited_passesboolfalseKeep scanning until the target is reached or you stop it. No time limit.

kind: "autoscan" — same as /autoscan

Scans every category and ranks the most recruitable creators. Fields: country (default "US"), count (1–100, default 100), include_existing, unlimited_passes. Results carry _score, _grade and _why.

POST /v1/jobs
Content-Type: application/json

{ "country": "US", "count": 25, "categories": ["gaming:fortnite", "gaming:roblox"],
  "min_followers": 500, "max_followers": 50000 }

→ 202
{ "id": "a1b2c3d4e5f6", "kind": "generate", "state": "queued", "source": "api", "target": 25, … }

A 409 queue_unavailable means the queue is full or you already have a scan waiting.

GET/v1/jobs

Your recent jobs, newest first — including ones started in Discord. Results aren't included here; use results.

QueryDescription
limit1–100, default 20.
alltrue shows every user's jobs (super admin only; ignored for others).
{ "items": [ { "id": "a1b2c3d4e5f6", "kind": "generate", "state": "running", … } ] }

GET/v1/jobs/{id}

One job with live progress while it runs. Add ?include_creators=true to include the result rows.

{
  "id": "a1b2c3d4e5f6", "kind": "generate", "state": "running", "source": "api",
  "target": 25, "created_at": "2026-11-05T09:20:01+00:00",
  "progress": { "phase": "Checking profiles", "currently_qualified": 7, … },
  "remaining_seconds": 1420.5,
  "data": { "used_bytes": 41022331, "limit_bytes": 2500000000, "limit_source": "scan" },
  "checkpoint": { "pass": 2, "dropped": { "region": 31, "min_followers": 12 } },
  "result_count": 7
}

GET/v1/jobs/{id}/results

The creators a job found. While it runs you get what's qualified so far; once it's finished you get the final list.

QueryDescription
formatjson (default), csv or txt.
{ "state": "completed", "count": 25, "items": [ { "username": "examplecreator", … } ] }

POST/v1/jobs/{id}/stop

Stop a queued or running job. Work already done is kept. A running job moves to stopping, then stopped; a queued one becomes cancelled. No body needed.

curl -X POST -H "Authorization: Bearer tts_YOUR_KEY" \
  http://YOUR_SERVER:8787/v1/jobs/a1b2c3d4e5f6/stop

Objects

Creator

Fields you'll use most. Any number field can be null when it was never observed.

FieldDescription
usernameTikTok handle, lowercase, no @. Profile: https://www.tiktok.com/@username.
display_name, bio, avatarProfile details.
followers, following, likes, video_countProfile counts.
region, country_source, country_checked_atAccount country and how/when it was confirmed.
post_countryWhere recent posts mostly come from (a hint).
languageDetected language code.
verified_live, verified_live_atConfirmed live within the last 15 minutes, and when.
last_live_at, last_post_atLast time seen live / last post.
live_league, live_league_rankTikTok LIVE league tier (D5 lowest … A1 highest) and its rank 1–18.
avg_views_10, engagement_rateAverage views of recent videos and engagement.
status, notesRecruitment state and notes set in Discord.
created_at, updated_atWhen the row was added / last changed (UTC, ISO 8601).

Job

FieldDescription
id12-character job ID. Works with /run, /jobs and /resume in Discord too.
kindgenerate, autoscan, or a maintenance job from Discord (e.g. proxies).
stateSee Scans.
sourceapi or discord.
target, settingsWhat the scan was asked for.
reason, errorWhy it stopped or failed, in plain words.
progressLive phase and counts while running.
dataBytes used and the limit that applies (scan or daily).
checkpointSaved pass count, drop reasons and totals.
result_countHow many creators the job has right now.

Full examples

Start a scan, wait for it, and save the results as CSV.

import time, requests

BASE = "http://YOUR_SERVER:8787"
H = {"Authorization": "Bearer tts_YOUR_KEY"}

job = requests.post(f"{BASE}/v1/jobs", headers=H,
                    json={"country": "US", "count": 20}).json()
print("started", job["id"])

while True:
    job = requests.get(f"{BASE}/v1/jobs/{job['id']}", headers=H).json()
    print(job["state"], job["result_count"], "found")
    if job["state"] not in ("queued", "running", "stopping"):
        break
    time.sleep(15)

csv = requests.get(f"{BASE}/v1/jobs/{job['id']}/results",
                   headers=H, params={"format": "csv"})
open("results.csv", "wb").write(csv.content)

Server setup

The API starts automatically with the bot. Configure it with environment variables (on the server: deploy/tiktokbot.env):

VariableDefaultDescription
TIKTOK_API_PORT8787Port to listen on. 0 turns the API off.
TIKTOK_API_HOST0.0.0.0Address to listen on. Use 127.0.0.1 when a reverse proxy sits in front.

The bot log prints [api] listening on … at startup. Docs are served at /docs (and /).

Use HTTPS on the open internet. The API itself speaks plain HTTP, so keys can be read by anyone between you and the server. For use outside your own network, set TIKTOK_API_HOST=127.0.0.1 and put Apache or nginx with a TLS certificate in front, for example with Apache:
ProxyPass        /  http://127.0.0.1:8787/
ProxyPassReverse /  http://127.0.0.1:8787/
If you keep it on 0.0.0.0, open the port in the firewall (e.g. ufw allow 8787/tcp).

Keys live in data/api_keys.json (hashes only). Back it up with the rest of data/.