●Docs · API v1 · Early access
API reference.
One endpoint. Send an image by file or URL and get back a verdict, Likely AI or Likely real, with a confidence score and provenance signals.
# Scan an image by URL curl https://witness.vision/api/v1/scan \ -H "Authorization: Bearer $WITNESS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url": "https://…/claim_photo_48213.jpg"}' # Response { "id": "6f1c2b0e…9b31", "verdict": "likely_ai", "label": "Likely AI", "score": 0.93, "confidence": 0.93, "face_detected": false, "signals": { "c2pa": false, "synthid": false, "exif": { "present": false } } }
●Authentication
Bearer keys.
Every request needs an API key in the Authorization header. Keys start with wk_live_. Keep them server-side: never ship a key in browser or mobile code. To get a key, contact us.
Authorization: Bearer wk_live_...
●Endpoint
POST /scan
Upload a file as multipart form data, in a field named file:
curl https://witness.vision/api/v1/scan \ -H "Authorization: Bearer $WITNESS_API_KEY" \ -F "file=@photo.jpg"
Or send a public URL as JSON. Links to posts on social platforms work too; we resolve the media for you.
curl https://witness.vision/api/v1/scan \
-H "Authorization: Bearer $WITNESS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/photo.jpg"}'
Accepted: JPEG, PNG, WebP and GIF up to 20 MB. Direct uploads are capped at about 4 MB by our hosting; send larger images by URL. Video and audio are coming.
●Response
What comes back.
{
"id": "6f1c2b0e-4a8d-4b55-9d1e-2f0c8a7d9b31",
"object": "scan",
"modality": "image",
"verdict": "likely_ai",
"label": "Likely AI",
"ai_type": "ai_generated",
"score": 0.9731,
"confidence": 0.9731,
"face_detected": true,
"explanation": "Witness found strong generation signatures.",
"signals": {
"c2pa": false,
"synthid": false,
"exif": { "present": false, "camera_make": null, "camera_model": null, "software": null }
},
"created_at": "2026-11-12T15:04:05.000Z"
}
| Field | Meaning |
|---|---|
| verdict | likely_ai or likely_real. |
| label | The same verdict as display text: Likely AI or Likely real. |
| ai_type | ai_generated or face_swap when the verdict is Likely AI, otherwise null. |
| score | AI likelihood from 0 to 1. Higher means more likely AI. |
| confidence | How confident the verdict is, 0 to 1. |
| face_detected | Whether a face was found. Face-swap checks run when one is. |
| signals.c2pa | Content Credentials present on the file. |
| signals.synthid | An invisible AI watermark was found. |
| signals.exif | Camera metadata, when present. |
Results are probabilistic. Use them as one input to a decision, not the whole decision.
●Endpoint
GET /usage
Check how much of your quota is left.
curl https://witness.vision/api/v1/usage -H "Authorization: Bearer $WITNESS_API_KEY"
{
"object": "usage",
"key": { "name": "my-app", "prefix": "wk_live_Ab3x", "expires_at": null },
"window": "rolling_30_days",
"images": { "used": 42, "limit": 500, "remaining": 458 },
"rate_limit_per_minute": 10
}●Limits
Per key, per minute and per month.
Each key has a per-minute limit and a scan quota over a rolling 30 days. Every /scan response carries these headers:
| Header | Meaning |
|---|---|
| X-RateLimit-Limit | Scans allowed per minute. |
| X-RateLimit-Remaining | Scans left this minute. |
| X-Quota-Limit | Scans allowed per 30 days. |
| X-Quota-Remaining | Scans left in the window. |
Failed scans do not count against your quota.
●Errors
When something goes wrong.
Errors return JSON in one shape:
{ "error": { "code": "quota_exceeded", "message": "..." } }
| HTTP | code | Meaning |
|---|---|---|
| 400 | missing_file · invalid_url · invalid_media | No file sent, a bad URL, or an image that could not be decoded. |
| 401 | invalid_key · key_revoked · key_expired | The key is missing, unknown, revoked or past its expiry. |
| 413 | too_large | Image over 20 MB. Use a URL for anything over about 4 MB. |
| 415 | unsupported_media | Not JPEG, PNG, WebP or GIF, or the wrong request content type. |
| 429 | rate_limited · quota_exceeded | Limit reached. See Retry-After and X-Quota-Remaining. |
| 502 | fetch_failed · upstream_error | We could not fetch the URL, or detection failed. Safe to retry. |
| 503 | capacity | The API is temporarily at capacity. |
| 504 | upstream_timeout | Detection took too long. Safe to retry. |
●Examples
Python and Node.
Python
import os, requests
API = "https://witness.vision/api/v1"
headers = {"Authorization": "Bearer " + os.environ["WITNESS_API_KEY"]}
with open("photo.jpg", "rb") as f:
r = requests.post(API + "/scan", headers=headers, files={"file": f}, timeout=60)
r.raise_for_status()
scan = r.json()
print(scan["verdict"], scan["confidence"])
Node.js
const API = "https://witness.vision/api/v1";
const res = await fetch(API + "/scan", {
method: "POST",
headers: {
Authorization: "Bearer " + process.env.WITNESS_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({ url: "https://example.com/photo.jpg" }),
});
if (!res.ok) throw new Error((await res.json()).error.message);
const scan = await res.json();
console.log(scan.verdict, scan.confidence);●MCP server
Scan from your AI chat.
Connect Witness to Claude, ChatGPT, Cursor or your own agent and check images in the conversation. Tools: scan_image (by URL or base64) and check_usage.
Sign in (recommended)
Add this URL as a custom connector. Your app opens a Witness sign-in page; log in and allow the connection. Scans count toward your Witness plan, the same as on the website.
https://witness.vision/api/mcp
In Claude: Settings, Connectors, Add custom connector. In ChatGPT: Settings, Connectors, Create. In Claude Code:
claude mcp add --transport http witness https://witness.vision/api/mcp
With an API key
For agents and servers with no one to sign in. Scans count toward the key's quota.
{
"mcpServers": {
"witness": {
"url": "https://witness.vision/api/mcp",
"headers": { "Authorization": "Bearer wk_live_..." }
}
}
}
Images uploaded into the chat
Chat apps don’t pass uploaded files to connectors yet, so Witness can’t see them. Paste a link to the image or post instead, or upload the file at witness.vision/scan.
Connection details
OAuth 2.1 with PKCE (S256) and dynamic client registration. Discovery: /.well-known/oauth-protected-resource and /.well-known/oauth-authorization-server. Access tokens last one hour; refresh tokens rotate.