NUis this AI generated?

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.
Public image example
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.

OperationTokensPublic status
Image1 / requestSupported lane
Tenant video1 / submitted frame, up to 8Supported aggregate
Text API1 / requestNot 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.

Multipart
curl "$BASE_URL/assets/check" \
  -H "Authorization: Bearer $API_KEY" \
  -F "file=@./sample.jpg" \
  -F "client_asset_id=asset-001" \

JSON shape
{
  "client_asset_id": "asset-001",
  "thumb_jpeg_base64": "<base64 omitted>",
  
}

Request fields

FieldTypeRequired / meaning
fileJPEG/PNGMultipart image unless fingerprints resolve
thumb_jpeg_base64stringJSON low-resolution JPEG unless fingerprints resolve
client_asset_idstringOptional caller reference/idempotency identifier
frame_sha256 / frame_phashstringOptional exact / perceptual fingerprints
booleanOptional; true by default, false queues fresh inference

Representative response

200 JSON
{
  "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.

JSON request
{
  "video_footprint": "stable-footprint",
  "frames": [{"thumb_jpeg_base64": "[BASE64_OMITTED]"}],
  
}

FieldTypeConstraint
video_footprintstringStable caller fingerprint; recommended
framesAssetCheckRequest[]0–8 frames; use 5–8 on the first request
booleanDefault true

Representative aggregate response

200 JSON
{
  "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.
Completed job
{"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.
Create review
curl "$BASE_URL/reviews" \
  -H "Authorization: Bearer $API_KEY" \
  -F "file=@./sample.jpg" \
  -F "declared_ai_use=unknown" \
  -F "supplier=Example supplier"

Decision JSON
{"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

StatusMeaning
200 / 204Completed or deleted
400Invalid or unsupported input
401 / 403Missing key, invalid key or insufficient scope
404Tenant-scoped resource not found
413Upload exceeds configured maximum
422Typed JSON validation failed
429Per-key rate limit exceeded
503Production 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.