API reference · Open beta
Build with context.
Practical client guide for image, sampled video, jobs and review dossiers.
Public checker API
The free website checker calls browser-safe endpoints without a tenant API key. Image and sampled video have no daily or monthly scan quota. Media-size, decoded-pixel, frame-count and validation limits still apply. Requests require the exact allowed Origin.
POST /v1/public/image-checkMultipart JPEG/PNG, maximum 4 MB and 25 million decoded pixels.POST /v1/public/video-checkJSON with 1–5 sampled frames, maximum 30 MB request body. Sampled-frame analysis, not temporal deepfake analysis.GET /v1/public/jobs/{job_id}Poll image/video jobs with the opaque X-Job-Token header.POST /v1/public/feedbackSubmit minimal image-result feedback using its signed result token.curl --fail-with-body -sS https://api.isthisaigenerated.app/v1/public/image-check \
-H 'Origin: https://isthisaigenerated.app' \
-F 'file=@./photo.jpg'
# A fresh image queues. The reply then carries job_id and job_token; poll with the
# token in the header until status is "completed", starting about a second later.
curl --fail-with-body -sS https://api.isthisaigenerated.app/v1/public/jobs/JOB_ID \
-H 'Origin: https://isthisaigenerated.app' \
-H 'X-Job-Token: JOB_TOKEN'Results are warning signals, not proof of origin. See the live Swagger reference or download the OpenAPI JSON for request and response schemas, headers and error codes.
Passwordless account and API keys
POST /v1/auth/magic-links accepts an email and always returns generic accepted copy. Consume the one-time token with POST /v1/auth/magic-links/consume; the response sets an HTTP-only session cookie and returns a CSRF token. Store CSRF in sessionStorage, send credentials: include, and include X-CSRF-Token plus the trusted Origin on browser writes.
GET /v1/model-infoActive image model, backend, thresholds and warning-only policy.GET /v1/accountEmail, tenant, role, free usage, paid balance and total available.GET / POST / DELETE /v1/account/api-keysList metadata, create a key shown once, or revoke it. Owner + CSRF required for writes.Server integrations send Authorization: Bearer $API_KEY. Never expose a key in browser code, localStorage, URLs, logs or data attributes.
Tokens and prepaid billing
Accounts receive 100 free tokens each UTC month. Paid tokens persist. Bundles are 1,000 for €5, 5,000 for €25 and 20,000 for €100: €0.005/token, one-time Stripe payment, no subscription or automatic overage.
| Operation | Tokens | Public status |
|---|---|---|
| Image | 1 / request | Supported lane |
| Tenant video | 1 / submitted frame, up to 8 | Supported aggregate |
| Text API | 1 / request | Not a supported public lane |
POST /v1/billing/checkout-sessionsOwner session + CSRF + fresh Idempotency-Key; body contains only bundle_code. Redirect only to returned url.GET /v1/billing/orders/{order_id}Tenant-scoped order status.GET /v1/billing/ledgerPaid-token ledger.HTTP 402 means tokens are exhausted, 409 means an idempotency key conflicts, and 503 means billing is unavailable.
Image check
POST /v1/assets/check accepts JSON with base64 JPEG or multipart with a JPEG/PNG file.
curl "$BASE_URL/assets/check" \
-H "Authorization: Bearer $API_KEY" \
-F "file=@./sample.jpg" \
-F "client_asset_id=asset-001" \{
"client_asset_id": "asset-001",
"thumb_jpeg_base64": "<base64 omitted>",
}Request fields
| Field | Type | Required / meaning |
|---|---|---|
file | JPEG/PNG | Multipart image unless fingerprints resolve |
thumb_jpeg_base64 | string | JSON low-resolution JPEG unless fingerprints resolve |
client_asset_id | string | Optional caller reference/idempotency identifier |
frame_sha256 / frame_phash | string | Optional exact / perceptual fingerprints |
| boolean | Optional; true by default, false queues fresh inference |
Representative response
{
"asset_id": "asset_example", "job_id": null, "status": "known",
"label": "possible_synthetic_indicators", "score": 0.84,
"confidence": null, "thresholds": {"possible_synthetic_indicators": 0.8},
"source": "model", "model": "active-model-version",
"cached": false, "ttl_seconds": 86400
}Identifiers and numbers illustrate the schema; they are not benchmark results or guaranteed outputs.
Sampled video
POST /v1/videos/check-frames accepts up to 8 image request objects plus a stable video_footprint. Use 5–8 representative frames. This is not temporal deepfake analysis.
{
"video_footprint": "stable-footprint",
"frames": [{"thumb_jpeg_base64": "[BASE64_OMITTED]"}],
}| Field | Type | Constraint |
|---|---|---|
video_footprint | string | Stable caller fingerprint; recommended |
frames | AssetCheckRequest[] | 0–8 frames; use 5–8 on the first request |
| boolean | Default true |
Representative aggregate response
{
"status": "known", "label": "possible_synthetic_indicators",
"score": 0.83, "median_score": 0.83, "mean_score": 0.82,
"max_score": 0.86, "frame_count": 6, "scored_frame_count": 6,
"pending_frame_count": 0, "frames": [], "cached": false,
"source": "video_aggregate"
}A real response includes per-frame response objects. Repeat with the same footprint and no frames for a cache lookup. A miss returns unknown.
Jobs and stored assets
Image inference is asynchronous: the initial response has status: processing and a job_id. There is no synchronous mode — a sync field used to be documented here, but nothing ever read it.
GET /v1/jobs/{job_id}Poll processing, completed or failed. Completed jobs include result.GET /v1/assets/{asset_id}Retrieve the tenant-scoped complete result.{"job_id":"job_example","status":"completed","result":{"asset_id":"asset_example","status":"known","label":"possible_synthetic_indicators"}}Recommended client policy: poll after 1, 2, 4, 8 and then 10 seconds; cap each later delay at 10 seconds and stop after 60 seconds or on completed/failed. The server does not currently guarantee that timeout and exposes no webhook or Retry-After contract.
Human review lifecycle
POST /v1/reviewsMultipart file, required declared_ai_use, optional supplier and notes.GET /v1/reviewsList tenant dossiers; no pagination contract during beta.GET /v1/reviews/{review_id}Retrieve one dossier.POST /v1/reviews/{review_id}/decisionRecord disclose, no_disclosure, reject or recheck.GET /v1/reviews/{review_id}/exportExport JSON.DELETE /v1/reviews/{review_id}Delete; success is 204.curl "$BASE_URL/reviews" \
-H "Authorization: Bearer $API_KEY" \
-F "file=@./sample.jpg" \
-F "declared_ai_use=unknown" \
-F "supplier=Example supplier"{"decision":"disclose","reviewer":"reviewer@example.invalid","rationale":"Disclosure selected after source and detector review."}Review responses include review_id, pending/completed status, content hash, filename, declaration, provenance, detector, optional decision, policy and created/updated timestamps. They never automatically reject or publish content.
HTTP status
| Status | Meaning |
|---|---|
| 200 / 204 | Completed or deleted |
| 400 | Invalid or unsupported input |
| 401 / 403 | Missing key, invalid key or insufficient scope |
| 404 | Tenant-scoped resource not found |
| 413 | Upload exceeds configured maximum |
| 422 | Typed JSON validation failed |
| 429 | Per-key rate limit exceeded |
| 503 | Production dependency or worker not ready |
Errors use {"detail":"error_code"}; for example {"detail":"invalid_api_key"}. Every response includes X-Request-ID. The current async image submission still returns the normal endpoint response with status: processing; do not assume HTTP 202.
