Profanity filtering users can't bypass.
What people type at each other, read through every disguise, with the reason, and no model to host.
Llama Guard: not run on our benchmark, so no figure for it here.
Head to head, 2026-09-28; the Scunthorpe Test, 2026-10-20. How it was measured
Choose Unprofane for
Each line on Llama Guard cites its own pages, one click away.
| Question | Unprofane | Llama Guard |
|---|---|---|
| Accuracy, published | 89.0% of toxic text caught at 7.0% clean flagged, on a private test set; 0.0% of the Scunthorpe Test’s innocent words flagged. See it | Not run on our benchmark: no figure here. |
| What it moderates | Profanity, slurs, insults, threats, sexual language, self-harm and reserved names, in 17 languages, with 17 disguises undone and named. Text only. See it | |
| What an answer shows | The decision and why: each word, where it was, how it was disguised, its severity, a censored version and the engine version. See it | A line saying safe or unsafe, then the codes of the categories hit. [3] |
| Deployment and upkeep | Nothing to install or run: one HTTPS call, answered at Cloudflare’s edge. Engine versions are dated and never change once published. See it | |
| Review by people | Allow, review or block, set by audience (kids, teen, standard, strict), so only what needs a person reaches your team. See it | |
| Integrations | JavaScript and Python SDKs, an MCP server, a Perspective-compatible endpoint, and recipes for Supabase, Clerk, Firebase, Auth0, Better Auth, Unity and Godot. See it | Built into the Llama 3.1 reference implementations. [1] |
| Pricing | From $9 a month for 25,000 checks, with a published overage rate. Free test keys, 100 checks a day, no card. See it | |
| Processing and retention | Kept 90 days when the model reads it, then deleted; Growth, Scale and Enterprise can turn that off. Never logged or cached. See it |
The live engine. Type a disguised word and watch it read; type an innocent one and watch it pass.
A free key and one request. The rules answer inside the Worker, at the edge, before any model is asked.
3ms
at p99 inside the Worker, for a check the rules answer
Our own measurement in workerd, 300 requests, 2026-10-19
One request in the language you use. The answer carries the evidence.
curl -X POST https://api.unprofane.com/v1/check \
-H "Authorization: Bearer up_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"text":"xXsh1tXx","context":"username","policy":"teen"}'const res = await fetch('https://api.unprofane.com/v1/check', {
method: 'POST',
headers: { Authorization: 'Bearer up_test_YOUR_KEY', 'Content-Type': 'application/json' },
body: JSON.stringify({
"text": "xXsh1tXx",
"context": "username",
"policy": "teen"
}),
});
const result = await res.json();import httpx
r = httpx.request(
"POST",
"https://api.unprofane.com/v1/check",
headers={"Authorization": "Bearer up_test_YOUR_KEY"},
json={
"text": "xXsh1tXx",
"context": "username",
"policy": "teen",
},
timeout=5.0,
)
result = r.json()req, _ := http.NewRequest("POST", "https://api.unprofane.com/v1/check",
strings.NewReader(`{"text":"xXsh1tXx","context":"username","policy":"teen"}`))
req.Header.Set("Authorization", "Bearer up_test_YOUR_KEY")
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)$res = file_get_contents('https://api.unprofane.com/v1/check', false, stream_context_create(['http' => [
'method' => 'POST',
'header' => "Authorization: Bearer up_test_YOUR_KEY\r\nContent-Type: application/json\r\n",
'content' => '{"text":"xXsh1tXx","context":"username","policy":"teen"}',
]]));
$result = json_decode($res, true);require 'net/http'; require 'json'
uri = URI('https://api.unprofane.com/v1/check')
req = Net::HTTP::Post.new(uri, 'Authorization' => 'Bearer up_test_YOUR_KEY', 'Content-Type' => 'application/json')
req.body = '{"text":"xXsh1tXx","context":"username","policy":"teen"}'
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
result = JSON.parse(res.body)using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "up_test_YOUR_KEY");
var req = new HttpRequestMessage(HttpMethod.Post, "https://api.unprofane.com/v1/check");
req.Content = new StringContent("{\"text\":\"xXsh1tXx\",\"context\":\"username\",\"policy\":\"teen\"}", Encoding.UTF8, "application/json");
var res = await http.SendAsync(req);
var result = await res.Content.ReadFromJsonAsync<JsonElement>();var http := HTTPRequest.new()
add_child(http)
http.request_completed.connect(func(_r, _code, _h, body):
var result = JSON.parse_string(body.get_string_from_utf8())
print(result))
http.request("https://api.unprofane.com/v1/check",
PackedStringArray(["Authorization: Bearer up_test_YOUR_KEY", "Content-Type: application/json"]),
HTTPClient.METHOD_POST, "{\"text\":\"xXsh1tXx\",\"context\":\"username\",\"policy\":\"teen\"}")