API Documentation
Check images, videos and music for AI from your own apps with our simple REST API.
Base URL: https://api.isitai.com
Quick Start
Get started with the Is It AI? API in three steps.
Get your API key
Create an account, then create a key on your API keys page: the key is your bearer token. Every account can create an API key; checks use your credits.
Make a request
Send an image URL to the detection endpoint.
# Detect an image by URL
curl -X POST "https://api.isitai.com/detect-img?image_url=https://example.com/image.jpg" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "accept: application/json"
Get results
Receive a detailed AI detection verdict with confidence scores.
{
"predicted_label": "AI",
"probability": 0.92,
"potential_method": "midjourney",
"method_probability": 0.85,
"NSFW": "No",
"NSFW_probability": 0.02
}
Authentication
The API uses JWT bearer tokens. Authenticate with your email and API secret to get a token, then include it in the Authorization header of every request.
Step 1: Get your API secret
Your API secret is available on your API keys page. This is different from your account password. It is used only to sign in to the API.
Step 2: Request an access token
POST your account email (as username) and API secret as JSON to the /login endpoint to receive a JWT access token.
curl -X POST https://api.isitai.com/login \
-H "Content-Type: application/json" \
-d '{"username": "you@example.com", "password": "YOUR_API_SECRET"}'
{
"access_token": "eyJhbGciOiJIUzI1NiIs..."
}
Step 3: Use the token
Include the token in the Authorization header as a Bearer token on all subsequent requests.
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
/login again. You can also revoke a token early using the /revoke-token endpoint. /login allows 5 requests a minute.
Endpoint Reference
All endpoints use the base URL https://api.isitai.com.
POST /login
Authenticate and receive a JWT access token.
Headers
| Header | Value |
|---|---|
Content-Type | application/json |
JSON Body
| Parameter | Type | Required | Description |
|---|---|---|---|
username | string | Yes | Your account email address |
password | string | Yes | Your API secret (from the API keys page) |
Response
{
"access_token": "eyJhbGciOiJIUzI1NiIs..."
}
POST /detect-img
Analyze an image for AI-generated content. Supports two modes: image URL (query parameter) or file upload (multipart form). One input per request: a file or a URL, never both (400).
Headers
| Header | Value |
|---|---|
Authorization | Bearer YOUR_ACCESS_TOKEN |
accept | application/json |
Option A: Image URL
| Parameter | In | Type | Description |
|---|---|---|---|
image_url | query | string | URL of the image to analyze |
curl -X POST "https://api.isitai.com/detect-img?image_url=https://example.com/photo.jpg" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "accept: application/json"
Option B: File Upload
| Parameter | In | Type | Description |
|---|---|---|---|
file | body (multipart) | file | Image file to analyze |
curl -X POST https://api.isitai.com/detect-img \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "accept: application/json" \
-F "file=@/path/to/image.jpg"
Response
{
"predicted_label": "AI",
"probability": 0.92,
"potential_method": "midjourney",
"method_probability": 0.85,
"NSFW": "No",
"NSFW_probability": 0.02
}
POST /detect-img/batch
Check up to 20 images in one request, 1 credit each. Upload them as files, or send image_urls as a comma-separated list. If one image cannot be read or downloaded, the whole request fails with 400 and nothing is charged. A failed image, or one you have no credits left for, is not charged. Match the results to your images by index.
| Parameter | In | Type | Description |
|---|---|---|---|
files | body (multipart) | file | Up to 20 images, each up to 10 MB (repeat the field) |
image_urls | query | string | Up to 20 image URLs, separated by commas |
curl -X POST https://api.isitai.com/detect-img/batch \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-F "files=@/path/to/first.jpg" \
-F "files=@/path/to/second.png"
{
"results": [
{ "index": 0, "success": true, "predicted_label": "AI", "probability": 0.92, ... },
{ "index": 1, "success": false, "error": "Monthly quota exhausted and no credits available", "code": "QUOTA_EXHAUSTED" }
],
"quota": { "monthly_limit": 200, "monthly_used": 200, "monthly_remaining": 0, "credits_remaining": 0 }
}
Face swap option (face_swap=true)
Adds the face swap check to an image check: did someone swap or fake a real person's face? The check uses 2 credits instead of 1. Only useful for images with people. Works the same on /detect-img/batch (2 credits per image).
| Parameter | In | Type | Description |
|---|---|---|---|
face_swap | query | boolean | true adds face_swap ("Yes" or "No") and face_swap_probability (0 to 1) to the answer. Default false. |
POST /detect-video
Checks a video shorter than 60 s (up to 50 MB; MP4, MOV, WebM, MKV or AVI) one frame every 2 seconds. Videos are analyzed one at a time, so the check goes into a queue: the answer is the result, or 202 with a job_id to poll. Price: 6 credits for each started 10 s (12 with face_swap=true), counted in whole seconds, rounded down. You can have 3 videos queued or running at a time; a fourth gets 429 TOO_MANY_VIDEO_JOBS. Send a file or a video_url, never both (400).
| Parameter | In | Type | Description |
|---|---|---|---|
file | body (multipart) | file | The video, or use video_url |
video_url | query | string | A direct link to a video file (http or https) |
face_swap | query | boolean | Also run the face swap check (doubles the price) |
wait | query | integer | Seconds to wait for the result, 0 to 60 (default 30). Not done by then: 202 |
curl -X POST "https://api.isitai.com/detect-video?wait=30" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-F "file=@/path/to/clip.mp4"
Queued
{
"job_id": 48213,
"status": "queued",
"position": 2,
"duration_seconds": 23,
"poll_url": "/detect-video/48213",
"quota": { "credits_used": 18, ... }
}
The credits are taken when the job is queued and come back if it fails. Save the job_id at once.
GET /detect-video/{job_id}
Where a video check stands: 202 while it is queued or running (with its place in line), 200 with the result, 410 if the result was deleted (90 days after the check), or the stored error of a failed job (its credits were refunded). Poll every few seconds. You can read only your own jobs: any other job_id gets 404.
Result
{
"predicted_label": "AI",
"probability": 0.95,
"potential_method": "sora",
"NSFW": "No",
"frames_checked": 12,
"frames": [
{ "time_seconds": 0.0, "ai_probability": 0.08, "NSFW_probability": 0.01 },
{ "time_seconds": 2.0, "ai_probability": 0.95, "NSFW_probability": 0.01 },
...
],
"job_id": 48213,
"quota": { "credits_used": 18, ... }
}
The highest frame decides each verdict: one AI frame is enough to call the video AI-generated. With face_swap=true, the answer also has face_swap and face_swap_probability, and so does each frame.
Optional parts of a video result:
| Field | Type | Description |
|---|---|---|
audio | object | Only when the soundtrack is probably AI music (ai_music_probability over 0.5): predicted_label, ai_music_probability, music_verdict, potential_method, analyzed_seconds and chunks, as on /detect-audio. A hint, not a verdict: speech can set it off, and it never changes the video's verdict. No audio part does not mean the music is human-made |
verdict_source, provenance | string, object | A video can carry Content Credentials too (MP4, MOV and AVI are read). verdict_source is "c2pa" when a trusted signed label said it is AI-generated: the label decided, and every frame has ai_probability: null (no AI score). NSFW and face swap answers are still there |
metadata | object | The unsigned metadata label, the same as for images |
GET /pricing
The price of every kind of check, in credits. No token needed. audio is the music check: 1 credit per started minute, at most 5.
{
"image": { "credits": 1, "with_face_swap": 2 },
"video": {
"credits_per_started_10_seconds": 6,
"face_swap_factor": 2,
"max_seconds": 60,
"max_megabytes": 50,
"examples": { "10s": 6, "30s": 18, "59s": 36, "30s_with_face_swap": 36 }
},
"audio": {
"credits_per_started_minute": 1,
"max_megabytes": 20,
"examples": { "30s": 1, "3m20s": 4 },
...
},
"notes": "Lengths are counted in whole seconds, rounded down. A failed detection does not use credits."
}
POST /detect-audio
Checks whether a music track was made with AI, with a score for every 10 seconds and separate scores for vocals and instruments. Made for music: not for speech, podcasts or voice clones. Send a file or an audio_url, never both (400). Files: MP3, WAV, FLAC, OGG, OPUS, M4A or MP4 audio (no WebM), 10 s or longer, up to 20 MB. Only the first 300 s are checked. Price: 1 credit for each started minute, at most 5, counted in whole seconds, rounded down. The answer comes back in the same request.
| Parameter | In | Type | Description |
|---|---|---|---|
file | body (multipart) | file | The track, or use audio_url |
audio_url | query | string | A direct link to an audio file (http or https) |
curl -X POST https://api.isitai.com/detect-audio \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-F "file=@/path/to/song.mp3"
Result
{
"predicted_label": "AI",
"probability": 0.92,
"ai_music_probability": 0.92,
"music_verdict": "pure_ai",
"potential_method": "suno",
"vocal_ai_probability": 0.75,
"instrumental_ai_probability": 0.94,
"analyzed_seconds": 200,
"duration_seconds": 200,
"chunks": [
{ "start_seconds": 0, "ai_music_probability": 0.88, "pure_ai_probability": 0.81 },
{ "start_seconds": 10, "ai_music_probability": 0.91, "pure_ai_probability": 0.84 },
...
],
"quota": { "credits_used": 4, ... }
}
| Field | Type | Description |
|---|---|---|
predicted_label, probability | string, number | "AI" or "Human", and the confidence from 0 to 1 |
ai_music_probability | number | The chance the music is AI-made, 0 to 1 |
music_verdict | string or null | "pure_ai" (straight from an AI tool), "processed_ai" (made with AI, then edited), "human", or null when it cannot tell |
potential_method | string | The likely tool, such as "suno", "udio", "mureka" or "elevenlabs_music". Only when the label is "AI" |
vocal_ai_probability, instrumental_ai_probability | number or null | Separate scores for the voice and the music, 0 to 1. null when the track could not be split: that is not a "human" score |
analyzed_seconds, duration_seconds | number | How much was checked (at most 300) and how long the track is |
chunks | array | One score per 10 s: start_seconds and ai_music_probability (0 to 1). Can be empty |
quota | object | The same as for images: credits_used (this check), source and what is left |
verdict_source, provenance | string, object | Only for a file with Content Credentials (MP3, WAV, FLAC and M4A are read): the same as for images |
metadata | object | Only when the file's own tags name AI: the same unsigned label as for images. Never changes the verdict |
Errors
| Status | When |
|---|---|
400 | A file and an audio_url together, a file that cannot be read, a format that is not supported, or a track shorter than 10 s. Not charged |
403 | INSUFFICIENT_CREDITS (with credits_required): not enough credits for this track |
413 | The file is over 20 MB |
429 | RATE_LIMITED: over 120 check requests a minute for your account. Wait for Retry-After |
502 | The music service failed. Your credits come back |
503 | SERVICE_BUSY: try again after Retry-After. Not charged |
GET /consumption/
Retrieve the total number of credits your account has used, all time (website and API together).
Headers
| Header | Value |
|---|---|
Authorization | Bearer YOUR_ACCESS_TOKEN |
curl https://api.isitai.com/consumption/ \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response
{
"username": "you@example.com",
"consumption": 142
}
POST /revoke-token
Revoke the token you send. After revocation, you will need a new key or a new token from /login.
Headers
| Header | Value |
|---|---|
Authorization | Bearer YOUR_ACCESS_TOKEN |
curl -X POST https://api.isitai.com/revoke-token \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response
{
"message": "Token revoked successfully!"
}
Response Fields
The detection endpoint (/detect-img) returns the following fields.
| Field | Type | Description |
|---|---|---|
predicted_label |
string | "AI" or "Human" - the detection verdict |
probability |
number | Confidence score from 0 to 1 (e.g. 0.92 = 92% confidence) |
potential_method |
string | Most likely AI generator (e.g. "midjourney", "stable_diffusion", "dalle"). Only when the label is "AI" and a generator was named. |
method_probability |
number | Confidence score for the generator (0 to 1). Only with potential_method. |
NSFW |
string | "Yes" or "No" - whether the image contains NSFW content |
NSFW_probability |
number | Confidence score for the NSFW classification (0 to 1) |
face_swap, face_swap_probability |
string, number | With face_swap=true only: "Yes" or "No", and its chance from 0 to 1 |
verdict_source |
string | What decided the verdict. "c2pa": a trusted Content Credentials label says the file was made or edited with AI. "metadata": an unsigned metadata label says ai_generated or ai_edited, or a software or encoder tag names a known AI tool (ai_tool, for example Suno in an MP3 or Sora in an MP4; only a fixed list of distinctive names counts, never free text). In both cases predicted_label is "AI" and probability is 0.99, which comes from the label, not a model score. "model" (or absent): our model decided. No label leaves the verdict to the model. Images, videos and music follow the same rules: a video decided by its label has ai_probability: null on every frame, and a song keeps its music scores, which can disagree with the label. So a real photo with one object erased by a phone's AI eraser reads "AI" when its label says so: show your users why. |
provenance |
object | Only for a file with Content Credentials: c2pa (always true), trusted, signed_by, generator, source_type, ai_generated, ai_edited. No label never means "human-made". For an AI edit, potential_method is the edit's tool as the file names it (for example "photo_assist"). |
metadata |
object | Only when the file's own metadata (such as XMP or EXIF) names AI. Not signed, so anyone can add or remove it. When it says ai_generated or ai_edited, or names a known AI tool (ai_tool), it decides the verdict (verdict_source "metadata"): says ("ai_generated", "ai_edited" or "ai_tool"), generator (the tool it names, if any) and found_in (where in the file it was found) |
quota |
object | credits_used (this check), source ("subscription", "credit" or "mixed": plan credits are used first, then bought credits), monthly_limit, monthly_used, monthly_remaining and credits_remaining (bought credits left) |
Error Handling
The API uses standard HTTP status codes. Errors include a JSON body with a message.
| Status | Meaning | Common Cause |
|---|---|---|
200 | Success | Request completed successfully |
202 | Accepted | A video is queued or running: poll its job_id |
400 | Bad Request | The file could not be read, the link failed, a parameter is missing, or you sent both a file and a URL (one input per request), or the video is 60 s or longer, or the track is shorter than 10 s. Not charged |
401 | Unauthorized | Missing, invalid, expired or revoked token |
403 | Forbidden | QUOTA_EXHAUSTED: no plan credits or bought credits left. INSUFFICIENT_CREDITS (with credits_required): not enough credits for a face swap check, a video or a music track |
404 | Not Found | No video job with this job_id for your account |
410 | Gone | A video result deleted 90 days after the check |
413 | Payload Too Large | The file is over its limit (images 10 MB, videos 50 MB, music 20 MB) |
422 | Unprocessable Entity | A parameter is not valid, for example wait=90 |
429 | Too Many Requests | RATE_LIMITED: over 120 check requests a minute for your account (or 5 a minute on /login). Wait for Retry-After and retry |
429 | Too Many Requests | TOO_MANY_VIDEO_JOBS: you already have 3 videos queued or running. Wait until one is done |
500 | Internal Server Error | Unexpected error on our side - retry or contact support |
502 | Bad Gateway | The detection service is down for a moment. Try again shortly. Not charged |
503 | Service Busy | SERVICE_BUSY: the detection service is busy. Try again after Retry-After (10 s). Not charged |
Error Response Format
{
"detail": "Monthly quota exhausted and no credits available",
"code": "QUOTA_EXHAUSTED"
}
Every error has a detail message. Some also have a code you can check in your app, such as QUOTA_EXHAUSTED, RATE_LIMITED or SERVICE_BUSY.
Supported Formats & Limits
Image Types
- JPEG / JPG
- PNG
- WebP
- GIF (first frame)
- AVIF, BMP and TIFF
Video Types
- MP4 and MOV
- WebM and MKV
- AVI
Music Types
- MP3, WAV and FLAC
- OGG and OPUS
- M4A and MP4 audio (no WebM)
Limits
- Max image size: 10 MB
- Videos: shorter than 60 s, up to 50 MB
- Music: 10 s or longer, up to 20 MB; the first 300 s are checked
- One input per request: a file or a URL, never both
- Batch: up to 20 images per request
- Up to 3 videos queued or running at a time
- Up to 120 check requests a minute per account
- Credits from your plan first, then bought credits
Best Practices
- Use images at least 256×256 px for best accuracy
- Avoid heavily compressed or cropped images
- Re-authenticate when you receive a 401 response
Code Examples
Full authentication + detection flow in popular languages.
Detect by Image URL
# 1. Get access token
TOKEN=$(curl -s -X POST https://api.isitai.com/login \
-H "Content-Type: application/json" \
-d '{"username": "you@example.com", "password": "YOUR_API_SECRET"}' \
| jq -r '.access_token')
# 2. Detect image
curl -X POST "https://api.isitai.com/detect-img?image_url=https://example.com/photo.jpg" \
-H "Authorization: Bearer $TOKEN" \
-H "accept: application/json"
import requests
# 1. Authenticate
auth = requests.post("https://api.isitai.com/login", json={
"username": "you@example.com",
"password": "YOUR_API_SECRET"
})
token = auth.json()["access_token"]
# 2. Detect by URL
response = requests.post(
"https://api.isitai.com/detect-img",
params={"image_url": "https://example.com/photo.jpg"},
headers={
"Authorization": f"Bearer {token}",
"accept": "application/json"
}
)
result = response.json()
print(f"Verdict: {result['predicted_label']} ({result['probability']:.0%} confidence)")
// 1. Authenticate
const authRes = await fetch("https://api.isitai.com/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username: "you@example.com", password: "YOUR_API_SECRET" })
});
const { access_token } = await authRes.json();
// 2. Detect by URL
const imageUrl = encodeURIComponent("https://example.com/photo.jpg");
const res = await fetch(
`https://api.isitai.com/detect-img?image_url=${imageUrl}`,
{
method: "POST",
headers: {
"Authorization": `Bearer ${access_token}`,
"accept": "application/json"
}
}
);
const result = await res.json();
console.log(`Verdict: ${result.predicted_label} (${(result.probability * 100).toFixed(0)}% confidence)`);
Detect by File Upload
curl -X POST https://api.isitai.com/detect-img \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "accept: application/json" \
-F "file=@/path/to/image.jpg"
import requests
token = "YOUR_ACCESS_TOKEN"
with open("image.jpg", "rb") as f:
response = requests.post(
"https://api.isitai.com/detect-img",
headers={
"Authorization": f"Bearer {token}",
"accept": "application/json"
},
files={"file": f}
)
print(response.json())
import { readFileSync } from "fs";
const token = "YOUR_ACCESS_TOKEN";
const formData = new FormData();
formData.append("file", new Blob([readFileSync("image.jpg")]), "image.jpg");
const res = await fetch("https://api.isitai.com/detect-img", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"accept": "application/json"
},
body: formData
});
console.log(await res.json());
API Pricing
Every account can create an API key; checks use your credits, the same as on the website: 1 per image (2 with the face swap check), 6 per started 10 s of video (12 with the face swap check), 1 per started minute of music (at most 5). Your monthly plan credits are used first, then bought credits.
Using Claude Code or Cursor? Connect Is It AI? to your AI assistant with the same API key.
View Plans & PricingStart Building with Is It AI?
Every account can create an API key; checks use your credits. Create a key and start checking images, videos and music from your own apps.