Lucenta
Live — calling the real API

Profanity & Toxicity Check

Paste text below and Lucenta will flag it in real time, calling the live API — edge-deployed on Cloudflare Workers, no ML pipeline to babysit.

Powered by Workers AI embeddings + Vectorize semantic search

Most profanity filters are keyword blocklists wearing a nicer API — a list of banned words, checked with exact or fuzzy string matching, that a single misspelling or added space defeats. Lucenta's profanity and toxicity checker takes a more resilient approach: real semantic similarity search over Workers AI embeddings, backed by a curated Vectorize index and a literal-match safety net for the handful of cases semantic search alone can miss. Below is exactly how it works, how it compares to the naive approach, how to wire it into your product, and — just as importantly — where its limits are.

How the detection actually works

1

Every check starts by slicing your submitted text into the whole message plus a series of overlapping word-level windows — six words at a time, sliding four words forward each step. This matters more than it sounds: embedding an entire paragraph as a single vector dilutes a toxic word once it's surrounded by enough unrelated words, which is exactly the failure mode that let short-but-clear violations slip through in earlier iterations of this checker. A sentence like "honestly this whole thing is just a bunch of fucking nonsense" scores lower as one long vector than the isolated phrase "fucking nonsense" does on its own — so we check both.

2

All of those chunks — the full message and every window — are embedded in a single batched call to the bge-base-en-v1.5 model on Workers AI, which keeps the whole operation to one round trip to the model regardless of how many chunks your text produces (capped at 40 chunks, evenly spread across the text, so even a message near the 2,000-character limit stays fast).

3

Each embedded chunk is then compared against a Vectorize index seeded with thousands of known profane and toxic terms and phrases — from plain profanity to leetspeak variants, insults, and harassment phrases — using cosine similarity. Because the comparison is about meaning rather than exact spelling, it catches paraphrased, misspelled, and lightly obfuscated variants of terms it's seen without needing a dictionary entry for every possible spelling.

4

Running alongside the semantic search is a literal, word-boundary-safe check against a curated list of unambiguous terms — a instant, zero-latency backstop for exact matches that also guards against the classic keyword-filter failure in reverse: it will never flag "assessment", "grass", or "assassin" just because they contain the substring "ass", because the match only fires on whole-word boundaries.

5

A request is flagged when the single highest-confidence match across every chunk and the literal check crosses a 0.82 similarity threshold — tuned to catch real toxicity while giving genuine disagreement, sarcasm-adjacent bluntness, and mild dismissiveness room to breathe. The API returns that raw score and the specific matched terms too, so you can see exactly why a piece of text was or wasn't flagged instead of trusting a black box.

Semantic detection vs. a keyword blocklist

It's worth being specific about why this is meaningfully different from the free npm package or fifty-line regex most teams start with, rather than just asserting it's "smarter."

 Keyword blocklistLucenta
Misspellings & leetspeak ("fck", "sh1t")Missed unless individually added to the listCaught via semantic similarity and a pre-seeded leetspeak set
Profanity buried in a long, otherwise-neutral sentenceOften missed — string search still finds it, but many blocklists cap or dilute scoringCaught via overlapping window chunking, regardless of sentence length
False positives on innocent words ("assessment", "Scunthorpe")Common with naive substring matchingAvoided via word-boundary matching and semantic context
Maintenance burden as new slang appearsManual — someone has to notice and add every new variantLower — semantic search generalizes to unseen paraphrases of known toxicity
Transparency into why something was flaggedUsually just a booleanReturns the score and the specific matched term(s)

Wiring it into your product

The whole point is that this is one HTTP call, not an ML pipeline you have to run and maintain yourself.

  1. 1

    Get an API key

    Head to /get-started and enter your email — I'll send you a sign-in link, and your dashboard (and first key) is ready the moment you click it. No password, no waiting.

  2. 2

    Send the text

    POST the text you want checked to /v1/check with your key in the x-api-key header. There's a 2,000 character limit per request.

  3. 3

    Read the verdict

    The response includes flagged (boolean), score (0–1), and matches (the specific terms that drove the score) — decide your own policy from there rather than trusting a single opaque boolean.

  4. 4

    Decide what "flagged" means for you

    Some products hard-block flagged content before it's ever stored; others store it but hide it pending human review, or use the score to route only borderline cases to a moderator. The API gives you the signal — the policy is yours to set.

Example request & response

curl https://api.lucenta.dev/v1/check \
  -H "content-type: application/json" \
  -H "x-api-key: YOUR_KEY" \
  -d '{ "text": "you are so stupid" }'

{
  "flagged": true,
  "score": 0.85,
  "matches": [{ "term": "you are stupid", "score": 0.85 }],
  "latencyMs": 480
}

Response fields, in detail

Every field in the response is there to help you build your own moderation policy on top of it, rather than trusting a single opaque boolean.

flaggedboolean
True if the highest-confidence match across all checked chunks meets or exceeds the 0.82 threshold.
scorenumber (0–1)
The similarity score of the single best match found — useful for building your own "needs review" band below the hard flag threshold.
matchesarray
Up to five of the highest-scoring terms found, each with its own term and score, sorted highest first and de-duplicated by term.
matches[].termstring
The specific known profane or toxic phrase that your text matched most closely — useful for logging or for showing a moderator why something was flagged.
matches[].scorenumber (0–1)
That specific match's similarity score. A literal blocklist hit is always reported as exactly 1.
latencyMsnumber
Server-side processing time in milliseconds, measured from receiving the request to returning the verdict — useful for your own monitoring, separate from network round-trip time.

Common use cases

A few places teams typically wire this in:

Comments & reviews

Pre-screen comments, star reviews, or forum posts before they're stored or shown to other users — reject or hold anything flagged rather than moderating after the fact.

Chat & DMs

Run messages through the checker on the write path in real-time chat products, where a slow or async moderation queue defeats the purpose of "real-time."

Usernames, display names & bios

Free-text profile fields are an easy blind spot — they're user-generated content too, and rarely get the same moderation attention as comments.

Pre-processing before a third-party LLM

Screen user input before forwarding it to a support chatbot or LLM-backed feature, so obviously abusive input never reaches (or gets a response from) a model you're paying for.

Community forums & marketplaces

Flag listing descriptions, seller messages, or classified ads for review without hiring a moderation team to read every single one.

Live chat & gaming

In-game chat and live-stream chat move fast enough that manual moderation alone can't keep up — an automated first pass catches the volume so humans handle the edge cases.

Signup & onboarding forms

Bios, "tell us about yourself" fields, and other free text collected at signup are still user-generated content, and often go completely unchecked.

Where the checker's limits are

Being upfront about this matters more than pretending it's perfect — no automated moderation tool is, and treating it as one is how false confidence creeps in.

  • English only, for now — multi-language support is on the roadmap but not yet shipped.
  • The Vectorize index is curated, not exhaustive — it covers common profanity, insults, and harassment patterns well, but it isn't a substitute for a human reviewing genuinely novel or highly context-dependent abuse.
  • The 0.82 threshold is tuned for typical toxic language, not for specialized domains — clinical, legal, or academic discussions of sensitive topics can occasionally trigger a false positive.
  • Sarcasm and reclaimed language can go either way — the model reads meaning, not intent, so it can't always tell a joke between friends from an actual insult.
  • Requests are capped at 2,000 characters — for longer documents, chunk them client-side and check each piece.
  • It checks text only — images, audio, and video content aren't covered by this endpoint.

FAQ