Developers
Business API
Check images and videos for signs of AI generation from your own servers - for example, everything a user posts to your platform - without anyone opening the CESOERA website.
Getting access
API keys are issued on request to Business accounts. Contact us with your account email and expected volume. API calls use the same credits as scans on the website: one credit per image and, for video, 2-18 credits by length (measured by us, never taken from the request), drawn from your plan's monthly balance.
Authentication
Send your key in the Authorization header. Keys start with ces_live_. Keep them on your servers - never in a browser, mobile app or public repository. If a key leaks, ask us to revoke it and we will issue a new one.
Authorization: Bearer ces_live_XXXXXXXX_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXDetect an image
POST /api/v1/detect/image accepts either an image URL (JSON) or the image itself (multipart). JPEG, PNG and WebP up to 15 MB. reference is optional - your own id for the item, returned with the result.
By URL
curl -X POST https://cesoera.com/api/v1/detect/image \
-H "Authorization: Bearer $CESOERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: post-8841" \
-d '{"url": "https://images.example.com/uploads/8841.jpg", "reference": "post-8841"}'By upload
curl -X POST https://cesoera.com/api/v1/detect/image \
-H "Authorization: Bearer $CESOERA_API_KEY" \
-F "file=@photo.jpg" \
-F "reference=post-8841"Node.js
const res = await fetch("https://cesoera.com/api/v1/detect/image", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CESOERA_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": postId,
},
body: JSON.stringify({ url: imageUrl, reference: postId }),
});
if (res.status === 503 || res.status === 429) {
const wait = Number(res.headers.get("Retry-After") ?? 10);
// retry after `wait` seconds
}
const detection = await res.json();Python
import os, requests
r = requests.post(
"https://cesoera.com/api/v1/detect/image",
headers={"Authorization": f"Bearer {os.environ['CESOERA_API_KEY']}",
"Idempotency-Key": post_id},
json={"url": image_url, "reference": post_id},
timeout=60,
)
detection = r.json()The response
{
"id": "4f0c1c7e-9b0a-4d1e-9a51-2d7f3c1b8e20",
"object": "detection",
"created_at": "2026-09-14T18:21:04.512Z",
"status": "completed",
"content_type": "image",
"input": "url",
"reference": "post-8841",
"verdict": "strong_indication",
"ai_likelihood": 0.91,
"confidence": "high",
"model": "…",
"credits_charged": 1,
"processing_ms": 412,
"error_code": null,
"request_id": "…",
"disclaimer": "CESOERA returns a probabilistic indicator, not proof. …"
}| verdict | Meaning |
|---|---|
strong_indication | Strong signs of AI generation. |
some_indication | Some signs of AI generation. |
inconclusive | Not enough evidence either way. |
low_indication | Little sign of AI generation. |
ai_likelihood is on the same 0-1 scale as the website, aligned with the verdict bands (0-0.25 low, 0.25-0.5 inconclusive, 0.5-0.75 some, 0.75-1 strong). Results are probabilistic: use them to prioritise human review or label content, not as proof about a specific person.
Detect a video (asynchronous)
POST /api/v1/detect/video takes an MP4, MOV, WebM or M4V file (multipart file) or a direct video URL (JSON url), up to 100 MB and 10 minutes. Videos are analysed frame by frame, which takes from a few seconds to a few minutes, so the call returns 202 Accepted immediately with a detection in status queued. The credits shown are held, measured from the video's real duration; they settle to the actual cost on completion and are fully refunded on failure.
curl -X POST https://cesoera.com/api/v1/detect/video -H "Authorization: Bearer $CESOERA_API_KEY" -H "Content-Type: application/json" -H "Idempotency-Key: video-5521" -d '{"url": "https://media.example.com/v/5521.mp4",
"reference": "video-5521",
"callback_url": "https://api.example.com/cesoera/webhook"}'HTTP/1.1 202 Accepted
Location: /api/v1/detections/0d6b…
{ "id": "0d6b…", "object": "detection", "status": "queued", "content_type": "video",
"duration_seconds": 42.5, "credits_charged": 5, "verdict": null, "queue_position": 1, … }Then either poll GET /api/v1/detections/{id} every few seconds until status is completed or failed, or supply callback_url (public https://) and we will POST the finished detection to you. Statuses: queued → processing → completed | failed. Videos are processed one at a time; if the queue is full the call returns 503 with Retry-After and nothing is charged.
Webhooks
When a video with a callback_url finishes, we send POST with JSON { "type": "detection.completed" | "detection.failed", "data": { …detection… } }. Answer with any 2xx within 10 seconds. We retry twice (after 5 and 30 seconds), and webhook_status on the detection shows whether delivery succeeded. Polling always works as a fallback.
Every delivery carries CESOERA-Signature: t=<unix time>,v1=<hex>: an HMAC-SHA256 of <t>.<raw body> using, as the secret, the SHA-256 hex digest of your API key. Verify it on the raw body before trusting the payload, and reject timestamps older than 5 minutes.
import crypto from "node:crypto";
const secret = crypto.createHash("sha256").update(process.env.CESOERA_API_KEY).digest("hex");
function verify(rawBody, header) {
const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(v1, "hex"));
}Fetch a result again
curl https://cesoera.com/api/v1/detections/4f0c1c7e-9b0a-4d1e-9a51-2d7f3c1b8e20 \
-H "Authorization: Bearer $CESOERA_API_KEY"Sending the same Idempotency-Key again returns the stored result with the header Idempotent-Replayed: true, without running the model or charging again. Use your own item id as the key so retries after timeouts are always safe.
Usage and credits
curl https://cesoera.com/api/v1/usage -H "Authorization: Bearer $CESOERA_API_KEY"Returns your plan, credits used and remaining this calendar month (UTC), and the key's rate limit. Checking usage is free. A failed detection is never charged.
Errors and retries
Errors share one shape:
{ "error": { "code": "rate_limit_reached", "message": "…", "requestId": "…", "details": {} } }| Status | code | What to do |
|---|---|---|
| 400 | validation_failed, file_required, unsupported_content_type | Fix the request. |
| 400 | invalid_url, unsafe_url, url_unreachable, url_empty | The URL must be a reachable, public image address. |
| 401 | invalid_api_key | Check the key; it may have been revoked. |
| 413 / 415 | file_too_large, url_too_large, unsupported_type, invalid_file_signature | Images: JPEG, PNG or WebP up to 15 MB. Videos: MP4, MOV, WebM or M4V up to 100 MB. |
| 413 / 422 | video_too_long, video_unreadable | Videos must be readable and 10 minutes or shorter. |
| 400 | invalid_callback_url | callback_url must be a public https:// address. |
| 429 | rate_limit_reached | Retry after the Retry-After header (seconds). |
| 429 | scan_limit_reached | Monthly credits are used up; do not retry until they renew. |
| 502 | detection_failed | Retry later with the same Idempotency-Key. Not charged. |
| 503 | capacity_exhausted | The service is busy. Retry after Retry-After. |
Limits and privacy
- Each key has a per-minute request limit agreed when it is issued (60 by default).
- Detection capacity is shared across all customers. Under load, requests queue briefly and may receive 503 with
Retry-After. Contact us before sending sustained high volume. - Images typically take under a second; allow up to 60 seconds before timing out. Videos are asynchronous.
- Video requests are limited to a tenth of the key's per-minute limit (at least 2 per minute).
- Media you send is analysed and discarded. Images are never written to disk; a queued video is held in a private temporary file only until its analysis ends. We do not store the media, its filename or its URL. We keep the result (verdict, likelihood, duration, credits, time and your reference) so you can fetch it again.
- Images and video are available. Text through the API is planned.