Using NSFW Classifier on Chutes
NSFW Classifier is a community chute by vonkaiser that puts two compact moderation models behind one host: a Vision Transformer for binary NSFW image detection and Detoxify for text toxicity scoring. Both endpoints take flat JSON and return the predicted label: the image endpoint with a single confidence score, the text endpoint with per-category toxicity scores.
Overview
The chute bundles two independent classifiers, each mapped to its own endpoint:
POST /image- Falconsai/nsfw_image_detection. A ViT fine-tuned fromgoogle/vit-base-patch16-224-in21k(85.8M parameters, Apache-2.0). Per its model card, it was fine-tuned on a proprietary dataset of 80,000 images spanning two classes; its config defines exactly two labels,normalandnsfw, at 224x224 input resolution.POST /text- Detoxify. Unitary's toxic-comment classification project built on the three Jigsaw Kaggle challenges. The chute readme names the Detoxify library rather than a specific checkpoint, but the live endpoint returns the seven per-category scores of Detoxify'sunbiasedmodel (unitary/unbiased-toxic-roberta, a RoBERTa-base sequence classifier, Apache-2.0):toxicity,severe_toxicity,obscene,identity_attack,insult,threat,sexual_explicit. The encoder's position limit caps input at 512 tokens.
Because both are single-forward-pass encoder classifiers rather than generative models, they are cheap and fast enough to run on every upload or message instead of sampling traffic.
Model specifications
| Property | Image classifier | Text classifier |
|---|---|---|
| Endpoint | POST /image | POST /text |
| Model | Falconsai/nsfw_image_detection | Detoxify (live label set matches the unbiased checkpoint, unitary/unbiased-toxic-roberta) |
| Architecture | ViT base, patch 16, 224x224 (fine-tuned from google/vit-base-patch16-224-in21k) | RoBERTa-base sequence classifier |
| Parameters | 85.8M | ~125M |
| Labels | normal, nsfw | toxicity, severe_toxicity, obscene, identity_attack, insult, threat, sexual_explicit |
| Input limit | Any common image format (resized to 224x224) | 512 tokens |
| Training data | 80,000 images (proprietary, per model card) | Jigsaw challenges incl. Unintended Bias (English) |
| License | Apache-2.0 | Apache-2.0 |
Quick start
# Image check: send base64 image bytes
curl -X POST "https://vonkaiser-nsfw-classifier.chutes.ai/image" \
-H "Authorization: Bearer $CHUTES_API_KEY" \
-H "Content-Type: application/json" \
-d '{"image_b64": "<base64-encoded image>"}'
# Text check
curl -X POST "https://vonkaiser-nsfw-classifier.chutes.ai/text" \
-H "Authorization: Bearer $CHUTES_API_KEY" \
-H "Content-Type: application/json" \
-d '{"text": "Text to screen for toxicity"}'
import base64
import os
import requests
HOST = "https://vonkaiser-nsfw-classifier.chutes.ai"
HEADERS = {"Authorization": f"Bearer {os.environ['CHUTES_API_KEY']}"}
# Screen an uploaded image
img_b64 = base64.b64encode(open("upload.jpg", "rb").read()).decode()
image_verdict = requests.post(f"{HOST}/image", headers=HEADERS,
json={"image_b64": img_b64}).json()
# Screen a user comment
text_verdict = requests.post(f"{HOST}/text", headers=HEADERS,
json={"text": "user comment here"}).json()
print(image_verdict, text_verdict) # {label, confidence} / {label, scores}
import { readFile } from "node:fs/promises";
const HOST = "https://vonkaiser-nsfw-classifier.chutes.ai";
const headers = {
Authorization: `Bearer ${process.env.CHUTES_API_KEY}`,
"Content-Type": "application/json",
};
const imgB64 = (await readFile("upload.jpg")).toString("base64");
const imageVerdict = await fetch(`${HOST}/image`, {
method: "POST",
headers,
body: JSON.stringify({ image_b64: imgB64 }),
}).then((r) => r.json());
const textVerdict = await fetch(`${HOST}/text`, {
method: "POST",
headers,
body: JSON.stringify({ text: "user comment here" }),
}).then((r) => r.json());
Parameters and tuning
The API surface is deliberately minimal, per the chute's llms.txt:
/image: one required field,image_b64(string), the raw base64 of the image bytes. No data-URI prefix needed./text: one required field,text(string). Keep inputs under the encoder's 512-token limit; chunk longer documents and take the max score across chunks.
There are no thresholds or options server-side; tuning happens in how you consume the confidence scores. A practical pattern is a three-band policy: auto-approve below a low threshold, auto-block above a high threshold, and queue the middle band for human review. Calibrate the thresholds on a labeled sample of your own traffic, since both models were trained on distributions that may differ from yours.
What it's best at
- Upload gating. Screen user-submitted images before they are stored or displayed.
- Generation pipelines. Check AI-generated images before returning them to users.
- Comment and chat moderation. Score messages for toxicity, threats, insults, obscenity, and identity hate.
- High-volume screening. Both models are small enough to run on every item, not a sample.
Honest limits: the image model is binary, so it cannot distinguish policy categories like violence, drugs, or self-harm from general NSFW content. The text model's training data is English, so non-English scores are unreliable. And the Detoxify authors themselves flag bias risks in toxicity classifiers; keep a human in the loop for consequential decisions.
How Chutes serves this model
Both endpoints run on the dedicated host https://vonkaiser-nsfw-classifier.chutes.ai and return application/json. Requests are flat JSON bodies (no input_args wrapper) with Authorization: Bearer $CHUTES_API_KEY. Response shapes differ per endpoint: /image returns {"label", "confidence"}, while /text returns {"label", "scores"} where scores is an object with one 0-1 score per toxicity category.
Related resources: the model page, the machine-readable llms.txt, and the callable OpenAPI spec.
FAQ
What does the image endpoint return?
A JSON body with the predicted label and its confidence, per the chute's playground notes. The underlying Falconsai/nsfw_image_detection model is binary: its config defines exactly two classes, normal and nsfw.
What toxicity categories does the text endpoint cover?
The endpoint returns an overall label plus a scores object with seven per-category 0-1 scores: toxicity, severe_toxicity, obscene, identity_attack, insult, threat, and sexual_explicit. That label set matches Detoxify's unbiased checkpoint (unitary/unbiased-toxic-roberta), trained on the Jigsaw challenges including Unintended Bias.
How do I send an image?
Base64-encode the image bytes and POST them as the image_b64 field in a flat JSON body to https://vonkaiser-nsfw-classifier.chutes.ai/image with your Bearer API key. Common image formats work; the ViT model internally resizes to 224x224.
Does the text classifier work in languages other than English?
Not reliably. The Detoxify checkpoint behind the endpoint (its label set matches unitary/unbiased-toxic-roberta) was trained on English Jigsaw data. Unitary publishes a separate multilingual Detoxify model, but that is not what the live label set indicates, so validate on your target language before trusting non-English scores.
Can I use this commercially?
Yes. Both upstream models, Falconsai/nsfw_image_detection and Unitary's Detoxify checkpoints (including unitary/unbiased-toxic-roberta), are Apache-2.0 licensed per their Hugging Face repos, which permits commercial use with attribution and license notice.
How accurate is it, and should I trust it unsupervised?
The image model card reports fine-tuning on 80,000 images but publishes no benchmark table, and toxicity models are known to carry biases from their training data (the Detoxify authors flag this explicitly). Use score thresholds with a human-review band rather than a single hard cutoff for consequential decisions.
How fast and expensive is it compared to using an LLM as a moderator?
Much cheaper. Both models are compact (~86M and ~125M parameter) encoder classifiers doing a single forward pass, not autoregressive generation, so you can afford to run them on every upload or message rather than sampling traffic.