For developers

CreatorScore API

Submit any social handle. Get back the same 1–100 trust score that brands use to vet creators on creatorscore.io — content risk, brand safety, sentiment, authenticity, audience quality, community trust, ROI prediction. Cached responses are free; uncached creators are scored on demand for one credit ($0.75) each.

Quick start

  1. Create a developer account — takes 30 seconds, you'll get an API key immediately.
  2. Buy a credit pack from your dashboard. Starter is $75 for 100 credits.
  3. Hit GET /api/v1/creators/{platform}/{handle} with your key. Cached scores are free; uncached creators cost 1–3 credits each.

Build with AI

The fastest way to integrate this API is to paste the system prompt below into Claude, ChatGPT, Cursor, or any AI assistant — it has the full API surface area, request/response shapes, and error codes baked in. You'll get working code in under a minute.

System prompt — copy this verbatim into your AI assistant

You are helping a developer integrate the CreatorScore API. The API enriches influencer rosters with a 1-100 trust score across 7 AI agents.

# Base URL
https://creatorscore.io

# Authentication
Every request requires an HTTP header: X-API-Key: <key>

# Pricing
Cached scores (≤30 days old): free.
Fresh scores cost credits per platform:
- TikTok, Instagram, Twitter/X, Threads, Reddit, LinkedIn, Facebook, Snapchat: 1 credit
- Twitch, Kick: 2 credits
- YouTube: 3 credits
Customers buy credit packs ($75 = 100 credits, $300 = 500, $1050 = 2000).

# Endpoints

## GET /api/v1/account
Returns the authenticated tenant info + credit balance + pricing table.
Response 200: { tenant: {id, name}, api_key: {id, label, created_at, last_used_at}, credits: {balance: number}, pricing: {per_platform_credits: {...}, cached_score_credits: 0, cache_freshness_days: 30} }

## GET /api/v1/creators/{platform}/{handle}
Cache-or-queue lookup of one creator.
Path params: platform (tiktok | instagram | youtube | twitter | x | facebook | reddit | twitch | kick | linkedin | threads | snapchat), handle (URL-encoded social handle, no @).

Response 200 (cache hit):
{
  "status": "cached",
  "creator": {
    "platform": "tiktok", "handle": "alixearle",
    "display_name": "Alix Earle", "followers": 8400000,
    "score": 84, "tier": "Excellent",
    "agents": {
      "content_risk": 81.0, "brand_safety": 82.1, "sentiment": 85.0,
      "authenticity": 98.8, "audience_quality": 84.4,
      "community_trust": 83.9, "roi_prediction": 58.6
    },
    "knockouts": [],
    "last_scored_at": "2026-04-15T05:30:39Z",
    "public_profile_url": "https://creatorscore.io/profile/..."
  }
}

Response 202 (queued, fresh score needed):
{ "status": "queued", "job_id": "uuid", "eta_seconds": 1800, "poll_url": "/api/v1/jobs/{job_id}", "deduplicated": false, "credits_charged": 1 }

## POST /api/v1/creators/bulk
Submit up to 100 (platform, handle) pairs at once.
Body: { "creators": [{ "platform": "...", "handle": "..." }, ...] }
Response 200: { "results": [...], "summary": { "total", "cached", "queued", "errored", "credits_spent", "credits_remaining" } }
Each result has the same shape as the single endpoint (cached/queued/error).

## GET /api/v1/jobs/{job_id}
Poll one job.
Response 200 (done): { "job_id", "status": "done", "progress": 100, "completed_at", "creator": {...same as cached payload...} }
Response 202 (in flight): { "job_id", "status": "queued"|"processing", "progress", "progress_message", "eta_seconds", "created_at" }
Response 200 (failed): { "job_id", "status": "failed", "progress", "error", "failed_at" }
Response 404: { "job_id", "status": "not_found", "message" }

## POST /api/v1/jobs/bulk-status
Poll up to 100 jobs at once.
Body: { "job_ids": ["uuid1", "uuid2", ...] }
Response 200: { "jobs": [...], "summary": { "total", "done", "in_flight", "failed", "not_found" } }

# Polling guidance
Fresh scores take 10–90 minutes (mostly Whisper transcription on YouTube). Poll every 60 seconds for the first 30 minutes, then every 5 minutes after. Use the eta_seconds field on every queued/processing response as the canonical guide.

# Errors
All errors return { "error": "code", "message": "..." }.
Codes: missing_api_key (401), invalid_api_key (401), insufficient_credits (402), unsupported_platform (400), invalid_handle (400), invalid_request (400), queue_failed (500), auth_unavailable (503).

# Constraints
- Bulk endpoints accept max 100 items.
- Duplicate handles in the same batch are charged once (deduped).
- Jobs are scoped to the calling tenant — cross-tenant polling returns not_found.
- Rate limits aren't hard-enforced in v1; the credit balance is the natural ceiling.

When generating code: always send the API key from an environment variable, never hardcoded. Surface insufficient_credits errors clearly so customers know to top up.

Sample user prompts

  • "Write a Python script that enriches my CSV of TikTok handles with their CreatorScore. The CSV has one column called ‘handle’."
  • "Build a TypeScript function I can use in my Next.js app to display a creator's score and 7-agent breakdown."
  • "Show me how to bulk-score 50 creators, then poll their job IDs until they all complete, with backoff between polls."
  • "Generate a React component that takes an array of (platform, handle) pairs and renders each creator's tier with a colored badge."

Authentication

Every request requires an X-API-Key header. Keys are tenant-scoped — keep them secret and rotate if compromised. Create or revoke keys at /account/api.

curl https://creatorscore.io/api/v1/account \
  -H "X-API-Key: sk_live_..."

Pricing

  • Cached scores (≤30 days old): free
  • Fresh scores: cost varies by platform (1–3 credits each)
  • No monthly fees, no commitments — prepaid credits only
  • Refunds are automatic if the scoring pipeline fails

Credit packs

Starter
$75
100 credits
$0.75 / credit
Pro — 20% off
$300
500 credits
$0.60 / credit
Scale — 30% off
$1,050
2,000 credits
$0.53 / credit

Fresh-score credits per platform

PlatformCredits per fresh score
TikTok, Instagram, Twitter/X, Threads, Reddit, LinkedIn, Facebook, Snapchat1
Twitch, Kick2
YouTube3 (long-form video transcription)

Endpoints

All endpoints are versioned under /api/v1.

GET/api/v1/account

Check your API key + credit balance

Use this to confirm a key works and view the current balance.

Request

curl https://creatorscore.io/api/v1/account \
  -H "X-API-Key: sk_live_..."

Response

{
  "tenant": { "id": "...", "name": "ACME Influence Co" },
  "api_key": {
    "id": "...",
    "label": "Score API key",
    "created_at": "2026-05-14T...",
    "last_used_at": "2026-05-15T..."
  },
  "credits": { "balance": 142 },
  "pricing": {
    "fresh_score_credits": 1,
    "cached_score_credits": 0,
    "cache_freshness_days": 30
  }
}
GET/api/v1/creators/{platform}/{handle}

Look up one creator (cache or queue)

Returns the cached score immediately if one exists and was calculated in the last 30 days. Otherwise queues a fresh score, deducts 1 credit, and returns a job_id you can poll.

Supported platforms: tiktok, instagram, youtube, twitter, x, facebook, reddit, twitch, kick, linkedin, threads, snapchat.

Request

curl https://creatorscore.io/api/v1/creators/tiktok/alixearle \
  -H "X-API-Key: sk_live_..."

Response — cached (200)

{
  "status": "cached",
  "creator": {
    "platform": "tiktok",
    "handle": "alixearle",
    "display_name": "Alix Earle",
    "followers": 8400000,
    "score": 84,
    "tier": "Excellent",
    "agents": {
      "content_risk": 81.0,
      "brand_safety": 82.1,
      "sentiment": 85.0,
      "authenticity": 98.8,
      "audience_quality": 84.4,
      "community_trust": 83.9,
      "roi_prediction": 58.6
    },
    "knockouts": [],
    "last_scored_at": "2026-04-15T05:30:39Z",
    "public_profile_url": "https://creatorscore.io/profile/..."
  }
}

Response — queued (202)

{
  "status": "queued",
  "job_id": "fa9b2c1e-...-...",
  "eta_seconds": 1800,
  "poll_url": "/api/v1/jobs/fa9b2c1e-...",
  "deduplicated": false
}

Optional ?include= param

Append a comma-separated list of extra fields to add to the response. No effect on credits or pricing — just larger payloads.

  • narrative — natural-language summary of the score
  • velocity — 30/60/90-day score deltas (positive = score going up)
  • risk_flags — up to 10 unresolved critical/high/medium flags
  • detailed_analysis — raw per-agent dimension breakdown
curl https://creatorscore.io/api/v1/creators/tiktok/alixearle?include=narrative,velocity \
  -H "X-API-Key: sk_live_..."
POST/api/v1/creators/bulk

Submit up to 100 creators at once

Main endpoint for roster enrichment. Cached results return inline; uncached creators get queued. Atomic credit accounting — if your balance runs out mid-batch, the remaining uncached items return status: "error" with error: "insufficient_credits". Duplicate handles in the same batch are deduplicated (charged once).

Request

curl -X POST https://creatorscore.io/api/v1/creators/bulk \
  -H "X-API-Key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "creators": [
      { "platform": "tiktok", "handle": "alixearle" },
      { "platform": "instagram", "handle": "alixearle" },
      { "platform": "youtube", "handle": "MrBeast" }
    ]
  }'

Response (200)

{
  "results": [
    { "platform": "tiktok",    "handle": "alixearle", "status": "cached", "creator": { ... } },
    { "platform": "instagram", "handle": "alixearle", "status": "cached", "creator": { ... } },
    { "platform": "youtube",   "handle": "MrBeast",
      "status": "queued", "job_id": "...", "eta_seconds": 1800, "deduplicated": false }
  ],
  "summary": {
    "total": 3,
    "cached": 2,
    "queued": 1,
    "errored": 0,
    "credits_spent": 1,
    "credits_remaining": 141
  }
}
GET/api/v1/jobs/{job_id}

Poll one queued job

Returns queued/processing while the job is in flight, done with the full creator payload when it completes, or failed if scoring couldn't complete. Failed jobs auto-refund the credit. HTTP codes: 200 for done/failed, 202 for in-flight, 404 for not-found.

Request

curl https://creatorscore.io/api/v1/jobs/fa9b2c1e-... \
  -H "X-API-Key: sk_live_..."

Response — done (200)

{
  "job_id": "fa9b2c1e-...",
  "status": "done",
  "progress": 100,
  "completed_at": "2026-05-15T03:42:11Z",
  "creator": { ...same shape as the cached response... }
}

Response — processing (202)

{
  "job_id": "fa9b2c1e-...",
  "status": "processing",
  "progress": 42,
  "progress_message": "Phase 2: enriching posts (TikTok)",
  "eta_seconds": 1200,
  "created_at": "2026-05-15T03:12:11Z"
}
POST/api/v1/jobs/bulk-status

Poll many jobs in one request

Same shape as the single-job endpoint, but accepts an array of up to 100 job_ids. Always returns 200 — per-job statuses are nested.

Request

curl -X POST https://creatorscore.io/api/v1/jobs/bulk-status \
  -H "X-API-Key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "job_ids": ["fa9b...", "ab12...", "..."] }'

Response (200)

{
  "jobs": [
    { "job_id": "fa9b...", "status": "done", "creator": { ... } },
    { "job_id": "ab12...", "status": "processing", "progress": 45, "eta_seconds": 1200 },
    { "job_id": "cd34...", "status": "not_found" }
  ],
  "summary": { "total": 3, "done": 1, "in_flight": 1, "failed": 0, "not_found": 1 }
}

Code samples

Reference implementations for the typical workflow: bulk-submit a roster, poll for completion, collect scores. Both samples handle insufficient_credits and failed jobs cleanly. Paste either into your AI assistant with the system prompt above to extend.

Python

import os
import time
import requests

API_KEY = os.environ["CREATORSCORE_API_KEY"]
BASE = "https://creatorscore.io/api/v1"
HEADERS = {"X-API-Key": API_KEY, "Content-Type": "application/json"}

def enrich_roster(creators: list[dict]) -> list[dict]:
    """
    creators: [{"platform": "tiktok", "handle": "alixearle"}, ...]
    Returns a list of score dicts (or {"error": "..."} for failures).
    """
    # 1. Bulk-submit
    r = requests.post(f"{BASE}/creators/bulk", json={"creators": creators}, headers=HEADERS)
    r.raise_for_status()
    data = r.json()

    results = list(data["results"])
    pending_jobs = {row["job_id"]: idx for idx, row in enumerate(results) if row["status"] == "queued"}

    if not pending_jobs:
        return [row.get("creator") or {"error": row.get("error")} for row in results]

    print(f"Cached: {data['summary']['cached']}  Queued: {data['summary']['queued']}  Spent: {data['summary']['credits_spent']} credits")

    # 2. Poll until all queued jobs finish (or fail). Backoff: 30s -> 60s -> 5m.
    delay = 30
    while pending_jobs:
        time.sleep(delay)
        r = requests.post(
            f"{BASE}/jobs/bulk-status",
            json={"job_ids": list(pending_jobs.keys())},
            headers=HEADERS,
        )
        r.raise_for_status()
        for job in r.json()["jobs"]:
            if job["status"] == "done":
                idx = pending_jobs.pop(job["job_id"])
                results[idx] = {"status": "done", "creator": job["creator"]}
            elif job["status"] in ("failed", "not_found"):
                idx = pending_jobs.pop(job["job_id"])
                results[idx] = {"status": job["status"], "error": job.get("error", "unknown")}
        delay = min(delay * 2, 300)  # 30 -> 60 -> 120 -> 240 -> 300 cap

    return [row.get("creator") or {"error": row.get("error", "unknown")} for row in results]


if __name__ == "__main__":
    roster = [
        {"platform": "tiktok",    "handle": "alixearle"},
        {"platform": "instagram", "handle": "MrBeast"},
        {"platform": "youtube",   "handle": "MrBeast"},
    ]
    for creator in enrich_roster(roster):
        print(creator)

TypeScript / Node.js

const API_KEY = process.env.CREATORSCORE_API_KEY!
const BASE = "https://creatorscore.io/api/v1"
const HEADERS = { "X-API-Key": API_KEY, "Content-Type": "application/json" }

type Creator = { platform: string; handle: string }

export async function enrichRoster(creators: Creator[]) {
  // 1. Bulk-submit
  const submitRes = await fetch(`${BASE}/creators/bulk`, {
    method: "POST",
    headers: HEADERS,
    body: JSON.stringify({ creators }),
  })
  if (!submitRes.ok) throw new Error(`Bulk submit failed: ${submitRes.status}`)
  const submit = await submitRes.json()

  const results = [...submit.results]
  const pending = new Map<string, number>()
  results.forEach((r: any, i: number) => {
    if (r.status === "queued") pending.set(r.job_id, i)
  })

  if (pending.size === 0) return results

  console.log(`Cached: ${submit.summary.cached}, Queued: ${submit.summary.queued}, Spent: ${submit.summary.credits_spent}cr`)

  // 2. Poll with exponential backoff (30s -> 60 -> 120 -> 240 -> 300 cap)
  let delay = 30_000
  while (pending.size > 0) {
    await new Promise((r) => setTimeout(r, delay))
    const pollRes = await fetch(`${BASE}/jobs/bulk-status`, {
      method: "POST",
      headers: HEADERS,
      body: JSON.stringify({ job_ids: Array.from(pending.keys()) }),
    })
    const poll = await pollRes.json()
    for (const job of poll.jobs as any[]) {
      if (job.status === "done") {
        const idx = pending.get(job.job_id)!
        results[idx] = { status: "done", creator: job.creator }
        pending.delete(job.job_id)
      } else if (job.status === "failed" || job.status === "not_found") {
        const idx = pending.get(job.job_id)!
        results[idx] = { status: job.status, error: job.error ?? "unknown" }
        pending.delete(job.job_id)
      }
    }
    delay = Math.min(delay * 2, 300_000)
  }

  return results
}

// Example usage
enrichRoster([
  { platform: "tiktok",    handle: "alixearle" },
  { platform: "instagram", handle: "MrBeast" },
  { platform: "youtube",   handle: "MrBeast" },
]).then((rows) => rows.forEach((r) => console.log(r)))

Polling cadence

Most fresh scores complete in 10–90 minutes (depends on platform count + video-heavy posts that need transcription). We recommend polling every 60 seconds for the first 30 minutes, then every 5 minutes after that. Don't hammer — the eta_seconds field on every queued/processing response is your guide.

Errors

All errors return a consistent JSON shape:

{ "error": "error_code", "message": "Human-readable description." }
CodeHTTPMeaning
missing_api_key401No X-API-Key header sent.
invalid_api_key401Key not found or revoked.
insufficient_credits402Balance is 0. Top up to continue.
unsupported_platform400Platform name not in the supported list.
invalid_handle400Handle is empty or too long.
invalid_request400Body didn't match the expected schema.
queue_failed500Couldn't enqueue the scoring job. Retry; if it persists, contact us.
rate_limit_exceeded429120 req/min per key. Retry-After header tells you when to retry.
auth_unavailable503Auth backend is temporarily unavailable. Retry.

Webhooks (avoid polling)

For job completion you have two options: poll /api/v1/jobs/{id} or configure a webhook on your API key. With a webhook configured, we POST a score.completed event the moment your job finishes, including the full creator payload — same shape as the GET response.

Set the URL at /account/api. The URL must be HTTPS. When you set or update it we generate a fresh signing secret and show it to you exactly once.

Delivery

We POST a JSON body with these headers:

X-CreatorScore-Event: score.completed       # or score.failed
X-CreatorScore-Signature: <hex HMAC-SHA256>
X-CreatorScore-Delivery: <delivery uuid>
X-CreatorScore-Attempt: 1                   # 1..5
Content-Type: application/json

Body

{
  "event": "score.completed",
  "job_id": "fa9b2c1e-...",
  "platform": "tiktok",
  "handle": "alixearle",
  "creator": { ...same shape as the GET /api/v1/jobs/{id} 'done' creator field... },
  "delivered_at": "2026-05-25T03:42:11Z"
}

Verifying the signature

Compute HMAC-SHA256 over the raw request body using your signing secret. Compare with the X-CreatorScore-Signature header. Reject anything that doesn't match.

// Node.js (Next.js route example)
import { createHmac, timingSafeEqual } from "crypto"

export async function POST(req: Request) {
  const raw = await req.text()
  const sig = req.headers.get("x-creatorscore-signature") || ""
  const expected = createHmac("sha256", process.env.CREATORSCORE_WEBHOOK_SECRET!)
    .update(raw)
    .digest("hex")

  // Constant-time comparison; both must be the same length first.
  if (
    sig.length !== expected.length ||
    !timingSafeEqual(Buffer.from(sig), Buffer.from(expected))
  ) {
    return new Response("bad signature", { status: 401 })
  }

  const event = JSON.parse(raw)
  // ... store event.creator, update your roster, etc.
  return new Response("ok")
}

Retries

We treat any non-2xx response (or connection failure / timeout >10s) as a retryable failure. Backoff is 30s → 1m → 5m → 30m → 2h, with up to 5 attempts. After that the delivery is marked failed and surfaced in your dashboard. Make your handler idempotent — duplicate deliveries are rare but possible.

Rate limits

Each API key is rate-limited to 120 requests per minute (rolling 60-second window). Exceeding the limit returns 429 rate_limit_exceeded with a Retry-After header in seconds. The credit balance remains your real ceiling for fresh-score work — the rate limit is just there to prevent runaway loops.

OpenAPI spec + SDKs

The full machine-readable spec lives at /api/v1/openapi.json (OpenAPI 3.1). Use it to:

  • Import the API into Postman / Insomnia in one click
  • Generate a typed SDK in any language with openapi-generator or openapi-typescript
  • Render interactive docs in Swagger UI / Redocly
# Generate a TypeScript client from the spec
npx openapi-typescript https://creatorscore.io/api/v1/openapi.json -o creatorscore-api.d.ts

Versioning + changes

This is v1. We'll add fields without bumping the version; we won't remove or rename fields without bumping to v2 and maintaining v1 for at least 6 months. Subscribe to release notes by replying to your onboarding email.

Questions

Email [email protected]. We respond same-day.

CreatorScore API — Score any creator on demand | CreatorScore