Introduction
image-guard is a REST API that detects copyright-protected content (characters, brand logos, trademarks) and NSFW content in images. Results are streamed in real-time via Server-Sent Events (SSE).
https://image-guard.readingtester.com
Authentication
All requests to /api/* require an API key passed as a request header:
| Header | Value |
|---|---|
X-API-Key | Your API key |
Requests without a valid key return 401 Unauthorized.
How it works
Detection runs in two stages:
| Stage | Model | Purpose | Avg time |
|---|---|---|---|
| Stage 1 โ Vision | GPT-4.1 (1024px, detail:high) | Analyze image for copyright content, NSFW, and generate an edit prompt | ~1s |
| Stage 2 โ Edit check | OpenAI gpt-image-1 + Gemini 2.5-flash (parallel) | Attempt image edit โ content policy block = copyright/NSFW confirmed | ~30s |
Early exit: if Stage 1 detects copyright or NSFW, Stage 2 is skipped. The full pipeline only runs for images that pass Stage 1.
GET /health
Response
"status": "ok", "version": "1.1.0"
POST /api/check
Request
| Field | Type | Required | Description |
|---|---|---|---|
image |
file | required | Image file โ multipart/form-data. JPEG, PNG, WEBP. Max 20MB. |
| Header | Required | Value |
|---|---|---|
X-API-Key | required | Your API key |
Content-Type | auto | multipart/form-data (set by HTTP client) |
Accept | optional | text/event-stream |
Response
Content-Type: text/event-stream. Each event is a JSON object prefixed with data: .
SSE Events
Events arrive in order. The final event is always result.
{
"type": "vision",
"copyright": true, // boolean โ copyright content detected
"identified": "Mickey Mouse (Disney)", // string โ what was found, or null
"nsfw": false, // boolean โ NSFW content detected
"nsfwReason": null, // string | null
"editPrompt": null // string | null โ only set if image is safe
}
{
"type": "provider_result",
"provider": "openai", // "openai" | "gemini"
"blocked": false // true = content policy blocked = flagged
}
{
"type": "result",
"safe": false, // true = safe to use, false = flagged
"reason": "copyright", // "copyright" | "nsfw" | "provider" | null
"identified": "Mickey Mouse (Disney)",
"earlyExit": true // true = Stage 2 was skipped
}
Complete flow โ FLAGGED (early exit)
data: {"type":"vision","copyright":true,"identified":"Mickey Mouse (Disney)","nsfw":false,"nsfwReason":null,"editPrompt":null}
data: {"type":"result","safe":false,"reason":"copyright","identified":"Mickey Mouse (Disney)","earlyExit":true}
Complete flow โ SAFE (full pipeline)
data: {"type":"vision","copyright":false,"identified":null,"nsfw":false,"nsfwReason":null,"editPrompt":"Replace the dog with a cat"}
data: {"type":"provider_result","provider":"openai","blocked":false}
data: {"type":"provider_result","provider":"gemini","blocked":false}
data: {"type":"result","safe":true,"reason":null,"identified":null,"earlyExit":false}
Error Responses
| HTTP Status | Condition | Body |
|---|---|---|
400 | No image uploaded | {"error": "No image provided..."} |
401 | Missing or invalid API key | {"error": "Unauthorized..."} |
413 | File exceeds 20MB | {"error": "File too large"} |
500 | Internal error | SSE {"type":"error","message":"..."} |
Code Examples
cURL
curl -X POST https://image-guard.readingtester.com/api/check \ -H "X-API-Key: YOUR_API_KEY" \ -F "image=@/path/to/image.jpg" \ --no-buffer
JavaScript (Node.js / fetch)
import fs from "fs";
const form = new FormData();
form.append("image", new Blob([fs.readFileSync("image.jpg")]), "image.jpg");
const res = await fetch("https://image-guard.readingtester.com/api/check", {
method: "POST",
headers: { "X-API-Key": "YOUR_API_KEY" },
body: form,
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const lines = decoder.decode(value).split("\n");
for (const line of lines) {
if (line.startsWith("data: ")) {
const event = JSON.parse(line.slice(6));
console.log(event);
if (event.type === "result") {
console.log("Safe:", event.safe, "| Reason:", event.reason);
}
}
}
}
Python
import requests
with open("image.jpg", "rb") as f:
response = requests.post(
"https://image-guard.readingtester.com/api/check",
headers={"X-API-Key": "YOUR_API_KEY"},
files={"image": ("image.jpg", f, "image/jpeg")},
stream=True,
)
for line in response.iter_lines():
if line.startswith(b"data: "):
import json
event = json.loads(line[6:])
print(event)
if event["type"] == "result":
print("Safe:", event["safe"])
Limits & Notes
| Limit | Value |
|---|---|
| Max file size | 20 MB |
| Supported formats | JPEG, PNG, WEBP, GIF |
| Vision resolution | Resized to 1024px max (detail:high โ tiles image for accuracy) |
| Edit API resolution | Resized to 768px max |
| Detection threshold | Reliable down to ~4% image size (~40px); below 2% is below perceptual limit |
| Stage 2 timeout | 90s per provider |
| Response format | SSE stream (text/event-stream) |
Provider Cost Comparison
Estimated cost per image check (approx. 500 input + 150 output tokens):
| Provider | Model | Input $/MTok | Output $/MTok | Cost/Check | Cost/1K checks |
|---|---|---|---|---|---|
| openai | gpt-4.1 | $2.00 | $8.00 | ~$0.0022 | ~$2.20 |
| gemini | gemini-2.5-flash | $0.15 | $0.60 | ~$0.00017 | ~$0.17 |
| claude | claude-haiku-4-5 | $0.80 | $4.00 | ~$0.001 | ~$1.00 |
| grok | grok-4-0709 | $3.00 | $15.00 | ~$0.0042 | ~$4.20 |
Recommendation: Default providers=openai,gemini gives best speed+cost balance. GPT-4.1 is fastest (~1s) for primary detection; Gemini 2.5-flash is 13x cheaper for secondary confirmation.