CROP · BLACK & WHITE · PIXELATE FACES · STRIP ALL METADATA
One request per minute is free — no key, no signup, no card. Send image bytes, get sanitized image bytes back.
curl -X POST http://localhost:8787/v1/sanitize \
-H 'Content-Type: application/json' \
-d "{\"image\":\"$(base64 -i photo.jpg)\"}" \
--output sanitized.jpg
That call pixelates every detected face and strips all metadata. Multipart works too:
curl -X POST http://localhost:8787/v1/sanitize \
-F image=@photo.jpg \
-F 'ops={"grayscale":true,"pixelate":{"strength":5}}' \
--output sanitized.jpg
const res = await fetch('http://localhost:8787/v1/sanitize', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
image: base64Bytes,
ops: {
grayscale: true,
pixelate: { faces: true, regions: [{ x: 40, y: 120, w: 220, h: 80 }], strength: 4 },
output: { format: 'webp', quality: 88 },
response: 'json',
},
}),
})
const { image, report } = await res.json()
console.log(`${report.facesDetected} faces pixelated, metadata stripped`)
import base64, requests
with open("photo.jpg", "rb") as f:
payload = {"image": base64.b64encode(f.read()).decode(), "ops": {"grayscale": True}}
r = requests.post("http://localhost:8787/v1/sanitize", json=payload)
r.raise_for_status()
open("sanitized.jpg", "wb").write(r.content)
1 req / minute
No API key. No account. Hard-capped — a second call inside the same minute returns 429.
$0.004 / call
60 req/minute. The first call each minute is still free; the rest are metered.
$0.002 / call
600 req/minute, self-host and white-label options, invoiced.
One call per rolling 60-second window is always free — on every plan. Every additional
call inside that same window is billable at your plan's rate. Live numbers:
GET /v1/pricing. Your running total:
GET /v1/usage.
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Requests permitted in the current window. |
X-RateLimit-Remaining | Requests still available in this window. |
X-RateLimit-Reset | Unix seconds when the window resets. |
X-Sterilizer-Billable | true if this call was charged. |
X-Sterilizer-Period-Cost-Cents | Month-to-date cost for your key. |
X-Sterilizer-Retention | Always none. |
Authenticate by sending Authorization: Bearer <api-key>. Keys are
issued by the operator via POST /v1/keys with an admin token.
The main call. Accepts application/json (base64 image +
ops) or multipart/form-data (file field image,
text field ops). Returns image bytes, or a JSON envelope when
ops.response is "json":
{
"image": "<base64>",
"content_type": "image/jpeg",
"report": {
"width": 1010, "height": 997, "bytes": 68231,
"facesDetected": 1, "regionsPixelated": 2,
"grayscale": true, "metadataStripped": true,
"sourceMetadataFound": ["exif", "icc"], "durationMs": 412
}
}
Face bounding boxes only — the image is neither modified nor returned.
{"width":1010,"height":997,"count":1,
"faces":[{"x":381,"y":66,"w":238,"h":277,"confidence":0.9998}]}
Read-only report of what the image is carrying — exactly what sanitize would destroy.
{"format":"jpeg","width":2687,"height":3356,"bytes":1663552,
"metadata_blocks_present":["exif","icc"],"has_gps":true}
Calls, billable calls and cost for the current UTC month.
/v1/tools.json returns function-calling definitions in both OpenAI and
Anthropic shapes, ready to register with an LLM.
{
"crop": { "x": 0, "y": 0, "width": 800, "height": 600 },
"grayscale": false,
"pixelate": {
"faces": true,
"regions": [{ "x": 10, "y": 20, "w": 120, "h": 60 }],
"strength": 3,
"confidence": 0.6,
"padding": 1.5
},
"output": { "format": "jpeg", "quality": 92, "maxWidth": 2000 },
"response": "binary"
}
| Field | Default | Notes |
|---|---|---|
crop | — | Pixels, origin top-left, applied after EXIF auto-rotation. |
grayscale | false | Black and white conversion. |
pixelate.faces | true | Detect and pixelate every face found. |
pixelate.regions | [] | Up to 200 extra rectangles: plates, badges, screens, documents. |
pixelate.strength | 3 | 1–12. Block size as a percentage of the shortest edge. |
pixelate.confidence | 0.6 | 0.05–0.99. Lower catches more faces and more false positives. |
pixelate.padding | 1.5 | 1–3. Expansion around each detected face. |
output.format | jpeg | jpeg, png or webp. |
output.quality | 92 | 30–100. Ignored for PNG. |
output.maxWidth | — | Downscale cap; never enlarges. |
response | binary | json returns base64 plus the purge report. |
EXIF auto-rotate → crop → grayscale → resize → pixelate → re-encode. This means
pixelate.regions coordinates refer to the image after crop and
resize. If you crop, re-run /v1/detect on the cropped bytes rather than
reusing boxes from the original.
Every error is JSON with a stable error code and a human message.
| Status | Code | Meaning |
|---|---|---|
| 400 | bad_request | Malformed body, bad base64, or missing image. |
| 400 | invalid_ops | Validation failed; issues[] names the field. |
| 401 | invalid_api_key | Unknown or revoked key. |
| 402 | quota_exceeded | Monthly billable cap reached. |
| 422 | unprocessable_image | Bytes are not a decodable image. |
| 429 | rate_limit_exceeded | Includes retry_after_seconds. |
Retry only 429 (after the stated wait) and genuine 5xx. A 4xx will not become valid on retry.
image_url is deliberately unsupported: the API never fetches remote images.An HTTP API means the image leaves the user's device; that is unavoidable. The STERILIZER web app performs the same operations entirely client-side with no upload at all. Use this API for automation and server-to-server work; point humans at the browser tool.
facesDetected rather than promising full coverage.regions.