How to moderate user uploaded images
Check the picture before it is published, not after a report. POST /v1/image takes a url, the bytes as base64 data, or a multipart file, so a picture does not have to be published before you find out whether it can be.
What the model sees in a picture
Nothing in the bytes of an image says what it shows, so for pictures the model is not an escalation, it is the step that sees. It scores six categories: sexual, violence, hate, spam (a price list or a phone number over a stock photo), scam and personal_data (an ID card, a bank statement, a screenshot of a private chat). Nudity on its own is not sexual content, and a news photograph is not gore.
Text in images: the oldest way past a text filter
The offer, the phone number and the wallet arrive as pixels because a word list cannot see them. The same reading transcribes the visible text, and it goes through the free text checks and the words in your own policy, so a rule written for comments applies to screenshots too. Each reason says "In the text visible in the image".
Recognising a picture that comes back
When a picture is blocked, its perceptual fingerprint (a 64-bit dHash) is kept for that project for a week. The same picture saved as PNG and then as JPEG lands about 3 bits apart, two different pictures 25 or more, and the line is 8. A match is refused before the model is asked, and the fingerprint comes back in facts.image.hash.
Whether the file says a model made it
ToxicFilter reads a Content Credentials (C2PA) manifest and its claim generator, the IPTC digital source type, an AI tool named in XMP, and generation settings written into PNG text chunks by Stable Diffusion's web UI, ComfyUI or NovelAI. It answers in facts.image.provenance with ai (generated, edited or null), the tool and whether content_credentials are present. A camera-signed photo has credentials and no ai.
Fetching a stranger's picture safely
An image URL is checked before it is downloaded: http and https only, the host resolved, and anything pointing at a private or internal address refused before it is opened, redirects not followed. The download is streamed and dropped past 5 MB, nothing over 40 megapixels is decoded, and the text chunks of a PNG are inflated within one 1 MB budget for the whole file.