Leetspeak, look-alike letters and creative spelling are caught and named, and Scunthorpe still gets through.
Recipes and SDKs for
The code on the left is all you add. The right is what your users see, with the engine's real answers.
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);
What they typed
const r = await unprofane.check({
text: username,
context: 'username',
policy: 'teen',
mode: 'fast',
});
if (!r.allowed) {
return form.error('username', 'Pick another username.');
}
Choose your player name
Everyone in the game will see it.
xXsh1tl0rdXxPick another username.
What the API said
const r = await unprofane.check({
text: engraving,
context: 'custom_text',
mode: 'fast',
});
if (r.action !== 'allow') {
throw new Refusal("We can't engrave that.");
}Walnut board, engraved
$48
Your engraving
B1TCH QUEENWe can’t engrave that.
What the API said
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.
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.
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.
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.
Moving to Scunthorpe next month
Whole words only, and the innocent list.
Le patient présente une douleur au sein gauche et au pénis.
Body words in a medical sentence.
La palabra «puta» tiene muchos usos en el español de América.
A lesson about a word.
«Я тебя убью», прошептал злодей и исчез в тумане.
Dialogue in fiction is the story’s.
איזה אידיוט אני, שכחתי שוב את המפתחות
The writer is the idiot; aimed at someone, the same word blocks.
gg ez, uninstall
A taunt about play; in chat the same line is an insult.
He told me I was worthless and I cried all night.
The writer reports what was said to them.
xXsh1tl0rdXx
Split into words and decoded.
you're such a b1tch
Read as the word, and the disguise named.
ƒυ¢к off
A hooked f, a Greek u, a cent sign and a Cyrillic k.
v a i s e f o d e r
Portuguese, joined back into its words.
eres un pedazo de 💩
The picture reads as its Spanish word inside the phrase; alone it is a picture.
what an Arschloch move, seriously
A German insult in an English sentence.
Nice family you have. Shame if something happened to them.
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.
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.
Moving to Scunthorpe next month
The list finds a slur inside a town.
Blocks it
allow
f.u.c.k off, nobody wants you here
Four dots and the list sees no word.
Misses it
block
ƒυ¢к off
A hooked f, a Greek u, a cent sign and a Cyrillic k.
Misses it
review
I know where you live
A threat in ordinary words.
Misses it
block
1 innocent blocked3 missed
1 innocent let through3 caught
89.0%
of toxic text caught, with 7.0% of clean text flagged
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
13
disguise techniques, each one caught and named
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.
Per-technique recall, 2026-10-12-11-25-45-dev.json
0.0%
false positives on innocent text
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
5ms
at p99, inside the Worker
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
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.
No request text is stored: not in a database, a cache, a log or a trace.
Text the rules answer never leaves Cloudflare.
A project can be set to Edge-only so no text ever reaches a model.
Each promise is a test in the source, and the privacy page says them in plain words.
A check is one text. Each step up costs less per check, and checks past the allowance go on the same invoice.
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.