Checkobot

API documentation

One REST call per image: a verdict, which detector fired, both detector scores and the face box. Base URL https://api.checkobot.ai.

Authentication

Send your API key as a bearer token: Authorization: Bearer cb_live_…. Create and revoke keys in your dashboard. Keys come with the API plans; we store only a hash of each key, so copy it when it’s shown.

Check an image: POST /v1/check

Send the image one of three ways. The original bytes are scored as sent; don’t resize or re-encode first.

  • multipart/form-data with a file field
  • JSON {"image_url": "https://…"}: we fetch it (http/https, public addresses only)
  • JSON {"image_b64": "…"}: standard base64 of the original file

Accepted: JPEG, PNG, WebP, AVIF, GIF (first frame), BMP and TIFF, up to 20 MB. Images under 256 px on the shorter side, or JPEGs below about quality 50, are refused with a reason and aren’t metered.

curl https://api.checkobot.ai/v1/check \
  -H "Authorization: Bearer $CHECKOBOT_API_KEY" \
  -F "file=@photo.jpg"

# or from a URL
curl https://api.checkobot.ai/v1/check \
  -H "Authorization: Bearer $CHECKOBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"image_url": "https://example.com/photo.jpg"}'
200 response
{
  "id": "chk_Elp2uq1pt4qfqnDo",
  "band": "ai",
  "is_ai": true,
  "fired": ["face_specialist"],
  "reason": "Face analysis found strong signs that the face was swapped or edited with AI.",
  "face_found": true,
  "content_credentials": { "found": false },
  "image": { "width": 512, "height": 512 },
  "model": "genai_face_v1",
  "bands_version": "0.3",
  "latency_ms": 612,
  "scores": { "v0": 0.0965, "face_specialist": 0.974 },
  "face_box": [71, 100, 136, 136],
  "profile": "general",
  "image_stored": false
}
  • band: ai or no_ai (shown as “No AI detected”). is_ai is the same as a boolean.
  • fired: which detectors crossed their threshold: v0 (whole image) and/or face_specialist (face analysis), plus content_credentials when the file carries signed Content Credentials that record AI.
  • content_credentials: the C2PA manifest embedded in the file, if any: { found, signer, verified, ai }. verified means the signer chains to the C2PA trust list and the image is unchanged since signing; only verified manifests can make the verdict ai. { found: false } is normal: most sites strip manifests, so it says nothing either way.
  • scores: each detector’s raw score, 0 to 1. They’re on different scales, so don’t average or compare them; use band and fired. face_specialist is null when no face of at least 64 px was found. Both are nullwhen verified Content Credentials record AI: that decides the verdict, so the detectors aren’t run.
  • face_box: [x, y, w, h] of the face checked, in original pixels, or null.
  • model and bands_version: store them with the result; verdicts can change when either changes.
  • image_stored: true when this image is being kept (see below).

Image storage

By default an image sent with an API key is processed in memory and not stored; only the SHA-256 hash of the file, the verdict and the scores are kept. To keep images, either turn on Store images for the key in your dashboard, or send store=true with a request: as a query parameter (POST /v1/check?store=true), a store form field with multipart uploads, or "store": true in a JSON body. store=falseskips storage for one request even when the key’s switch is on. It works the same on /v1/check/batch, for every image in the call.

Stored images are encrypted and kept for your plan’s 30-day history, then deleted. You can see and remove them in your dashboard history. They aren’t shown on the check’s result page unless you choose to show them there.

Batch: POST /v1/check/batch

1 to 8 images per call, on API Growth and above. Send multipart files fields or JSON images. Results come back in the same order; a bad image gets its own error and doesn’t stop the rest. Only successful images are metered.

curl https://api.checkobot.ai/v1/check/batch \
  -H "Authorization: Bearer $CHECKOBOT_API_KEY" \
  -F "files=@one.jpg" -F "files=@two.png"
200 response
{
  "results": [
    { "id": "chk_…", "band": "no_ai", "is_ai": false, "fired": [], "reason": "…", "scores": { … }, … },
    { "error": { "code": "image_too_small", "message": "This image is too small to check reliably (200×300). …" } }
  ],
  "model": "genai_face_v1",
  "bands_version": "0.3",
  "latency_ms": 1840
}

Usage: GET /v1/usage

Checks used in the current period (calendar month, UTC). On API plans, checks past the allowance are allowed and counted as overage, billed monthly at your plan’s rate: $0.012 on Starter, $0.009 on Growth, $0.006 on Scale.

200 response
{ "plan": "api_starter", "period": "2026-10", "used": 1204, "quota": 5000, "overage": 0 }

Errors

Every error has the same shape. Branch on code; the message is for humans.

error
{ "error": { "code": "image_too_small", "message": "This image is too small to check reliably (200×300). Try a copy at least 256 px on its shorter side." } }
codeHTTPWhen
bad_request400Malformed body, no image field, invalid base64, a store value other than true/false, or a batch outside 1–8 images.
url_fetch_failed400image_url couldn't be fetched: not http(s), a non-standard port, a private address, a non-200 response, or too many redirects.
unauthorized401Missing, invalid or revoked API key.
plan_required403The key's plan has no API access, or the endpoint needs a higher plan (batch is API Growth and above).
image_too_large413Image over 20 MB, or a batch over 25 MB in total.
unsupported_format415HEIC/HEIF, a file that isn't an image, or one the detector couldn't decode.
image_too_small422Shorter side under 256 px. Refused rather than judged; not metered.
image_too_compressed422JPEG quality below about 50. Refused rather than judged; not metered.
content_refused422The image can't be checked. Not metered. Don't retry the same image.
quota_exceeded429Monthly allowance used up on a plan without overage.
rate_limited429More than 10 requests per second on one key (burst of 10).
model_warming503The detector is starting up after a quiet spell. Retry after retry_after_s (also sent as Retry-After). Not metered.
safety_check_unavailable503A pre-check couldn't run, so the image wasn't checked. Retry after retry_after_s (also sent as Retry-After). Not metered.
upstream_error502The detector failed. Safe to retry with backoff. Not metered.
internal500Unexpected error on our side. Not metered.

Limits and retries

  • 10 requests per second per key, with bursts up to 10.
  • Retry model_warming, upstream_error and rate_limitedwith exponential backoff. Don’t retry 4xx input errors.
  • Allow up to 60 seconds per request. The first call after a quiet spell can be slow while the detector starts.