Sterilizer API

V1 · ZERO RETENTION

CROP · BLACK & WHITE · PIXELATE FACES · STRIP ALL METADATA

01 Quickstart

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

JavaScript

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`)

Python

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)

02 Pricing & rate limits

Free

1 req / minute

No API key. No account. Hard-capped — a second call inside the same minute returns 429.

Pro

$0.004 / call

60 req/minute. The first call each minute is still free; the rest are metered.

Enterprise

$0.002 / call

600 req/minute, self-host and white-label options, invoiced.

The billing rule, precisely

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.

Headers on every response

HeaderMeaning
X-RateLimit-LimitRequests permitted in the current window.
X-RateLimit-RemainingRequests still available in this window.
X-RateLimit-ResetUnix seconds when the window resets.
X-Sterilizer-Billabletrue if this call was charged.
X-Sterilizer-Period-Cost-CentsMonth-to-date cost for your key.
X-Sterilizer-RetentionAlways none.

Authenticate by sending Authorization: Bearer <api-key>. Keys are issued by the operator via POST /v1/keys with an admin token.

03 Endpoints

POST/v1/sanitize METERED

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
  }
}
POST/v1/detect METERED

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}]}
POST/v1/inspect METERED

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}
GET/v1/usage AUTH REQUIRED

Calls, billable calls and cost for the current UTC month.

GET/v1/health FREE
GET/v1/pricing FREE
GET/openapi.json FREE
GET/v1/tools.json FREE

/v1/tools.json returns function-calling definitions in both OpenAI and Anthropic shapes, ready to register with an LLM.

04 Ops reference

{
  "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"
}
FieldDefaultNotes
cropPixels, origin top-left, applied after EXIF auto-rotation.
grayscalefalseBlack and white conversion.
pixelate.facestrueDetect and pixelate every face found.
pixelate.regions[]Up to 200 extra rectangles: plates, badges, screens, documents.
pixelate.strength31–12. Block size as a percentage of the shortest edge.
pixelate.confidence0.60.05–0.99. Lower catches more faces and more false positives.
pixelate.padding1.51–3. Expansion around each detected face.
output.formatjpegjpeg, png or webp.
output.quality9230–100. Ignored for PNG.
output.maxWidthDownscale cap; never enlarges.
responsebinaryjson returns base64 plus the purge report.

Order of operations

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.

05 Errors

Every error is JSON with a stable error code and a human message.

StatusCodeMeaning
400bad_requestMalformed body, bad base64, or missing image.
400invalid_opsValidation failed; issues[] names the field.
401invalid_api_keyUnknown or revoked key.
402quota_exceededMonthly billable cap reached.
422unprocessable_imageBytes are not a decodable image.
429rate_limit_exceededIncludes retry_after_seconds.

Retry only 429 (after the stated wait) and genuine 5xx. A 4xx will not become valid on retry.

06 Privacy & retention

What happens to an uploaded image

  • Decoded into memory, processed, encoded, returned — then freed.
  • Never written to disk. Never logged. Never used for training.
  • Only a numeric call counter persists, for billing.
  • Face detection runs on our own hardware — no third-party vision API.
  • image_url is deliberately unsupported: the API never fetches remote images.

The browser app is stricter — prefer it when you can

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.

Limits worth stating to your users