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. Cached responses are free; uncached creators are scored on demand for one credit ($0.75) each.
GET /api/v1/creators/{platform}/{handle} with your key. Cached scores are free; uncached creators cost 1–3 credits each.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
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.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_..."| Platform | Credits per fresh score |
|---|---|
| TikTok, Instagram, Twitter/X, Threads, Reddit, LinkedIn, Facebook, Snapchat | 1 |
| Twitch, Kick | 2 |
| YouTube | 3 (long-form video transcription) |
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": {
"fresh_score_credits": 1,
"cached_score_credits": 0,
"cache_freshness_days": 30
}
}/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.
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/..."
}
}{
"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; 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).
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": { ... } },
{ "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
}
}/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_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. |
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.