OpenNSFW2 HTTP API
August 29, 2026 ยท View on GitHub
A FastAPI-based HTTP service for NSFW content detection using the OpenNSFW2 library.
Quick Start
Using Docker (Recommended)
# Build and run.
docker build -t opennsfw2-api .
docker run -p 8000:8000 opennsfw2-api
# Alternatively, use `docker compose`.
docker compose up opennsfw2-api
Direct Installation
# Install dependencies.
pip install -r requirements-api.txt
# Run the API.
uvicorn app.main:app --host 0.0.0.0 --port 8000
API Documentation
Once running, visit:
- Interactive API docs: http://localhost:8000/docs
- Alternative docs: http://localhost:8000/redoc
Endpoints
Health Check
GET /health/- Basic health checkGET /health/model- Check if model is loaded
Image Prediction
POST /predict/image- Single image predictionPOST /predict/images- Multiple images prediction
Video Prediction
POST /predict/video- Video frame prediction
Usage Examples
Single Image (URL)
curl -X POST "http://localhost:8000/predict/image" \
-H "Content-Type: application/json" \
-d '{
"input": {
"type": "url",
"data": "https://example.com/image.jpg"
},
"options": {
"preprocessing": "YAHOO"
}
}'
Single Image (Base64)
curl -X POST "http://localhost:8000/predict/image" \
-H "Content-Type: application/json" \
-d '{
"input": {
"type": "base64",
"data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg=="
}
}'
Multiple Images
curl -X POST "http://localhost:8000/predict/images" \
-H "Content-Type: application/json" \
-d '{
"inputs": [
{
"type": "url",
"data": "https://example.com/image1.jpg"
},
{
"type": "url",
"data": "https://example.com/image2.jpg"
}
],
"options": {
"preprocessing": "YAHOO"
}
}'
Video
curl -X POST "http://localhost:8000/predict/video" \
-H "Content-Type: application/json" \
-d '{
"input": {
"type": "url",
"data": "https://example.com/video.mp4"
},
"options": {
"preprocessing": "YAHOO",
"frame_interval": 8,
"aggregation_size": 8,
"aggregation": "MEAN"
}
}'
Python Client Example
import requests
import base64
# Single image prediction.
def predict_image_url(url: str) -> dict:
response = requests.post(
"http://localhost:8000/predict/image",
json={
"input": {
"type": "url",
"data": url
}
}
)
return response.json()
# Base64 image prediction.
def predict_image_base64(image_path: str) -> dict:
with open(image_path, "rb") as f:
image_data = base64.b64encode(f.read()).decode()
response = requests.post(
"http://localhost:8000/predict/image",
json={
"input": {
"type": "base64",
"data": image_data
}
}
)
return response.json()
# Usage.
result = predict_image_url("https://example.com/image.jpg")
print(f"NSFW probability: {result['result']['nsfw_probability']}")
Testing
tests/api_smoke.py exercises a running service end to end. It covers what the
library test suite cannot see: the container serving at all, the event loop
staying free during a slow request, the download deadline firing, and the input
handling of each endpoint.
docker run -d --name opennsfw2-api -p 8000:8000 \
--add-host host.docker.internal:host-gateway \
-e OPENNSFW2_DOWNLOAD_DEADLINE_SECONDS=10 opennsfw2-api
python3 tests/api_smoke.py \
--base-url http://127.0.0.1:8000 \
--deadline-seconds 10 \
--callback-host host.docker.internal
The script serves a deliberately stalled URL for the target to fetch, so
--callback-host must be the name by which the target reaches your machine.
Use host.docker.internal for a container, or 127.0.0.1 for a local process.
It needs no dependencies beyond the standard library.
The api job in .github/workflows/ci.yml runs the same script against a
freshly built image on both x86_64 and arm64.
Request/Response Format
Input Types
url: HTTP/HTTPS URL to image or videobase64: Base64 encoded image or video data
Preprocessing Options
YAHOO: Original Yahoo preprocessing (default)SIMPLE: Simplified preprocessing
Video Options
frame_interval: Process every Nth frame (default: 8)aggregation_size: Number of frames to aggregate (default: 8)aggregation: Aggregation method -MEAN,MEDIAN,MAX,MIN(default:MEAN)
Response Format
All successful responses include:
processing_time_ms: Processing time in millisecondsversion: OpenNSFW2 package version
For images:
{
"result": {
"nsfw_probability": 0.85
},
"processing_time_ms": 245.5,
"version": "<version>"
}
For videos:
{
"result": {
"elapsed_seconds": [0.0, 0.125, 0.25, ...],
"nsfw_probabilities": [0.1, 0.15, 0.8, ...]
},
"processing_time_ms": 15000.0,
"version": "<version>"
}
Error Handling
The API returns appropriate HTTP status codes:
200: Success400: Bad Request (invalid input, download failed)500: Internal Server Error
Error response format:
{
"detail": "Error message"
}
Configuration
Environment Variables
OPENNSFW2_HOME: Directory for model weights (default:~/.opennsfw2)OPENNSFW2_DOWNLOAD_DEADLINE_SECONDS: Total time budget for fetching one input URL, after which the request fails with400(default:120)
File Limits
- Supported image formats: JPEG, PNG, GIF, BMP, TIFF (via Pillow)
- Supported video formats: MP4, AVI, MOV, etc. (via OpenCV)