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-datawith afilefield- 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"}'{
"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:aiorno_ai(shown as “No AI detected”).is_aiis the same as a boolean.fired: which detectors crossed their threshold:v0(whole image) and/orface_specialist(face analysis), pluscontent_credentialswhen the file carries signed Content Credentials that record AI.content_credentials: the C2PA manifest embedded in the file, if any:{ found, signer, verified, ai }.verifiedmeans the signer chains to the C2PA trust list and the image is unchanged since signing; only verified manifests can make the verdictai.{ 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; usebandandfired.face_specialistisnullwhen no face of at least 64 px was found. Both arenullwhen 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, ornull.modelandbands_version: store them with the result; verdicts can change when either changes.image_stored:truewhen 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"{
"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.
{ "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": { "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." } }| code | HTTP | When |
|---|---|---|
bad_request | 400 | Malformed body, no image field, invalid base64, a store value other than true/false, or a batch outside 1–8 images. |
url_fetch_failed | 400 | image_url couldn't be fetched: not http(s), a non-standard port, a private address, a non-200 response, or too many redirects. |
unauthorized | 401 | Missing, invalid or revoked API key. |
plan_required | 403 | The key's plan has no API access, or the endpoint needs a higher plan (batch is API Growth and above). |
image_too_large | 413 | Image over 20 MB, or a batch over 25 MB in total. |
unsupported_format | 415 | HEIC/HEIF, a file that isn't an image, or one the detector couldn't decode. |
image_too_small | 422 | Shorter side under 256 px. Refused rather than judged; not metered. |
image_too_compressed | 422 | JPEG quality below about 50. Refused rather than judged; not metered. |
content_refused | 422 | The image can't be checked. Not metered. Don't retry the same image. |
quota_exceeded | 429 | Monthly allowance used up on a plan without overage. |
rate_limited | 429 | More than 10 requests per second on one key (burst of 10). |
model_warming | 503 | The detector is starting up after a quiet spell. Retry after retry_after_s (also sent as Retry-After). Not metered. |
safety_check_unavailable | 503 | A pre-check couldn't run, so the image wasn't checked. Retry after retry_after_s (also sent as Retry-After). Not metered. |
upstream_error | 502 | The detector failed. Safe to retry with backoff. Not metered. |
internal | 500 | Unexpected error on our side. Not metered. |
Limits and retries
- 10 requests per second per key, with bursts up to 10.
- Retry
model_warming,upstream_errorandrate_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.