Images
POST /v1/image checks one image and answers with the decision. One image is one check, whatever the number of categories it is checked against.
Request
| Part | Where | Description |
|---|---|---|
| The image | Body | The raw bytes. JPEG, PNG, WebP or GIF (the first frame). The API recognises the format from the bytes. |
workflow | Query | The workflow to check against. default when absent. |
ref | Query | Your own id for the image, 1 to 128 characters, no control character. It is echoed in the answer and kept in the journal. |
url | Query | The address of the image, instead of a body. See below. |
Limits: at most 15 MB, 50 megapixels (12 on the free plan) and 16,384 pixels on a side.
curl -X POST "https://api.openshield.dev/v1/image?workflow=default&ref=avatar_4821" \
-H "Authorization: Bearer $OPENMOD_API_KEY" \
--data-binary @photo.jpg
import { readFile } from "node:fs/promises"
const response = await fetch("https://api.openshield.dev/v1/image?ref=avatar_4821", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.OPENMOD_API_KEY}` },
body: await readFile("photo.jpg"),
})
const answer = await response.json()
console.log(answer.summary.action)
Checking a public URL
Instead of sending the bytes, pass the address of the image and an empty body. The service downloads it from the public internet:
curl -X POST "https://api.openshield.dev/v1/image?url=https%3A%2F%2Fexample.com%2Fphoto.jpg" \
-H "Authorization: Bearer $OPENMOD_API_KEY"
The download is guarded: only http and https addresses of the public internet, at most 15 MB, 3 redirects and 10 seconds, and the answer must have Content-Type: image/*. A refused address gets 400 url_refused, with the reason in message.
What is checked
The categories of the workflow. The default workflow checks nudity, gore, weapons, hate symbols and self-harm (rejected at 0.8, sent to review at 0.5), drugs and minors (sent to review at 0.5), and the text and QR codes found in the image: links to adult, unsafe or gambling sites, and extremist text. Workflows explains how to change that.
Answer
The answer has the shape described here. For an image rejected for nudity:
{
"status": "success",
"workflow": "default",
"workflow_version": 1,
"summary": {
"action": "reject",
"reject_prob": 0.97,
"reject_reason": [
{
"id": "nudity",
"text": "Nudity",
"message": "This content contains nudity or sexual content.",
"score": 0.97,
"threshold": 0.8,
"source": "marqo_nsfw+owen_safety"
}
],
"review_reason": []
},
"scores": { "nudity": 0.97, "gore": 0.01, "weapon": 0.02 },
"time_ms": 212,
"request_id": "0192b7c4-5f3a-7c1e-9a52-3d8f1e6b0a47",
"ref": "avatar_4821"
}
Errors
| Status | error.type | When |
|---|---|---|
| 400 | bad_request | The body is empty and there is no url, or ref is refused. |
| 400 | corrupt_media | The file does not decode. |
| 400 | unknown_workflow | There is no workflow of that name. |
| 400 | url_refused | The url is not public, too large, too slow or not an image. |
| 413 | body_too_large | The body is over 15 MB. |
| 413 | image_too_large | The image has more than 50 megapixels (12 on the free plan), or a side over 16,384 pixels. |
| 415 | unsupported_media | The file is not JPEG, PNG, WebP or GIF. |
| 503 | workflow_unavailable | The workflow has no rule for images, or none this server can check. |
Authentication, rate limit and quota errors are in responses and errors.