DEV Community

Cover image for Clarifai NSFW API Alternative: Migrate in Python
AI Engine
AI Engine

Posted on Originally published at ai-engine.net

Clarifai NSFW API Alternative: Migrate in Python

If your moderation pipeline calls Clarifai NSFW recognition, check it today: on 31 October 2026, the Clarifai API host no longer resolved. This guide maps Clarifai's NSFW and moderation concepts to the NSFW Detect API, then gives you a small Python module that returns the same concept format, so the rest of your code can stay as it is.

What Happened to the Clarifai API

  • May 2026: Clarifai announced that Nebius would license its inference technology and bring in its core team. Clarifai's announcement says the agreement does not include its legacy computer vision models.
  • June 2026: Clarifai's homepage stated that platform credit purchases would be disabled after June 18, 2026.
  • July 2026: Ximilar reported that all Clarifai services would end on July 17, 2026. Ximilar sells a competing product, and we did not find this date in a notice from Clarifai.
  • 31 October 2026: we queried public DNS (1.1.1.1 and 8.8.8.8). api.clarifai.com and docs.clarifai.com returned no address, so calls to the API host cannot connect.
  • 7 November 2026: same check, same result: both hosts still return no address (NXDOMAIN).

Clarifai Concepts Mapped to Top-Level Categories

Clarifai's nsfw-recognition model returned nsfw and sfw. Its moderation model returned gore, drug, explicit, suggestive and safe. NSFW Detect returns 10 top-level categories, each with sub-labels:

Clarifai concept NSFW Detect top-level category
nsfw (nsfw-recognition) Explicit, Non-Explicit Nudity of Intimate parts and Kissing
sfw (nsfw-recognition) Empty ModerationLabels list
explicit Explicit
suggestive Non-Explicit Nudity of Intimate parts and Kissing, Swimwear or Underwear
gore Violence, Visually Disturbing
drug Drugs & Tobacco
safe Empty ModerationLabels list
No equivalent concept Alcohol, Gambling, Rude Gestures, Hate Symbols

Clarifai values run from 0 to 1, while NSFW Detect confidence runs from 0 to 100. Top-level categories are the labels with an empty ParentName. Key your rules on those, so that a deep sub-label is always covered by its category.

Step 1: A Concept-Compatible Module

Save this as nsfw_compat.py. It returns a list of concepts with name and value fields, as dictionaries. Categories with no Clarifai equivalent keep their own lowercase name, such as alcohol.

import os
import requests

API_URL = "https://nsfw-detect3.p.rapidapi.com/nsfw-detect"
HEADERS = {
    "x-rapidapi-host": "nsfw-detect3.p.rapidapi.com",
    "x-rapidapi-key": os.environ["RAPIDAPI_KEY"],
}

# Top-level categories mapped to the concept names your code already uses
CONCEPTS = {
    "Explicit": "explicit",
    "Non-Explicit Nudity of Intimate parts and Kissing": "suggestive",
    "Swimwear or Underwear": "suggestive",
    "Violence": "gore",
    "Visually Disturbing": "gore",
    "Drugs & Tobacco": "drug",
}


def _to_concepts(labels):
    scores = {}
    for label in labels:
        if label["ParentName"] == "":  # top-level categories only
            name = CONCEPTS.get(label["Name"], label["Name"].lower())
            scores[name] = max(scores.get(name, 0), label["Confidence"] / 100)
    if not scores:
        return [{"name": "safe", "value": 1.0}]
    ranked = sorted(scores.items(), key=lambda item: item[1], reverse=True)
    return [{"name": name, "value": round(value, 3)} for name, value in ranked]


def predict_by_filepath(path):
    with open(path, "rb") as f:
        response = requests.post(API_URL, headers=HEADERS, files={"image": f})
    return _to_concepts(response.json()["body"]["ModerationLabels"])


def predict_by_url(image_url):
    response = requests.post(API_URL, headers=HEADERS, data={"url": image_url})
    return _to_concepts(response.json()["body"]["ModerationLabels"])
Enter fullscreen mode Exit fullscreen mode

Step 2: Check Real Images

Set your key in the RAPIDAPI_KEY environment variable, then run:

from nsfw_compat import predict_by_filepath, predict_by_url

for path in ["bar.jpg", "poker.jpg", "jar.jpg", "salad.jpg"]:
    print(path, predict_by_filepath(path))

url = "https://images.pexels.com/photos/1871508/pexels-photo-1871508.jpeg"
print("url", predict_by_url(url))
Enter fullscreen mode Exit fullscreen mode

This is the real output, from a bar, a poker table, a jar on a desk and a salad:

bar.jpg [{'name': 'alcohol', 'value': 0.999}]
poker.jpg [{'name': 'gambling', 'value': 0.992}]
jar.jpg [{'name': 'drug', 'value': 0.995}]
salad.jpg [{'name': 'safe', 'value': 1.0}]
url [{'name': 'gambling', 'value': 0.948}]
Enter fullscreen mode Exit fullscreen mode

The last line sends a Pexels URL instead of a file. It is a larger version of the poker photo, sent at its original size.

Step 3: Keep Your Moderation Rules

from nsfw_compat import predict_by_filepath

BLOCK = {"explicit": 0.8, "gore": 0.8, "hate symbols": 0.8}
REVIEW = {"suggestive": 0.7, "drug": 0.7, "alcohol": 0.7, "gambling": 0.7}


def moderate(path):
    concepts = {c["name"]: c["value"] for c in predict_by_filepath(path)}
    if any(concepts.get(name, 0) >= limit for name, limit in BLOCK.items()):
        return "block"
    if any(concepts.get(name, 0) >= limit for name, limit in REVIEW.items()):
        return "review"
    return "allow"


for path in ["bar.jpg", "poker.jpg", "jar.jpg", "salad.jpg"]:
    print(path, moderate(path))
Enter fullscreen mode Exit fullscreen mode
bar.jpg review
poker.jpg review
jar.jpg review
salad.jpg allow
Enter fullscreen mode Exit fullscreen mode

Migration Tips

  • Search your code for the old host and model IDs: look for api.clarifai.com, nsfw-recognition, moderation-recognition and nsfw-v1.0.
  • Re-check your thresholds: run a sample of your own images through the new module and set each limit from the scores you see.
  • Update attribute access: the module returns dictionaries, so concept.name becomes concept["name"].

To compare other options, see the best NSFW detection APIs.

Move your moderation today: get a key from the NSFW Detect API page and drop in nsfw_compat.py.

Top comments (0)