// API_REFERENCE · AGENCY_TIER
TokBench API
Programmatic access to the same analyzer that powers the tokbench.ai UI. Authenticate with a Bearer key, POST a creative input, get back the structured report. Available on the Agency plan.
// AUTHENTICATION
Bearer keys
Generate a key at /account/api-keys. Keys are shown once at creation; store them in your secret manager (1Password, Bitwarden, GitHub Actions secrets, etc.). Pass the key in the Authorization header on every request:
Authorization: Bearer sk_tok_<your_key>Each key is rate-limited independently (so a runaway CI loop can’t starve your interactive scripts). The default Agency quota is 1000 requests/day per key; email hello@tokbench.ai if you need more.
// ENDPOINT
POST /api/analyze
The analyzer accepts four input formats. Pick one and POST it with mode set to quick (~10s, scorecard only) or deep (~30-60s, full Deep Bench rubric).
Request body
// All formats share these top-level fields:
{
mode: "quick" | "deep", // default: "deep"
format: "script" | "url" | "image" | "video_frames",
// …format-specific fields below
}
// format: "script" — paste the ad copy
{ format: "script", script: "Your TikTok script…", caption?: string, vertical?: string }
// format: "url" — public TikTok video URL
{ format: "url", url: "https://www.tiktok.com/@handle/video/123…" }
// format: "image" — base64-encoded JPEG/PNG/WebP/GIF
{
format: "image",
imageBase64: "<base64>",
mediaType: "image/jpeg" | "image/png" | "image/gif" | "image/webp",
caption?: string,
}
// format: "video_frames" — frames already on Vercel Blob (3-10)
{
format: "video_frames",
frames: [{ timestamp: 0.0, url: "https://*.public.blob.vercel-storage.com/…" }],
durationSec: 60,
caption?: string,
transcript?: string, // Whisper transcript, ≤20k chars
}Response
// 200 OK
{
mode: "quick" | "deep",
report: { /* scorecard / Deep Bench rubric */ },
preview?: { thumbnailUrl, caption?, author?, frames? },
savedId: string | null, // saved-report ID (always set for API calls)
quota: { limit, used, signedIn: true }
}
// 401 — Bearer header missing or sk_tok_… not recognized
{ error: "Invalid API key." }
// 403 — key's owner is no longer on the Agency plan
{ error: "API access requires an active Agency plan." }
// 429 — daily cap reached for this key
{
error: "Daily analysis limit reached. Try again in NhM.",
retryAfter: <seconds>,
limit: 1000,
visibleLimit: 1000,
signedIn: true
}
// 400 — input shape rejected (zod errors joined with "; ")
{ error: "<message>" }// EXAMPLES
curl
curl -X POST https://tokbench.ai/api/analyze \
-H "Authorization: Bearer sk_tok_<your_key>" \
-H "Content-Type: application/json" \
-d '{
"mode": "deep",
"format": "url",
"url": "https://www.tiktok.com/@handle/video/1234567890"
}'// EXAMPLES
JavaScript / TypeScript
const res = await fetch("https://tokbench.ai/api/analyze", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TOKBENCH_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
mode: "deep",
format: "url",
url: "https://www.tiktok.com/@handle/video/1234567890",
}),
});
if (!res.ok) {
const { error } = await res.json();
throw new Error(`TokBench ${res.status}: ${error}`);
}
const { report, preview, savedId } = await res.json();
console.log(report.hookScore, report.format);// EXAMPLES
Python
import os, requests
res = requests.post(
"https://tokbench.ai/api/analyze",
headers={
"Authorization": f"Bearer {os.environ['TOKBENCH_API_KEY']}",
"Content-Type": "application/json",
},
json={
"mode": "deep",
"format": "url",
"url": "https://www.tiktok.com/@handle/video/1234567890",
},
timeout=120,
)
res.raise_for_status()
data = res.json()
print(data["report"]["hookScore"], data["report"]["format"])// CI / CD
GitHub Actions example
Run TokBench against every TikTok URL in a YAML manifest, fail the workflow on hook scores under a threshold:
# .github/workflows/tokbench-gate.yml
- name: Hook-score gate
env:
TOKBENCH_API_KEY: ${{ secrets.TOKBENCH_API_KEY }}
run: |
set -eo pipefail
for url in $(jq -r '.creatives[].url' creatives.json); do
score=$(curl -fsS -X POST https://tokbench.ai/api/analyze \
-H "Authorization: Bearer $TOKBENCH_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"mode\":\"quick\",\"format\":\"url\",\"url\":\"$url\"}" \
| jq '.report.hookScore')
echo "$url → $score"
[ "$score" -ge 50 ] || { echo "Below threshold"; exit 1; }
done// LIMITS
Quotas + caps
- 1000 requests/day per key on the Agency plan (override per-account via
ANALYZE_LIMIT_AGENCY). - 10 active keys per account. Revoke unused ones to make room.
- Body cap: 4.5 MB (Vercel function limit). Video frames must be uploaded to Vercel Blob first via
/api/blob-upload; only the URLs go in the analyze body. - Function timeout: 300s. Deep Bench typically returns in 30-60s.
// DRAFT_ENDPOINTS
Agency workbench endpoints
All endpoints below are Agency-only and share the same Bearer auth and error envelope as /api/analyze. Default rate limit is 60 requests/day per team (override via DRAFT_LIMIT). All methods are POST with a JSON body.
POST /api/draft/compliance-preflight
Flags FTC/TikTok-Shop/brand-voice violations before a script goes to production.
// Request
{
script: string,
brandKit?: {
voiceDoc?: string,
bannedWords?: string[],
compliancePosture?: "standard" | "strict" | "ftc" | "tiktok-shop",
},
}
// Response
{
report: {
overallRisk: string,
issues: [{ rule: string, severity: string, excerpt: string, fix: string }],
passedChecks: string[],
},
}POST /api/draft/hook-test
Tests multiple hook variants side-by-side and returns them ranked by score.
// Request
{
hooks: string[],
brandKit?: BrandKit,
vertical?: string,
}
// Response
{
report: {
ranked: [{ hook: string, score: number, pattern: string, patternFit: string, notes: string }],
},
}POST /api/draft/voice-match
Compares a script to a brand voice doc and surfaces alignment gaps with suggested rewrites.
// Request
{
script: string,
brandKit: { voiceDoc: string },
}
// Response
{
report: {
score: number,
alignment: string,
gaps: string[],
suggestions: string[],
},
}POST /api/draft/compare-concepts
Scores and ranks creative concepts against each other; also accessible as /api/compare-concepts.
// Request
{
concepts: [{ title: string, brief: string }],
vertical?: string,
}
// Response
{
report: {
ranked: [{ title: string, score: number, strengths: string[], weaknesses: string[] }],
},
}POST /api/draft/thumbnail-prompts
Generates AI image prompts optimised for TikTok cover frames.
// Request
{
context: string,
style?: string,
count?: number,
}
// Response
{
report: {
prompts: [{ prompt: string, rationale: string }],
},
}POST /api/draft/creator-scorecard
Scores a creator’s recent content for campaign fit based on their handle and a campaign brief.
// Request
{
handle: string,
brief: string,
vertical?: string,
}
// Response
{
report: {
overallFit: number,
fitLabel: string,
categories: [...],
topHooks: string[],
redFlags: string[],
},
}POST /api/draft/swipe-file
Extracts hook patterns and creative signals from a creator’s content for use in your own swipe file.
// Request
{
handle: string,
vertical?: string,
}
// Response
{
report: {
hookPatterns: [...],
creativeSignals: string[],
styleNotes: string[],
topItems: [...],
},
}// WEBHOOKS
Outbound webhooks
TokBench can POST events to URLs you configure on /account/team/webhooks. Up to 5 webhook subscriptions per team, each with its own HMAC-SHA256 signing secret + selectable event types.
Event types
analyze.completed— every successful/api/analyzecall (Quick or Deep)analyze.completed.deep— only Deep Bench completions, useful when you want a quieter feedbatch.completed— fires once per/api/analyze/batchrun, with the full item rosterdigest.weekly— Monday 09:00 UTC, the same payload the email digest is rendered from (so you can ship it to Slack / a dashboard without parsing markdown)digest.daily— daily 09:00 UTC, same payload asdigest.weeklybut covering the previous 24h; only fires when the team’s digest frequency is set to “daily”error.reported— fires when a server-side error occurs on a/api/analyzerequest from this team;dataincludes{ source, message, url }
Request shape
POST <your_url>
Content-Type: application/json
X-TokBench-Event: analyze.completed
X-TokBench-Signature: t=1715000000,v1=<sha256_hmac_hex>
User-Agent: TokBench-Webhook/1
{
"id": "evt_<random>", // unique event ID
"type": "analyze.completed", // matches X-TokBench-Event
"created": 1715000000, // unix seconds; matches the t= in the signature
"teamId": "tm_<random>",
"data": { /* event-specific — see /api/analyze + /api/analyze/batch response shapes */ }
}Verifying the signature
Compute HMAC-SHA256(secret, `${t}.${rawBody}`) and compare to v1. The rawBodymust be the exact bytes you received — don’t re-serialize the parsed JSON or whitespace differences will break the verify.
// Node.js
import crypto from "node:crypto";
function verify(rawBody, signatureHeader, secret) {
const parts = Object.fromEntries(
signatureHeader.split(",").map((p) => p.split("=")),
);
if (!parts.t || !parts.v1) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected, "hex"),
Buffer.from(parts.v1, "hex"),
);
}
// Usage in a Next.js route handler:
export async function POST(req) {
const body = await req.text(); // RAW string
const sig = req.headers.get("x-tokbench-signature") ?? "";
if (!verify(body, sig, process.env.TOKBENCH_WEBHOOK_SECRET)) {
return new Response("bad signature", { status: 400 });
}
const event = JSON.parse(body);
// …handle event
return new Response("ok");
}Delivery + retry
- Single attempt per event (no auto-retry in v1). Look at the delivery log on
/account/team/webhooksfor the last 50 attempts + status codes. - A webhook that returns non-2xx 10 times in a row is auto-disabled. The team owner sees the
DISABLEDbadge on the listing and has to re-enable by deleting + re-creating. - Request timeout is 5 seconds. If your endpoint takes longer than that, queue the work and ack the request synchronously.
- Localhost / private-network URLs are blocked in production to mitigate SSRF.
// SECURITY
Key handling
- Treat keys like passwords. Never check them into version control or paste them in chat.
- Keys are stored as SHA-256 hashes, not raw values — if you lose one you must rotate (revoke + create).
- Plan downgrade auto-disables existing keys (request returns 403). Re-upgrading the account re-enables them.
- Revocation is immediate — the next request returns 401 within a few seconds of clicking
Revoke.