For developers
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. Every creator is a flat 1 credit — one platform or six, cached or scored fresh. Footprint discovery is free.
GET /api/v1/creators/{platform}/{handle} with your key. Every creator is a flat 1 credit — cached or scored fresh, one platform or six.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.
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
1 credit = 1 creator. Scoring or vetting a creator costs a flat 1 credit, no matter how many platforms or accounts they have — one platform or a six-platform footprint, TikTok or YouTube, all the same 1 credit. This is true for both a fresh score (scraped + scored on demand) and a cache hit (a score ≤30 days old, returned immediately). Footprint discovery (finding a creator's other accounts from one seed handle) is FREE — the 1 credit is charged on the vetting result, so a single-handle footprint vet costs exactly 1 credit total. Every response includes credits_charged. If the balance can't cover a lookup, the API returns 402 insufficient_credits and does NOT return the score.
Customers buy credit packs: Starter $399 = 50 creators ($7.98 each), Pro $1,499 = 250 creators ($6.00 each), Scale $4,990 = 1,000 creators ($4.99 each).
# 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: {credits_per_creator: 1, discovery_credits: 0, cache_freshness_days: 30, note: "1 credit per creator, any platform; discovery is free"} }
## 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/..."
},
"credits_charged": 1
}
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_scope (403), insufficient_credits (402), unsupported_platform (400), invalid_handle (400), invalid_request (400), rate_limit_exceeded (429), daily_scrape_limit_exceeded (429), queue_failed (500), auth_unavailable (503).
daily_scrape_limit_exceeded (429) is a per-key safety cap on FRESH scrapes per UTC day; cached lookups are unaffected. Treat like a soft 429 — slow down or ask support to raise the cap.
insufficient_scope (403) means a read-only key tried to queue a fresh score (cache miss). Only full-scope keys can queue. Cached lookups (charged the flat cache fee) + job-status reads work on read-only keys.
# 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 limit: 120 requests/minute per API key. Exceeding it returns 429 rate_limit_exceeded with a Retry-After header (seconds). Back off and retry.
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.Prefer to vet creators without writing code? The CreatorScore MCP server wraps these same endpoints as tools for Claude, ChatGPT, Cursor, and any Model Context Protocol client. Add it with your API key and ask in plain language — get_creator_score, vet_roster (cache-only, so an agent can't fan out into surprise scrapes), score_creator_fresh, and a free calculate_fair_rate. See the MCP setup guide.
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_..."List price is $9.99/creator (matching the dashboard one-off); packs apply a volume discount. The same flat credit covers a fresh scrape-and-score or a cache hit — you pay for the vetted creator, not for how we got there.
All endpoints are versioned under /api/v1.
/api/v1/accountCheck your API key + credit balance
Use this to confirm a key works and view the current balance.
curl https://creatorscore.io/api/v1/account \
-H "X-API-Key: sk_live_..."{
"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": {
"credits_per_creator": 1,
"discovery_credits": 0,
"cache_freshness_days": 30,
"note": "1 credit per creator (whole cross-platform footprint), any platform. Footprint discovery is free."
}
}/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 — charged a flat 1 credit (credits_charged in the response). Otherwise queues a fresh score for the same flat 1 credit and returns a job_id you can poll. If your balance can't cover the cost, you get 402 insufficient_credits and no score.
Supported platforms: tiktok, instagram, youtube, twitter, x, facebook, reddit, twitch, kick, linkedin, threads, snapchat.
curl https://creatorscore.io/api/v1/creators/tiktok/alixearle \
-H "X-API-Key: sk_live_..."{
"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/..."
},
"credits_charged": 1
}{
"status": "queued",
"job_id": "fa9b2c1e-...-...",
"eta_seconds": 1800,
"poll_url": "/api/v1/jobs/fa9b2c1e-...",
"deduplicated": false
}?include= paramAppend 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 scorevelocity — 30/60/90-day score deltas (positive = score going up)risk_flags — up to 10 unresolved critical/high/medium flagsdetailed_analysis — raw per-agent dimension breakdowncurl https://creatorscore.io/api/v1/creators/tiktok/alixearle?include=narrative,velocity \
-H "X-API-Key: sk_live_..."/api/v1/creators/bulkSubmit up to 100 creators at once
Main endpoint for roster enrichment. Cached results return inline (1 credit each); uncached creators get queued (same flat 1 credit each). Atomic credit accounting — if your balance runs out mid-batch, the remaining items (cached or uncached) return status: "error" with error: "insufficient_credits". Duplicate handles in the same batch are deduplicated (charged once).
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" }
]
}'{
"results": [
{ "platform": "tiktok", "handle": "alixearle", "status": "cached", "creator": { ... }, "credits_charged": 1 },
{ "platform": "instagram", "handle": "alixearle", "status": "cached", "creator": { ... }, "credits_charged": 1 },
{ "platform": "youtube", "handle": "MrBeast",
"status": "queued", "job_id": "...", "eta_seconds": 1800, "deduplicated": false, "credits_charged": 1 }
],
"summary": {
"total": 3,
"cached": 2,
"queued": 1,
"errored": 0,
"credits_spent": 3,
"credits_remaining": 139
}
}/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.
curl https://creatorscore.io/api/v1/jobs/fa9b2c1e-... \
-H "X-API-Key: sk_live_..."{
"job_id": "fa9b2c1e-...",
"status": "done",
"progress": 100,
"completed_at": "2026-05-15T03:42:11Z",
"creator": { ...same shape as the cached response... }
}{
"job_id": "fa9b2c1e-...",
"status": "processing",
"progress": 42,
"progress_message": "Phase 2: enriching posts (TikTok)",
"eta_seconds": 1200,
"created_at": "2026-05-15T03:12:11Z"
}/api/v1/jobs/bulk-statusPoll 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.
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...", "..."] }'{
"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 }
}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.
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)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)))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.
All errors return a consistent JSON shape:
{ "error": "error_code", "message": "Human-readable description." }| Code | HTTP | Meaning |
|---|---|---|
missing_api_key | 401 | No X-API-Key header sent. |
invalid_api_key | 401 | Key not found or revoked. |
insufficient_scope | 403 | Read-only key tried to queue a fresh score (cache miss). Use a full-scope key. Cached lookups and job-status reads still work on read-only keys. |
insufficient_credits | 402 | Balance is 0. Top up to continue. |
unsupported_platform | 400 | Platform name not in the supported list. |
invalid_handle | 400 | Handle is empty or too long. |
invalid_request | 400 | Body didn't match the expected schema. |
queue_failed | 500 | Couldn't enqueue the scoring job. Retry; if it persists, contact us. |
rate_limit_exceeded | 429 | 120 req/min per key. Retry-After header tells you when to retry. |
daily_scrape_limit_exceeded | 429 | Per-key daily cap on fresh scrapes hit (a safety backstop). Cached lookups still work; resets at 00:00 UTC. Email us to raise your cap. |
auth_unavailable | 503 | Auth backend is temporarily unavailable. Retry. |
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.
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{
"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"
}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")
}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.
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.
The full machine-readable spec lives at /api/v1/openapi.json (OpenAPI 3.1). Use it to:
openapi-generator or openapi-typescript# Generate a TypeScript client from the spec
npx openapi-typescript https://creatorscore.io/api/v1/openapi.json -o creatorscore-api.d.tsThis 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.
Email [email protected]. We respond same-day.