Could This Be True? API
REST API for forensic AI detection. JSON in, JSON out. Bearer auth. Same heuristics as the web tool, returned as raw metrics so you can plug them into pipelines. Text and image endpoints live today; audio and video API endpoints are next.
Quickstart
One curl away. Generate a key on the dashboard then:
curl -X POST https://couldthisbetrue.com/api/detect/text \
-H "Authorization: Bearer ctbt_<your-key>" \
-H "Content-Type: application/json" \
--data '{"text":"In conclusion, the multifaceted tapestry of artificial intelligence underscores..."}'Response (truncated):
{
"ok": true,
"verdict": "likely-ai",
"combinedScore": 0.72,
"wordCount": 142,
"sentenceCount": 9,
"averageWordsPerSentence": 15.78,
"signals": [
{
"id": "burstiness",
"name": "Burstiness",
"score": 0.86,
"evidence": "Mean 15.8 words/sentence · σ 1.6 · CV 0.10"
},
{
"id": "lexical",
"name": "Lexical tics",
"score": 1.00,
"evidence": "10 filler phrases · 12 tell-words"
}
],
"perplexity": {
"meanPredictability": 0.61,
"predictabilityVariance": 0.04,
"uniformityScore": 0.68
},
"usage": { "today": 12, "limit": 100, "plan": "free" }
}Authentication
Two auth methods accepted on detect endpoints, in this resolution order:
- Bearer token —
Authorization: Bearer ctbt_<…>header. Use for CLI, CI, server-to-server. Get a key from /dashboard. - Browser session cookie — issued by Google OAuth on
/api/auth/google. Used by the web app itself.
Key format: ctbt_<32 hex chars> — 128 bits of entropy. We store only its SHA-256 hash. The plaintext is shown once at creation; save it. Revoke leaked keys from the dashboard.
Endpoints
- POST
/api/detect/textbearer or cookieRun the full text-detection pipeline (burstiness, lexical tics, n-gram repetition, sentence-start variety, punctuation pattern, perplexity proxy) and return a JSON verdict + per-signal evidence.
Body
{ "text": "string, 1..100000 chars" }Counts toward the daily API limit (free: 100, pro: 5000). Daily window resets at 00:00 UTC.
- POST
/api/detect/imagebearer or cookieServer-side image forensics on a PNG or JPEG: per-channel statistics, noise-residual variance (Laplacian high-pass), frequency-energy ratio (8×8 DCT), and DCT watermark scan. Decoded with upng-js / jpeg-js — no JPEG re-encode, so ELA is NOT in the response (the web tool still runs ELA client-side).
Body
multipart/form-data with field "image", OR raw bytes with Content-Type: image/png | image/jpeg — max 8 MB; >1024 px is auto-downsampled.Counts toward the daily API limit. ~10–100 ms typical Worker CPU. Image is processed in-memory and discarded; nothing is stored.
- POST
/api/trackbearer or cookieBest-effort tracking endpoint used by the web tool to log a client-side detection. Anonymous callers get a silent 200; signed-in callers get the event persisted with source='web'. Not rate-limited.
Body
{ "modality": "text" | "image" | "audio" | "video", "verdict": "string", "combinedScore": 0..1, "bytes": number?, "summary": object? } - GET
/api/me/usagebearer or cookiePer-modality usage buckets for the authenticated user — today (since 00:00 UTC) + lifetime, plus current plan and daily API limit.
- GET
/api/me/history?limit=Nbearer or cookieRecent check events for the authenticated user, newest first. Default limit 50, hard-capped at 200.
- GET / POST / DELETE
/api/me/keys[/:id]cookie onlyList, create, and revoke API keys. Cookie-session only — managing keys must happen from the signed-in browser to keep blast radius small if one leaks.
POST returns the plaintext key EXACTLY once. Max 10 active keys per account.
Client examples
JavaScript / TypeScript
const res = await fetch("https://couldthisbetrue.com/api/detect/text", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.CTBT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ text }),
});
const data = await res.json();
if (data.ok) console.log(data.verdict, data.combinedScore);
else if (res.status === 429) console.warn("rate limited:", data.message);Python
import os, requests
r = requests.post(
"https://couldthisbetrue.com/api/detect/text",
headers={"Authorization": f"Bearer {os.environ['CTBT_API_KEY']}"},
json={"text": text},
timeout=30,
)
data = r.json()
if r.status_code == 200 and data["ok"]:
print(data["verdict"], data["combinedScore"])
elif r.status_code == 429:
print("rate limited; resets at 00:00 UTC")Rate limits
Each API event (a call to /api/detect/*) counts toward your daily quota. Web-tool detections are tracked but not counted toward the API quota. Window resets at 00:00 UTC.
| Plan | Daily API events | Notes |
|---|---|---|
| Free | 100 | Text checks via API, all 4 web-tool detectors free forever. |
| Pro | 5 000 | All web-tool detectors + API + history + (soon) image/audio/video API + batch. |
| Enterprise | — | Unmetered API + SLA + self-hosted option. |
429 response includes the current usage and limit, so you can back off and retry tomorrow.
Error responses
All errors return JSON of the shape { ok: false, error: string, message: string }.
| Status | error | When |
|---|---|---|
| 400 | bad_request | Missing required field, wrong type, or invalid value (e.g. empty `text`). |
| 401 | unauthorized | No valid Bearer key or session cookie. Get a key from /dashboard. |
| 404 | not_found | Resource doesn't exist or is already revoked (e.g. revoking a key twice). |
| 413 | payload_too_large | `text` exceeds 100 000 characters. |
| 429 | rate_limited | Daily API-event cap reached for your plan. Resets at 00:00 UTC. |
| 500 | internal_error | Detection pipeline threw. Retry; if persistent, email support. |
Coming soon
- POST /api/detect/audio — mel-spectrogram analysis + echo-hiding (Bender) watermark detection.
- POST /api/detect/video — per-frame DCT scan aggregated to a clip-level verdict.
- Webhooks — POST a result payload to your own URL after batch jobs finish.
- Batch upload — submit up to 100 files in one request, get a job id, poll or webhook.
Pro early-access subscribers get introductory pricing locked in for life. Email signups via sign-in go to the front of the line.
One important note
The text-detection pipeline today is a deterministic heuristic stack (burstiness, lexical tics, n-gram repetition, sentence-start variety, punctuation pattern, perplexity proxy) — not a neural classifier. It's fast, transparent, and good at catching the obvious LLM tells, but it will miss careful adversarial edits and it will sometimes flag formal human writing. Treat the verdict as one piece of evidence, not a courtroom verdict. The full per-signal evidence is in every response so you (or your reviewer) can make the final call.
Ready to plug it in?
Generate an API key →