// API_REFERENCEBUILD · 2.5.β

// 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/analyze call (Quick or Deep)
  • analyze.completed.deep — only Deep Bench completions, useful when you want a quieter feed
  • batch.completed — fires once per /api/analyze/batch run, with the full item roster
  • digest.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 as digest.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/analyze request from this team; data includes { 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/webhooks for the last 50 attempts + status codes.
  • A webhook that returns non-2xx 10 times in a row is auto-disabled. The team owner sees theDISABLED badge 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.