PurifyAI

Scrub a file

Removes metadata from a PNG or JPEG and returns the rewritten file.

POST/api/v1/scrub

Request

Content type must be multipart/form-data. Maximum body size is 25 MB; larger uploads are rejected before being read into memory.

filefileREQUIRED
The PNG or JPEG to scrub. Other formats return 415 — the endpoint will not guess at a container it cannot safely rewrite.
modestringOPTIONAL
all (default) removes every strippable segment. ai removes AI and provenance metadata only, keeping camera EXIF. May also be given as a ?mode= query parameter.

Modes

mode=all — strips EXIF, IPTC, XMP, PNG text chunks and C2PA manifests. Use this when publishing anywhere you do not control.

mode=ai — strips C2PA manifests, generator tags and SynthID-style markers, but preserves camera EXIF. Use this when provenance must go but shot data is worth keeping.selective is accepted as an alias.

ICC profiles survive both modesColour profiles are never stripped, so a scrubbed file renders identically to the original.

Response

On success the body is the cleaned file bytes — not JSON. The report is in the headers. Every failure, by contrast, returns JSON, so branch on the response content type rather than assuming.

200 OK
HTTP/1.1 200 OK
Content-Type: image/png
Content-Disposition: attachment; filename="photo_scrubbed.png"
Cache-Control: no-store
X-PurifyAI-Tags-Removed: 4
X-PurifyAI-Format: png
X-PurifyAI-Bytes-Saved: 18244
X-PurifyAI-Usage-Count: 12
X-RateLimit-Remaining: 118
<binary image data>
X-PurifyAI-Tags-RemovedintegerOPTIONAL
How many metadata tags were found and stripped. Zero is a success — the file simply carried nothing.
X-PurifyAI-FormatstringOPTIONAL
Detected container: png or jpeg. Derived from the file contents, not the upload filename or its declared type.
X-PurifyAI-Bytes-SavedintegerOPTIONAL
Size reduction in bytes. Large values usually mean an embedded thumbnail or colour-managed manifest was removed.
X-PurifyAI-Usage-CountintegerOPTIONAL
Lifetime number of calls made with this key. Useful for spotting a leaked key being used elsewhere.
X-RateLimit-RemainingintegerOPTIONAL
Requests left in the current 60-second window for this key.
The output filename is sanitisedContent-Disposition is built from your upload filename with everything outside A–Z a–z 0–9 . _ - replaced. An uploaded name is attacker-controlled, and a newline or quote in a response header is header injection.

Status codes

200
OK
The rewritten file. Check X-PurifyAI-Tags-Removed for what changed.
400
bad_request
Body was not multipart form data, or the file field was absent.
401
unauthorized
Key missing, unknown, revoked or on a disabled account. See Authentication.
403
plan
Valid key, but the account is not on Agency.
413
payload_too_large
Over 25 MB.
415
unsupported_format
Not a PNG or JPEG. The body includes the detected format and the tagsFound count, so you can still report what was in the file.
429
rate_limited
Per-key or per-IP rate limit, or the monthly quota. Honour Retry-After; see Rate limits.
503
not_configured
The service is not accepting API traffic. Retry with backoff.

Discovery

A GET on the same path returns a small JSON description of the endpoint and needs no key — handy for confirming a deployment is wired up.

GET /api/v1/scrub
{
"endpoint": "/api/v1/scrub",
"method": "POST",
"auth": "Authorization: Bearer pk_live_…",
"body": "multipart/form-data field \"file\"",
"maxFileSize": "25MB",
"modes": ["all", "ai"],
"docs": "/docs"
}
PreviousAuthenticationNext GET /v1/health