# Azure Content Moderator Alternative — Profanity Filter (`dropin-apis/content-moderator-compat`) Actor

Azure Content Moderator alternative and drop-in profanity filter + PII detector for the text screen API. Accepts POST /contentmoderator/moderate/v1.0/ProcessText/Screen and returns the same Terms, PII, Classification and Status JSON. Content Safety is a different API. $1 per 1,000 texts.

- **URL**: https://apify.com/dropin-apis/content-moderator-compat.md
- **Developed by:** [drop-in apis](https://apify.com/dropin-apis) (community)
- **Categories:** AI, Developer tools
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 text screeneds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Azure Content Moderator Alternative — Profanity Filter & PII Detector

**Azure Content Moderator alternative, drop-in profanity filter and PII detector** for text: this Actor accepts `POST /contentmoderator/moderate/v1.0/ProcessText/Screen` and returns the same `Terms`, `PII`, `Classification` and `Status` JSON. Microsoft retires Content Moderator on 15 March 2027 ([overview](https://learn.microsoft.com/en-us/azure/ai-services/content-moderator/overview); the [lifecycle page](https://learn.microsoft.com/en-us/lifecycle/products/azure-ai-content-moderator) says 1 April 2027).

**At a glance:** **$1.00 per 1,000 screened texts** (failed requests are not charged) · about 20–80 ms per screen on a warm Standby instance · up to 1,024 characters per text · text only (no image or video moderation). Try it with no code: click **Start** with the prefilled input (batch mode), or send one request to the Standby endpoint.

Last updated: 2026-10-02 · **Full migration guide:** https://alidaram99.github.io/api-alternatives/azure-content-moderator-alternative/

Microsoft's successor, Azure AI Content Safety, uses a different URL, auth and JSON (severity levels instead of `Terms`, `PII` and `Category1/2/3`). With this Actor you point your existing code at a new endpoint and keep everything else.

- ✅ Same route, query parameters (`language`, `classify`, `PII`, `autocorrect`, `listId`) and body formats (`text/plain`, `text/html`, `text/xml`, `text/markdown`)
- ✅ Same response field names, casing and order. `Terms: null` when nothing is found, like Azure
- ✅ Built-in profanity list in 24 languages, with correct `Index` / `OriginalIndex` offsets
- ✅ PII detection: email, IPv4/IPv6, US/UK phone, US SSN, US street address
- ✅ English classification (Category1/2/3 plus ReviewRecommended), as in Azure
- ✅ **$1 per 1,000 screens**; errors are free
- 🔒 Nothing is stored or logged. Text is screened in memory inside the Actor and no third-party AI API is called.

### Migrate in one minute

Your endpoint is the Actor's Standby URL. You'll find it on the Actor's **Standby** tab, for example `https://<username>--content-moderator-compat.apify.actor`. Authenticate with your [Apify API token](https://console.apify.com/settings/integrations), either as `?token=…` or as `Authorization: Bearer …`. The `Ocp-Apim-Subscription-Key` header is accepted and ignored.

#### curl / any REST client

```bash
## Before
curl -X POST "https://westus.api.cognitive.microsoft.com/contentmoderator/moderate/v1.0/ProcessText/Screen?language=eng&classify=True&PII=True" \
  -H "Ocp-Apim-Subscription-Key: $AZURE_KEY" -H "Content-Type: text/plain" \
  --data-binary 'Is this a crap email abcdef@abcd.com, phone: 6657789887, IP: 255.255.255.255, 1 Microsoft Way, Redmond, WA 98052'

## After: same path, query and body
curl -s -X POST "https://<username>--content-moderator-compat.apify.actor/contentmoderator/moderate/v1.0/ProcessText/Screen?language=eng&classify=True&PII=True&token=$APIFY_TOKEN" \
  -H 'Content-Type: text/plain' \
  --data-binary 'Is this a crap email abcdef@abcd.com, phone: 6657789887, IP: 255.255.255.255, 1 Microsoft Way, Redmond, WA 98052'
```

#### C# (`HttpClient`, .NET 6+)

```csharp
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

var baseUrl = "https://<username>--content-moderator-compat.apify.actor";
var token = Environment.GetEnvironmentVariable("APIFY_TOKEN")!;

using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

var content = new StringContent("Is this a crap email abcdef@abcd.com?", Encoding.UTF8, "text/plain");
var response = await http.PostAsync(
    $"{baseUrl}/contentmoderator/moderate/v1.0/ProcessText/Screen?language=eng&classify=True&PII=True", content);
response.EnsureSuccessStatusCode();

using var json = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
var root = json.RootElement;
Console.WriteLine($"Status {root.GetProperty("Status").GetProperty("Code").GetInt32()}, " +
                  $"Category3 {root.GetProperty("Classification").GetProperty("Category3").GetProperty("Score").GetDouble():F3}, " +
                  $"first term '{root.GetProperty("Terms")[0].GetProperty("Term").GetString()}'");
// Status 3000, Category3 0.971, first term 'crap'
```

If you use the old `Microsoft.Azure.CognitiveServices.ContentModerator` SDK, set `client.Endpoint` to the Standby URL and pass `ServiceClientCredentials` that add the `Authorization: Bearer` header. *That SDK path has not been tested; the HttpClient code above has.*

#### Python (`requests`)

```python
import os
import requests

BASE = "https://<username>--content-moderator-compat.apify.actor"
APIFY_TOKEN = os.environ["APIFY_TOKEN"]

r = requests.post(
    f"{BASE}/contentmoderator/moderate/v1.0/ProcessText/Screen",
    params={"language": "eng", "classify": "True", "PII": "True"},
    headers={"Content-Type": "text/plain", "Authorization": f"Bearer {APIFY_TOKEN}"},
    data="This is crap, email me at someone@example.com".encode("utf-8"),
)
screen = r.json()
print(screen["Status"]["Code"], screen["Classification"]["Category3"]["Score"],
      [t["Term"] for t in screen["Terms"] or []], [e["Text"] for e in screen["PII"]["Email"]])
## 3000 0.98 ['crap'] ['someone@example.com']
```

If you use the `azure-cognitiveservices-vision-contentmoderator` SDK, pass `msrest.authentication.BasicTokenAuthentication({"access_token": APIFY_TOKEN})` as the credentials and set `endpoint` to the Standby URL. *(That SDK path has not been tested; the `requests` code above has.)*

#### Node.js

```js
const BASE = 'https://<username>--content-moderator-compat.apify.actor';
const res = await fetch(`${BASE}/contentmoderator/moderate/v1.0/ProcessText/Screen?language=eng&classify=True`, {
  method: 'POST',
  headers: { 'Content-Type': 'text/plain', Authorization: `Bearer ${process.env.APIFY_TOKEN}` },
  body: 'You are a worthless idiot',
});
const screen = await res.json();
console.log(screen.Status.Code, screen.Classification.ReviewRecommended, screen.Classification.Category3.Score);
// 3000 true 0.997
```

### Response example (real output)

```json
{
  "OriginalText": "Is this a crap email abcdef@abcd.com, phone: 6657789887, IP: 255.255.255.255, 1 Microsoft Way, Redmond, WA 98052",
  "NormalizedText": "Is this a crap email abcdef@ abcd. com, phone: 6657789887, IP: 255. 255. 255. 255, 1 Microsoft Way, Redmond, WA 98052",
  "Misrepresentation": null,
  "PII": {
    "Email": [{ "Detected": "abcdef@abcd.com", "SubType": "Regular", "Text": "abcdef@abcd.com", "Index": 21 }],
    "SSN": [],
    "IPA": [{ "SubType": "IPV4", "Text": "255.255.255.255", "Index": 61 }],
    "Phone": [{ "CountryCode": "US", "Text": "6657789887", "Index": 45 }],
    "Address": [{ "Text": "1 Microsoft Way, Redmond, WA 98052", "Index": 78 }]
  },
  "Classification": {
    "ReviewRecommended": true,
    "Category1": { "Score": 0.0023 },
    "Category2": { "Score": 0.2708 },
    "Category3": { "Score": 0.9562 }
  },
  "Language": "eng",
  "Terms": [{ "Index": 10, "OriginalIndex": 10, "ListId": 0, "Term": "crap" }],
  "Status": { "Code": 3000, "Description": "OK", "Exception": null },
  "TrackingId": "c110a5a9-3659-4fff-b2ce-fcac1f4ae9a9"
}
```

This is Microsoft's own documented example sentence. `NormalizedText`, the `Terms` offsets and every `PII` index are identical to Microsoft's published response.

### Compatibility matrix

| Feature | Status |
|---|---|
| Route, HTTP method, query parameters (case-insensitive), content types, 1,024-character limit | **Identical** |
| Response field names, casing, order, `Status`, `Misrepresentation: null`, `Terms: null` when clean, `TrackingId` | **Identical** |
| `NormalizedText` (space inserted after punctuation, as Azure does) | **Identical** on Microsoft's documented examples; may differ on unusual inputs |
| `Terms` with `Index` (into NormalizedText) and `OriginalIndex` (into OriginalText), `ListId: 0` | **Same format.** The word list is our own, so matches can differ from Azure's private list |
| `PII` (Email, IPA, Phone, Address, SSN) with Azure's sub-field names | **Same format.** Patterns are deliberately strict: phones are US/UK, addresses US with state and ZIP, SSNs valid ddd-dd-dddd only |
| `Classification` Category1 (sexually explicit), Category3 (offensive) | **Approximate.** Open model, different scores from Azure's |
| `Classification` Category2 (sexually suggestive) | **Approximate proxy:** `max(Category1, 0.3 × obscene)`. Flirty but clean text is not detected |
| `ReviewRecommended` | True when any category is ≥ 0.5. Azure's internal thresholds are not published |
| Classification for non-English text | Omitted, as in Azure (English only) |
| `autocorrect=true` | Accepted. `AutoCorrectedText` is returned but **no spelling correction is applied** (same as NormalizedText) |
| `listId` (custom term lists) | **Not supported.** Custom lists live in your Azure resource. The built-in list is applied, and the response carries an `X-Compat-Warning` header |
| Image and video moderation, term/image list management, the human review tool | **Not supported** (text screening only) |

### How it compares

| Option | Same `ProcessText/Screen` request and JSON? | Price | Notes |
|---|---|---|---|
| **This Actor** | Yes: change the endpoint, add an Apify token | $1 per 1,000 texts | Category scores approximate; custom term lists (`listId`) not supported |
| Azure Content Moderator | It is the original | Azure pricing | Retires 15 Mar 2027 |
| Azure AI Content Safety | No: different request and JSON (severity levels per harm category) | Azure pricing | Microsoft's recommended successor; rewrite and re-tune |
| Standalone profanity or PII APIs | No | Varies | Cover only part of the Screen response |

### Pricing

**$1.00 per 1,000 screened texts** (pay-per-event `text-screened`). Requests that fail validation (empty, too long, wrong content type) are not charged. Apify also adds its standard small per-start fee to each run, and you don't pay for idle time in Standby mode. Set a maximum total charge per run; once it is reached the API returns `429 {"Error":{"Code":"TooManyRequests",…}}`.

### Batch mode (no code)

Run the Actor normally with a list of `texts` and get one dataset row per text. Each row has the category scores, terms and PII, plus the full Content Moderator response. You can export it as CSV, Excel or JSON.

```json
{ "texts": ["Thanks for your order, it ships tomorrow.", "This is crap, you idiot. Email me at someone@example.com"], "PII": true }
```

### How scores are computed

- **Category1** = Detoxify `sexual_explicit`.
- **Category3** = the maximum of `toxicity`, `obscene`, `insult`, `threat` and `identity_attack`.
- **Category2** = `max(Category1, 0.3 × obscene)`.

Detoxify is Apache-2.0 [Detoxify](https://github.com/unitaryai/detoxify) (`unbiased-toxic-roberta`, quantized ONNX, running on CPU inside the Actor). Clear-cut offensive text scores 0.95–0.99 on Category3, and benign text scores below 0.01. **Scores are not identical to Azure's.** If you rely on precise thresholds, re-check them on a sample of your own texts with batch mode before switching traffic.

Language is auto-detected and returned as an ISO 639-3 code. Latin-script text shorter than 40 characters is treated as English. For short non-English texts, pass `language=spa`, `language=deu` and so on so that the right profanity list is used.

### Latency

On a warm instance, a screen takes about 20–80 ms and the instance handles about 15+ requests/s. After about 5 minutes without requests the instance sleeps, and the next request waits a few seconds while it starts.

### Limits

- Text only: image and video moderation, term/image list management and the human review tool are **not** supported.
- Up to **1,024 characters** per text, as in Azure's own Screen API; longer text returns HTTP 400.
- Custom term lists (`listId`) are not supported; the built-in term list is applied and the response carries an `X-Compat-Warning` header.
- Category scores are independent Detoxify estimates, **not identical to Azure's**; re-check any threshold on a sample of your own texts (batch mode makes that easy) before switching traffic.
- After about 5 minutes without requests the Standby instance sleeps, and the next request waits a few seconds while it starts.

### FAQ

#### What replaces Azure Content Moderator without rewriting my code?

This endpoint. It takes the same `ProcessText/Screen` path, query parameters and raw-text body, and returns the same JSON. $1 per 1,000 successful screens.

#### Is this a profanity filter?

Yes, among other things. The built-in `Terms` list covers profanity in 24 languages (the same categories Azure Content Moderator shipped), with the correct `Index`/`OriginalIndex` character offsets so you can redact in place. Pass `classify=True`/`PII=True` too for the Category1/2/3 classification and PII detection in the same call.

#### When does Azure Content Moderator retire?

15 March 2027 according to Microsoft's product overview; the lifecycle page says 1 April 2027. Plan for 15 March.

#### Is Azure AI Content Safety a drop-in replacement?

No. It uses different fields (severity levels for Hate, Sexual, SelfHarm and Violence) and a different request. Use it if you can change your parser and re-tune; use this Actor if you can't yet.

#### Does it detect email, phone and address (PII)?

Yes. `PII` follows the Content Moderator shape: Email, IPA (IPv4/IPv6), Phone (US/UK), Address (US with state and ZIP) and SSN. Patterns are deliberately strict.

#### Are Category1, Category2 and Category3 the same numbers as Azure?

They are the same fields, computed by an open model. Category2 (suggestive) is a documented proxy. `ReviewRecommended` is true when any category is ≥ 0.5. Re-check thresholds on your own texts.

#### Are custom term lists (listId) supported?

No. Custom lists live inside your Azure resource. The built-in list (ListId 0) is always applied, and the response carries an `X-Compat-Warning` header when you pass a custom `listId`.

#### What does a ProcessText/Screen call cost?

$0.001 per screened text ($1 per 1,000). Requests that fail validation are not charged.

#### Is my text stored?

No. Nothing is written to disk or logged; text is screened in memory and discarded.

#### What about short non-English texts?

Latin-script text under 40 characters is treated as English. Pass `language=spa`, `language=deu` and so on to use the right profanity list.

#### Is this affiliated with Microsoft?

No. It is an independent, compatible implementation of the public Content Moderator v1.0 text screening interface.

# Actor input Schema

## `texts` (type: `array`):

Texts to screen, up to 1,024 characters each (Azure's limit). One dataset item and one 'text-screened' event per successfully screened text.

## `classify` (type: `boolean`):

Return Classification scores (English only, like Azure).

## `PII` (type: `boolean`):

Return PII matches (email, IP address, phone, US SSN, US address).

## `language` (type: `string`):

ISO 639-3 code such as eng, spa, fra, deu. Leave empty to auto-detect per text.

## `contentType` (type: `string`):

How to read the texts: text/plain, text/html, text/xml or text/markdown. Markup is ignored for screening.

## Actor input object example

```json
{
  "texts": [
    "Thanks for your order, it ships tomorrow.",
    "This is crap, you idiot. Email me at someone@example.com"
  ],
  "classify": true,
  "PII": true,
  "contentType": "text/plain"
}
```

# Actor output Schema

## `results` (type: `string`):

All items in the run's default dataset.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "texts": [
        "Thanks for your order, it ships tomorrow.",
        "This is crap, you idiot. Email me at someone@example.com"
    ],
    "PII": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("dropin-apis/content-moderator-compat").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "texts": [
        "Thanks for your order, it ships tomorrow.",
        "This is crap, you idiot. Email me at someone@example.com",
    ],
    "PII": True,
}

# Run the Actor and wait for it to finish
run = client.actor("dropin-apis/content-moderator-compat").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "texts": [
    "Thanks for your order, it ships tomorrow.",
    "This is crap, you idiot. Email me at someone@example.com"
  ],
  "PII": true
}' |
apify call dropin-apis/content-moderator-compat --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dropin-apis/content-moderator-compat"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/BpHWr7TjwIQhrsvZ3/builds/dk6uMGkll78gqzrgc/openapi.json
