Unprofane
Replacing Perspective API? It ends after 31 December 2026. Switch by changing one URLPerspective API ends 31 Dec. Switch

Profanity filtering your users can't get around

Leetspeak, look-alike letters and creative spelling are caught and named, and Scunthorpe still gets through.

check.ts Live: edit the text
import { Unprofane } from '@unprofane/sdk';
const unprofane = new Unprofane(process.env.UNPROFANE_KEY);

const r = await unprofane.check({
  text: ,
  context: 'username',
  policy: 'teen',
  mode: 'fast',
  …
});

if (!r.allowed) refuse(r.reason);
Try
Response

Recipes and SDKs for

One call wherever people type

The code on the left is all you add. The right is what your users see, with the engine's real answers.

chat.ts
const r = await unprofane.check({
  text: message,
  context: 'chat',
  policy: 'kids',
  mode: 'fast',
});

// The room sees the censored line; nothing vanishes.
room.send(r.allowed ? message : r.censored);
Your game’s chatExample
  1. mira_07omg that jump was insane
  2. tomtomthis level is ****
  3. kailol the boss has 1 hp left
  4. mira_07******* you *****
  5. kainice, see you next round

What they typed

  • this level is sh1tleetspeakblock
  • shut up you l0serleetspeakblock

A profanity filter in 17 languages

Each has its own words, spellings and insults made of ordinary words. A line joins two languages that share a spelling: suka is a swear in Russian and means “like” in Indonesian, so the engine reads which language a sentence is in.

A form is a word in one of its spellings. A line joins two languages when a term of one is a common word of the other: 95 pairs of languages, 354 spellings. The lexicon of October 12, 2026.

Your coding agent can wire it in

Ask for “a username check at sign-up” and your agent has what it needs: an MCP server, a recipe for your stack, and the whole docs in one file.

  • check_text
  • check_batch
  • get_integration_snippet

Terminal

claude mcp add --transport http unprofane https://api.unprofane.com/mcp \
  --header "Authorization: Bearer up_test_YOUR_KEY"

.cursor/mcp.json

{
  "mcpServers": {
    "unprofane": {
      "url": "https://api.unprofane.com/mcp",
      "headers": { "Authorization": "Bearer up_test_YOUR_KEY" }
    }
  }
}

.vscode/mcp.json

{
  "inputs": [{
    "type": "promptString", "id": "unprofane-key",
    "description": "Unprofane key", "password": true
  }],
  "servers": {
    "unprofane": {
      "type": "http",
      "url": "https://api.unprofane.com/mcp",
      "headers": { "Authorization": "Bearer ${input:unprofane-key}" }
    }
  }
}

Then askCheck usernames at sign-up with Unprofane, and show the reason when one is refused.

Will it block my real users?

Not for living in a town, quoting a word in a lesson or a villain's line in a story. These are the API's answers, in the languages they were written in.

Gets through

  • Moving to Scunthorpe next month

    English, generalby the modelallow

    Whole words only, and the innocent list.

  • Le patient présente une douleur au sein gauche et au pénis.

    French, generalby the modelallow

    Body words in a medical sentence.

  • La palabra «puta» tiene muchos usos en el español de América.

    Spanish, a lessonby the modelallow

    A lesson about a word.

  • «Я тебя убью», прошептал злодей и исчез в тумане.

    Russian, fictionby the modelallow

    Dialogue in fiction is the story’s.

  • איזה אידיוט אני, שכחתי שוב את המפתחות

    Hebrew, chatby the modelallow

    The writer is the idiot; aimed at someone, the same word blocks.

  • gg ez, uninstall

    English, game chatby the modelallow

    A taunt about play; in chat the same line is an insult.

  • He told me I was worthless and I cried all night.

    English, chatby the modelallow

    The writer reports what was said to them.

Gets caught

  • xXsh1tl0rdXx

    English, a usernameby the rulesblock

    Split into words and decoded.

  • you're such a b1tch

    English, chatby the rulesblock

    Read as the word, and the disguise named.

  • ƒυ¢к off

    English, chatby the rulesreview

    A hooked f, a Greek u, a cent sign and a Cyrillic k.

  • v a i s e f o d e r

    Portuguese, chatby the rulesblock

    Portuguese, joined back into its words.

  • eres un pedazo de 💩

    Spanish, chatby the rulesblock

    The picture reads as its Spanish word inside the phrase; alone it is a picture.

  • what an Arschloch move, seriously

    German, chatby the rulesblock

    A German insult in an English sentence.

  • Nice family you have. Shame if something happened to them.

    English, chatby the modelblock

    No listed word; the model reads the threat.

Standard policy, the API's default mode. Recorded on engine 2026-09-29, October 8, 2026. All 24 examples in 9 languages.

Where a word list goes wrong

The same texts through a list of our own English words matched as typed, and through the rules alone, in fast mode. The model is not asked.

  1. Moving to Scunthorpe next month

    The list finds a slur inside a town.

    Blocks it

    allow

  2. f.u.c.k off, nobody wants you here

    Four dots and the list sees no word.

    Misses it

    block

  3. ƒυ¢к off

    A hooked f, a Greek u, a cent sign and a Cyrillic k.

    Misses it

    review

  4. I know where you live

    A threat in ordinary words.

    Misses it

    block

Out of 4

1 innocent blocked3 missed

1 innocent let through3 caught

  1. 89.0%

    of toxic text caught, with 7.0% of clean text flagged

    Head to head, on judged text

    On the same texts, labelled blind, and allowed the same share of false alarms, Google's Perspective API catches 58.4% and OpenAI moderation 40.8%. Where our own false alarms fall is published beside it. See the head to head.

    The private test set, 9,029 texts labelled blind, read once on 2026-09-28, engine 2026-09-29

  2. 13

    disguise techniques, each one caught and named

    Hard to fool

    From leetspeak to zero-width characters to a word read backwards, each is undone before the words are read and named in the response. The weakest is caught 89% of the time; the rest better.

    • homoglyph
    • zero_width
    • separator
    • leetspeak
    • repeated_chars
    • diacritics
    • mixed_script
    • fullwidth
    • combining_marks
    • phonetic
    • reversed
    • concatenation
    • case_mixing

    Per-technique recall, 2026-10-12-11-25-45-dev.json

  3. 0.0%

    false positives on innocent text

    Does not block Scunthorpe

    Measured on 1,447 innocent words and phrases that contain a profane string: places, surnames, animals, tools, anatomy in a medical sentence. Each one is a test.

    The Scunthorpe Test, 2026-10-12

  4. 5ms

    at p99, inside the Worker

    Fast where it must be

    For a check the rules answer, which is most of them. The model is asked only about text the rules cannot settle, and every response says which route answered.

    Our own measurement in workerd, 300 requests, 2026-09-28

79days left

Google's Perspective API ends after 31 December 2026

Same path, same JSON, same errors, the key as ?key=. Change the base URL and keep the client. Guides for Coral, Discourse, Python and Node, and a replay tool that maps your thresholds.

Migration hub

Private by architecture

Each promise is a test in the source, and the privacy page says them in plain words.

Pricing

A check is one text. Each step up costs less per check, and checks past the allowance go on the same invoice.

  1. Hobby$9 a month25,000 checks a month
    • Then $29 per 100,000
    • 1 project
    • Free test keys, 100 checks a day
    $0.36 per 1,000 checks
  2. Starter$29 a month100,000 checks a month
    • Then $29 per 100,000
    • 3 projects
    • Free test keys, 100 checks a day
    $0.29 per 1,000 checks
  3. Growth$249 a month1,000,000 checks a month
    • Then $25 per 100,000
    • 10 projects
    • Engine pinning and CSV replay
    • Free test keys, 100 checks a day
    $0.25 per 1,000 checks
  4. Scale$999 a month5,000,000 checks a month
    • Then $20 per 100,000
    • Unlimited projects
    • Engine pinning and CSV replay
    • Priority support
    • Free test keys, 100 checks a day
    $0.20 per 1,000 checks

50% offThe first 3 months of any plan, for a first subscription started by January 31, 2027. Applied at checkout.

Research plans are free by application. Enterprise adds a zero-retention model tier, a custom lexicon and invoice billing. All plans.