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:

  1. Bearer token Authorization: Bearer ctbt_<…> header. Use for CLI, CI, server-to-server. Get a key from /dashboard.
  2. 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 cookie

    Run 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 cookie

    Server-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 cookie

    Best-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 cookie

    Per-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 cookie

    Recent check events for the authenticated user, newest first. Default limit 50, hard-capped at 200.

  • GET / POST / DELETE/api/me/keys[/:id]cookie only

    List, 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.

PlanDaily API eventsNotes
Free100Text checks via API, all 4 web-tool detectors free forever.
Pro5 000All web-tool detectors + API + history + (soon) image/audio/video API + batch.
EnterpriseUnmetered 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 }.

StatuserrorWhen
400bad_requestMissing required field, wrong type, or invalid value (e.g. empty `text`).
401unauthorizedNo valid Bearer key or session cookie. Get a key from /dashboard.
404not_foundResource doesn't exist or is already revoked (e.g. revoking a key twice).
413payload_too_large`text` exceeds 100 000 characters.
429rate_limitedDaily API-event cap reached for your plan. Resets at 00:00 UTC.
500internal_errorDetection 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 →