Send a file — or a link — and get a verdict with the confidence behind it. Works for images and video. Audio is in preview: Tayanch shows what the model hears and a research score, but gives no audio verdict until a model passes its published release test.
100 free checks, 30 requests/min — no card required. The key is shown once and stored only as a hash; when the free quota runs out you'll get HTTP 402 — contact us to raise your quota. Want to see your keys and usage later, or revoke one? Create an account instead — same free key, plus a page to manage it.
We use your email to issue and meter the key. By creating a key you accept the Terms — see Privacy.
Every number below is dated, carries its sample size, and says what it was measured on.
/analyze/url and the server fetches the media (SSRF-guarded) and runs the same pipeline — social previews resolve automatically.X-API-Key header.Base URL: https://jamoliddinbekyodgorov--tayanch-detector-tayanch-serve.modal.run
HTTPS only. Send your key in the X-API-Key header. Every billed reply carries
X-Quota-Remaining; when it reaches 0 the next call answers 402.
curl -X POST "https://jamoliddinbekyodgorov--tayanch-detector-tayanch-serve.modal.run/analyze" \ -H "X-API-Key: $TAYANCH_KEY" \ -F "image=@photo.jpg" -F "preview=0"
| Route | Auth | Send | What it does |
|---|---|---|---|
GET /health | none | — | Service health and which modalities are live. |
GET /pubkey | none | — | The server's current public key for sealing uploads (TSE2). Query: nonce (64 hex chars). |
POST /analyze | X-API-Key | multipart: image (the image file (JPEG, PNG, WebP, HEIC, …; max 40 MB)), preview (0 to omit the preview thumbnail) | Analyse one image. |
POST /analyze/video | X-API-Key | multipart: video (the video file (MP4, MOV, WebM, …)) | Analyse one video (max 200 MB). |
POST /analyze/audio | X-API-Key | multipart: audio (wav, mp3, m4a, aac, ogg, opus, flac or webm) | Analyse one audio file (first 120 s). Advisory while the audio head has no measured operating point. |
POST /analyze/url | X-API-Key | JSON: url (http(s) URL of the post or file) | Fetch a public link (image, video or social post) and analyse its media. |
POST /heatmap | X-API-Key | multipart: image (the same image), token (heatmap_token from /analyze), gate (the verdict's p_ai) | Region map for an image already analysed; not billed when nothing is painted. |
POST /feedback | X-API-Key | JSON: kind (image, video, audio or link), shown (the verdict), claimed (ai, real or unsure), optional model and config_version from the reply | Report a wrong verdict. Stores one row with those fields and the day; never takes a file (a multipart body is refused with 415). Not billed. |
POST /auth/signup | none | JSON: email (your e-mail address) | Create a free API key (100 checks, 30 requests/min). |
POST /auth/register | none | JSON: email (e-mail), password (at least 10 characters), name (optional), company (optional) | Create an account and sign in. |
POST /auth/login | none | JSON: email (e-mail), password (password) | Sign in; returns a session token. |
POST /auth/logout | Bearer token | — | End this session. |
GET /account | Bearer token | — | The signed-in profile, keys, sessions and 30-day usage. |
verdict is the answer: ai, real or uncertain. Read it first;
p_ai is the model's score, compared with thresholds, not a calibrated probability.mix ("Mixed signals"): how much reads AI and how much reads real, each model on its
own calibrated band, and which parts. The verdict stays uncertain.method: provenance means signed AI provenance (C2PA) decided the call before any
pixel was scored; content means the pixels were scored./health reports audio as
preview): those replies read "No verdict yet (audio beta)", carry advisory_only: true,
verdict uncertain, p_ai and ai_probability null, and the research score
under advisory. They are not charged. The route may also answer 503 under_maintenance, or 403 with code
audio_research_preview to an API key while audio runs on the site demo only.model and config_version with every result: when either changes, the same
file can get a different verdict. Changes are listed in the changelog below.Replies are JSON objects. Keys are only ever added, so ignore keys you do not know.
| Field | Meaning |
|---|---|
bbox_frame | Heat map / localized edit: the coordinate frame of bbox — 'image' (full-image pixel coordinates) or 'square' (the centre square the localizer examined). |
decided_by | Which head produced p_ai and the verdict: the primary image model, the web-copy sidecar, a fallback candidate, or provenance. |
examined | Heat map / localized edit: the region the localizer actually looked at (only the centre is examined), in the same frame as bbox. |
verdict | ai, real or uncertain (maintenance replies: unavailable). Uncertain is deliberate: the evidence did not support an answer. |
label | Short display text: 'Likely AI-generated', 'Likely authentic', 'Uncertain — flag for review', 'Mixed signals', 'Uncertain — possible local edit' and their video or audio variants. |
confidence | 0-100: how far the score sits past the threshold that decided the verdict. 0 on uncertain replies. Not a probability. |
p_ai | The model's score in [0, 1]. Higher means more AI-like. It is a ranking score compared with thresholds, not a calibrated probability; read verdict first. |
ai_probability | Same value as p_ai (kept for older clients). |
conclusion | Legacy summary string: 'AI-Generated', 'REAL' or 'UNCERTAIN'. Prefer verdict. |
detail | One human-readable sentence about how the verdict was reached, or null. |
method | content (pixels were scored), provenance (signed AI provenance decided it) or video_content; on /heatmap: inpaint_localizer, aeroblade or none. |
model | Name of the head that produced the score. |
config_version | Short hash of the served operating configuration (heads and thresholds). It changes whenever a verdict could change for the same file; log it with each result. |
elapsed_ms | Server-side processing time in milliseconds (excludes upload and network). |
mix | Present only on some uncertain replies: 'Mixed signals' — ai_share and real_share (each model's read on its own calibrated band), every model's read, and the parts (time segments or an image region). The verdict stays uncertain. |
| Field | Meaning |
|---|---|
degradation | Image: how much forensic signal the file has left — level (none, moderate, heavy), reasons (e.g. recompressed, resized, no camera EXIF), size and format. |
error | Machine-readable or human-readable error string. |
heatmap_token | Image: pass back to POST /heatmap within 30 minutes to get the region map without re-sending the verdict. |
localized_edit | Image judged real: the edited-photo screen's result {score, thr, fires}; fires=true turns the verdict into 'Uncertain — possible local edit'. |
preview | Image: a small (<= 360 px) JPEG data URL of the analysed image; omitted when the request sends preview=0. |
provenance_kind | Provenance replies: which marker was found (e.g. C2PA digitalSourceType). |
provenance_verified | Provenance replies: true when the C2PA signature was cryptographically verified. |
| Field | Meaning |
|---|---|
analysis_basis | Video: which evidence decided the verdict — frames, face, talking_head, temporal, audio, provenance, conflict or preview. |
audio | Video: the soundtrack screen's result (advisory), or null when the clip has no audio or the screen is off. |
cover_p_ai | Link check of a video: the image model's score on the post's cover picture. Recorded, never a verdict on its own. |
face_coverage | Video: share of sampled frames with a face large enough to score. |
face_p_ai | Video: the face-swap model's own score (0-1), present only when it read a face (coverage ≥ 0.25) and did not fail; evidence only, it never decides the verdict alone. |
frame_scan | Video: the per-frame scan — n frames, median score, share confidently AI, the thresholds, a timeline of {t, p_ai, band} and the composition in percent. |
message | Human-readable message (maintenance replies). |
modality | Which modality is under maintenance. |
scope | Video: whole-frame or face scope of the scan. |
source_media | Link check: original (the full file was fetched) or preview (only a social preview image was available). |
source_url | Link check: the URL that was fetched. |
status | Health: ok, or degraded when any entry in checks.failed is set (HTTP stays 200). Maintenance replies: under_maintenance. |
t2v | Video: the text-to-video head's read, or null. |
talking_head | Video: the talking-head head's read, or null. |
timings_ms | Video: time spent per stage. |
| Field | Meaning |
|---|---|
advisory | Audio without an accepted calibration only: the research score (score, the statistic it is, the release gate the head failed, the window aggregates and per-window reads). Not a verdict; p_ai and ai_probability are null in these replies. |
advisory_only | true when the audio head has no measured operating point: the score is information, never a verdict. |
agg | Audio: which window aggregate the verdict statistic uses (pooled, w_max, w_med or w_q75). |
calibration | Audio: where the thresholds come from; 'UNCALIBRATED' means the reply is advisory. |
domain | Audio: the kind of sound the head judged (speech, music or env). |
domain_p | Audio: the head's confidence in that domain, 0-1. |
duration_s | Audio: seconds analysed (at most 120). |
analyzed_s | Audio: seconds actually analysed (only the first 120 s of a file are read). |
file_s | Audio: the whole file's length in seconds, when it could be measured; else null. |
truncated | Audio: true when the file was longer than the analysed part (truncation_note then says how much was read). |
gate | Heat map: the verdict score the heat map was gated on. Audio: the content gate that applied. |
kind | Audio: which head family scored the file (speccube or xlsr). |
n_win | Audio: number of 8-second windows scored. |
offmanifold_z | Audio: distance from the real-sound manifold, in standard deviations. |
pooled | Audio: the pooled window score. |
speech_frac | Audio: share of the file detected as speech. |
starts_s | Audio: start time in seconds of each scored window. |
thr_ai | Audio: the score at or above which the verdict would be AI. |
thr_real | Audio: the score at or below which the verdict would be real. |
threshold_domain | Audio: which domain's thresholds applied. |
views | Audio: the spectrogram views the head reads. |
w_max | Audio: the highest window score. |
w_med | Audio: the median window score. |
w_q75 | Audio: the 75th-percentile window score. |
windows | Audio: the score of each window. |
| Field | Meaning |
|---|---|
bbox | Heat map: the localizer region [x0, y0, x1, y1] in pixels of the returned heat-map image (not the original upload). |
edited_score | Heat map: the localizer's score for the region it found, 0-1. |
flag | Heat map: 'check' when a region is worth a human look (amber, never an accusation). |
heatmap | Heat map: a PNG data URL, or null when there is nothing to paint. |
mask_area | Heat map: share of the image inside the localizer's mask. |
reason | Why no heat map was painted, or why a token was refused. |
zoom | Heat map: a PNG data URL zoomed on the region, or null. |
| Field | Meaning |
|---|---|
account | The signed-in profile: e-mail, name, company, verification state, its API keys (prefix, plan, quota used and total, last use — never the key) and, on GET /account, its sessions. |
api_key | The new API key. Shown once; only its hash is stored. |
email_sender | Whether e-mail actions work: {configured, support}. When configured is false, password reset is unavailable; write to support. |
expires_at | Session expiry, Unix time (30 days). |
limits | Account: max keys, free quota and free rate. |
note | Key signup: save-once reminder. |
ok | true on success. |
plan | Key plan (free). |
quota | Key signup: number of checks included. |
rate_per_min | Key signup: requests per minute allowed. |
token | Session bearer token. Send as 'Authorization: Bearer <token>' on /account routes. Only its hash is stored. |
upgrade | Key signup: what happens when the quota runs out. |
usage | Key signup: a ready-to-run curl example with the base URL. |
usage_30d | Account: calls in the last 30 days — total, by route, days active. |
| Field | Meaning |
|---|---|
attestation | Attested tier only: the evidence the client checks before sealing to this key; null on the standard tier. |
audio_head | Name of the served audio head, or null. |
build | Which deploy answered: the first 12 characters of the git commit (plus '-dirty' when uncommitted served edits shipped), or 'unknown'. On /health, the full record: sha, dirty, diff_sha256, branch, built_at, artifacts (sha256[:12] of each served model and config) and id. |
code | Errors: a stable machine-readable code (e.g. too_large, rate_limited, image_unavailable, timeout, token_expired). Branch on this, not on the error sentence. |
retryable | Errors: true when the same request may succeed later (429, 5xx); honour Retry-After. |
request_id | Errors: the id of this request, also sent as the X-Request-Id header on every reply. Quote it to support. |
checks | Health: per-component readiness. |
link_auth | Health: true when link video downloads run signed in to the platforms (yt-dlp cookies present); false means Instagram/TikTok reels may fall back to the preview picture. |
ready | Health: true once the image model is built on this container (a cold container answers /health before it is warm). |
request_errors | Health: {count, last_seen} of unhandled server errors on this container. A counter, not a failed check. |
client | Public key document: the client versions this envelope suite supports. |
container_started | Health: Unix time the serving container started. |
envelope | Public key document: the sealed-envelope format (TSE2). |
key_mode | Public key document: how the server key is held (ephemeral, env or file). |
kid | Key id of the server key the envelope must be sealed to. |
kid | Key id of the server key the envelope must be sealed to. |
minted_at | Public key document: when this key was created, Unix time. |
modalities | Health: image / video / audio each 'live' or 'maintenance'; audio is 'preview' while its head has no accepted calibration (replies carry no verdict). |
not_after | Public key document: the key is not accepted after this Unix time. |
pubkey | Pre-open error: the current public key document, so the client can reseal. |
public_key | Public key document: the server's X25519 public key, base64. |
server_time | Pre-open error: the server clock, so the client can correct a stale timestamp. |
suite | Public key document: HPKE suite (X25519, HKDF-SHA256, AES-256-GCM). |
tier | Public key document: standard or attested. |
version | Public key document: format version. |
Errors are JSON with an error sentence, a stable code, retryable and request_id. Every reply carries an X-Request-Id header; 429 and 503 carry Retry-After (seconds). On billable POSTs, send an Idempotency-Key header (1-128 printable characters) to retry safely: the same key on the same API key and route replays the first successful reply for 24 hours, marked Idempotent-Replayed: true, and is charged once. Use a new key for each file: the same key with a different body is refused (422, idempotency_key_reused).
| Status | When |
|---|---|
| 400 | Bad request: no file in the expected field, empty upload, undecodable media, bad JSON, or a sealed request with a stale timestamp (server_time tells the right clock). |
| 401 | Missing API key (send X-API-Key), expired site token, or not signed in (/account). |
| 402 | The key's quota is used up. |
| 403 | Invalid, revoked or expired API key. |
| 409 | Sealed request made to a rotated key (reseal with the returned pubkey), or the account / key limit already exists. |
| 410 | heatmap_token expired (30 minutes) — call /analyze again. |
| 413 | File too large (image 40 MB, upload 200 MB) or over the image pixel limit. |
| 415 | Unsupported file type. |
| 422 | The link is not a fetchable http(s) URL, or the platform refused the fetch. |
| 429 | Rate limit (free keys 30 requests/min) — retry after a minute. |
| 500 | The analysis failed on our side; the file is discarded. Retry once, then contact support. |
| 503 | Modality under maintenance (status: under_maintenance), the daily capacity is reached, the server is busy, or a model is still warming up after idle. Retry later. |
| 504 | A link check passed its 90 s deadline: code timeout, retryable. Retry, or upload the file directly. |
heatmap_token is valid for 30 minutes.Plain requests are protected by TLS only. To seal the file and the reply end to end against every
proxy on the path, fetch GET /pubkey and send an HPKE envelope (TSE2: X25519, HKDF-SHA256,
AES-256-GCM) with the X-Tayanch-Nonce header. The reference Python client,
secure_client.py, does this for you; ask hello@tayanch.com
for it. A sealed request that fails before opening answers 400 with server_time or 409 with
the current pubkey.
Routes are stable. Reply keys are only added; a key is renamed or removed only after at least 90 days' notice in this changelog and by e-mail to key holders.
p_ai / ai_probability are null and the score, window
aggregates, per-window reads and the failed release gate moved under advisory
(thr_ai, thr_real, pooled, w_*, windows,
starts_s left those replies; calibrated audio replies keep them). /health
reports audio as preview in that state.config_version; this reference,
/openapi.json and /analyze/audio documented.mix added to uncertain replies; account routes
(/auth/register, /auth/login, /account).