A probability for every message. You set the line.

Moderation for Discord, Telegram, Reddit and any app with user text: eight categories, rules in your own words, thresholds you own, every decision logged.

v0.2.0 · Python 3.10+ · Node 20+ · PyPI · npm · GitHub

0 / 300

Type a message or pick an example. The decision the bots and the API return appears here.

action -   waiting for a message

The tick on each bar is the default threshold. Default action is flag; delete and timeout are opt-in per category.

Your text goes to this demo server and to TypeSafe's API, and stays in the demo log with a hashed IP so the author can see what people try. Do not paste anything private.

cost
$0.042per 1,000 messages

A 20,000-message community runs for under $1 a month. About 30x less than a general LLM as judge at list price. Measured: 1,005 input tokens per judged message, Jev at $0.042 per million.

jevmod is free (MIT). Self-hosted, the only bill is TypeSafe's Jev API, paid to them; a free tier exists at console.typesafe.ai.

quality, AUROC

Catches harassment, sexual content, self-harm and grooming better than Meta's and Google's open moderation models on OpenAI's human-labelled test set.

0.93/0.98/0.99/0.98
harassment, sexual content, self-harm, minors

On OpenAI's human-labelled moderation set. Best in every category it was compared on, against Llama Guard 3, ShieldGemma and toxic-bert.

coverage
8categories, plus your rules in plain English

spam, scam, harassment, nsfw, off-topic, self-harm, doxxing, minors. One yes/no question each, all in one request. Flag-only by default; react ❌ or ✅ on a log entry and that category's threshold moves a notch.

Terminal recording: jevmod check flags a fake Nitro giveaway as scam 0.99 and exits 1; a four-line file is checked in one request; the HTTP API returns the same decisions as JSON.

What it costs at your volume.

Type your monthly message count. Every figure below is your number multiplied by a measured or listed constant; the arithmetic stays visible. Nothing here is a claim about time saved.

Messages that reach Jev. jevmod never sends messages under eight letters without a link, from trusted authors, or repeats of judged text, so your bill is for a subset of traffic.

Used for the Llama Guard row. $0.30 is the benchmark's assumption for a consumer-class card; a cloud A10G or L4 is $0.50 to $1.20 an hour at list.

systemarithmeticper month
jevmod
Jev, 7 categories on, one request per batch
General LLM as judge
Claude Haiku 4.5 list price, input tokens only, not run
Llama Guard 3 8B
Q4_K_M, compute time on a rented GPU

Constants: 1,005 input tokens per judged message and 49 ms per message for Llama Guard, measured on 2,531 messages (RTX 5080); Jev $0.042 and Claude Haiku 4.5 $1.00 per million input tokens at list price; the 1.2 factor is prompt overhead. Details in BENCHMARK.md.

Self-hosted, plus wherever you run it: a $4/month VM is enough for the bots. The hosted bot has its own flat plans (see Community owner).

Three ways in.

Same questions, same thresholds and the same audit log whether a bot, a script or an agent asks. Judgments come from Jev, TypeSafe's System One model, so you need their API key.

Community owner

Invite the bot, or run it yourself.

Or let us run it.

Invite the hosted bot and it starts flagging in a minute. Free up to 5,000 judged messages a month per server. Pro: $3.99 a month per server, up to 50,000 judged messages. Type /mod upgrade in your server to get the payment link; cancel from the same link any time.

You can also run it yourself on any small machine or a $4/month VM: free forever, your own key, Docker in one command. Setup is three steps in the README.

The Discord bot creates a private log channel and starts flagging. /mod set scam delete 0.7 changes a threshold, /mod rule adds a rule in plain language. React on a log entry to mark a false positive and the threshold moves a notch.

Telegram has /mod_status, /mod_set, /mod_rule, /mod_topic and /mod_log. The Reddit adapter is configured by environment variables only.

By default it only flags into a private #jevmod-log channel.

Nothing is deleted or muted until you type /mod set <category> delete.

Wrong call? React ❌ on the log entry.

Regret everything? /mod forget wipes all data; kicking the bot does the same.

pip install "jevmod[discord]"
jevmod init                # asks for the key, stores it in the OS keyring
export DISCORD_TOKEN=...
jevmod discord             # or: jevmod telegram, jevmod reddit
FreeProSelf-hosted
price$0$3.99 per server per month$0 + your TypeSafe usage
judged messages per month5,00050,000unlimited, your key
who holds the keyususyou
datalog 30 days, /mod forgetsameon your machine
canceln/aany time, Stripe portaln/a
Developer

A function, a CLI or one HTTP call.

Python and npm packages ask Jev the questions in categories.json. The CLI exits 1 when something triggers, so it fits a shell script or a CI job. The HTTP API is one Docker image with SQLite on a volume.

The HTTP API fails open for you (reason="error_open"); the SDK raises so you choose.

Copy-paste starters: FastAPI dependency, pydantic field, Express middleware, Agent SDK hooks, LangChain callback in examples/.

OpenAPI at /docs on your own jevmod api (static copy: openapi.json); Postman collection: jevmod.postman_collection.json.

# Python
from jevmod import Moderator
from typesafe_sdk import TypeSafeError
try:
    d = Moderator().check(text, channel_topic="gaming")
    d.action, d.category, d.scores   # 'flag', 'scam', {...}
except TypeSafeError:                # Jev unreachable: fail open on purpose
    d = None

# Node 20+
npm install jevmod        # or: pnpm add jevmod
import { Moderator } from "jevmod";
const d = await new Moderator().check(text, { channelTopic: "gaming" });  // d.action, d.category, d.scores

# CLI, one message per line
cat comments.txt | jevmod check --rule "No politics." -
# exit 0 clean, 1 triggered, 2 error; --json prints every probability

# HTTP
JEVMOD_ADMIN_TOKEN=... jevmod api
curl -X POST localhost:8080/v1/keys -H "Authorization: Bearer $JEVMOD_ADMIN_TOKEN" \
     -H "Content-Type: application/json" -d '{"tenant":"my-app"}'   # -> {"api_key":"jm_..."}
curl -X POST localhost:8080/v1/moderate -H "Authorization: Bearer jm_..." -H "Content-Type: application/json" \
     -d '{"messages":[{"id":"a","text":"..."}]}'
# response
{"request_id":"9f1c...","decisions":[{"message_id":"a","action":"flag","category":"scam","probability":0.99,
  "scores":{"spam":0.98,"scam":0.99,"harassment":0.02,"nsfw":0.01,"selfharm":0.01,"doxxing":0.02,"minors":0.01},
  "judged":true,"reason":"jev"}],
 "usage":{"judged_this_month":1,"jev_requests_this_month":1,"input_tokens_this_month":1193}}
Agent

A plugin, an MCP server and a recipe.

jevmod-integrate detects your stack, picks SDK, npm, HTTP or MCP, stores the key, inserts the call and adds a real test; jevmod-moderate screens text while you work. Tools: moderate(texts, channel_topic?, rules?) and categories().

AGENTS.md is the integration recipe an agent follows without reading anything else.

pip install "jevmod[mcp]"
# Claude Code: plugin (two skills + the MCP server)
/plugin marketplace add ohernandezdev/jevmod
/plugin install jevmod@jevmod
# or only the MCP server
claude mcp add jevmod -- jevmod mcp
# Cursor, Codex, others
{ "mcpServers": { "jevmod": { "command": "jevmod", "args": ["mcp"], "env": { "TYPESAFE_API_KEY": "${TYPESAFE_API_KEY}" } } } }

Paste this into Claude Code, Cursor or Codex

Add content moderation to this repository with jevmod (https://github.com/ohernandezdev/jevmod, MIT, powered by TypeSafe's Jev). If you are Claude Code, first run /plugin marketplace add ohernandezdev/jevmod and /plugin install jevmod@jevmod, then use the jevmod-integrate skill; otherwise read https://raw.githubusercontent.com/ohernandezdev/jevmod/main/AGENTS.md and follow its integration recipe. Detect the stack and pick the surface (Python SDK: pip install jevmod; npm: npm install jevmod; or the HTTP API POST /v1/moderate); store the key with jevmod init or TYPESAFE_API_KEY in the environment, never in a committed file; insert one Moderator().check(text, channel_topic=...) call at the single point where user text enters the system; decide fail-open or fail-closed explicitly around TypeSafeError; keep the defaults flag-only; add a real test that skips without the key and asserts d.category with d.probability >= 0.7 on the scam sample and d.action == "none" on a clean one; finally run jevmod check "FREE NITRO for the first 100!! claim at discord-gifts.ru/nitro" (expect exit 1) and jevmod check "gg everyone, same time tomorrow?" (expect exit 0) and show me the diff before committing.

Full reference: README · AGENTS.md · llms.txt · examples/ · Postman collection · OpenAPI · Disclaimer

From the benchmark.

Twelve of the 2,531 messages, with the probabilities Jev gave them in the benchmark run. Click one to see what the default policy does with it. 12 items inlined from benchmark/results/jevmod.jsonl; this part sends nothing from this page.

    The tick on each bar is the default threshold. Default action is flag; delete and timeout are opt-in per category.

    For live checks: pip install jevmod, set TYPESAFE_API_KEY, run jevmod check "your text". Jev's probabilities move about 0.03 between runs, so a message within that band of a threshold can flip. Above 0.9 the probabilities match observed rates within a few points; between 0.5 and 0.85 they run high, which is why the shipped thresholds sit mostly at 0.75 to 0.85 (minors 0.70, off-topic 0.90).

    Against three local classifiers.

    2,531 public messages with human labels, the same items for every system. AUROC is threshold-free: 0.5 is a coin flip, 1.0 is a perfect ranking. On OpenAI's human-labelled set jevmod has the best AUROC in every category it was compared on (harassment and sexual content against three models; self-harm and minors against Llama Guard, the only other system with those labels); on Civil Comments toxic-bert wins, having been trained on it, and ties ShieldGemma at 0.939 on "any category" of the OpenAI set. Generated 2026-09-18 by benchmark/report.py.

    Swipe the table sideways for the other systems.

    setcategoryjevmodLlama Guard 3 8BShieldGemma 2Btoxic-bert
    OpenAI moderation, 1,680harassmentOpenAI moderation, 1,6800.9300.8050.9140.807
    nsfw0.9820.8430.9680.876
    self-harm0.9920.891no policyno label
    minors0.9770.590no policyno label
    any category0.9390.9210.9390.884
    Civil Comments, 351harassmentCivil Comments, 3510.8750.5390.8740.973
    any category0.8760.5920.8570.973
    YouTube spam, 500spamYouTube spam, 5000.9940.500no policyno label
    any category0.9600.7550.5890.418
    cost per 1,000 messagescost / 1,000$0.042$0.004$0.011$0.0006
    latency per messagelatency22 ms, batched 2549 ms130 ms8 ms

    Cost. jevmod: about 1,005 input tokens per judged message with seven categories, $0.042 per 1,000 judged messages at Jev's list price of $0.042 per million tokens. Local models: GPU time on an RTX 5080 at $0.30 per hour, from measured latency; hardware, hosting and your time are not in that number.

    Where jevmod loses. toxic-bert was trained on Civil Comments and wins there by a wide margin. Llama Guard returns a hard label, so its per-category AUROC is close to a coin flip by construction; compare it on the "any category" row. ShieldGemma has policies for harassment and sexual content only. YouTube labels are loose: many "spam" comments are plain self-promotion.

    Not measured, for scale: a general LLM as judge on the same text, 1,005 tokens x 1.2 prompt overhead x $1 per million input tokens (Claude Haiku 4.5 list price), is about $1.2 per 1,000 messages before output tokens.

    The full table with F1 at default thresholds, precision and recall is in BENCHMARK.md. A separate 98-message red-team suite (unicode evasion, Spanish, Portuguese, French, German, Russian and Japanese, prompt injection, gaming slang) runs as a regression test against the real API.

    One service, one request per batch.

    Every surface is an adapter over the same core. Messages that a local rule can settle never leave the machine; the rest go to Jev together, and every probability lands in the audit log.

    Architecture: Discord, Telegram, Reddit, CLI, Python, npm, HTTP and MCP feed one ModerationService, which calls the Judge, which sends one request to Jev. A SQLite store keeps policy, usage and the audit log.
    Only the message text and the channel topic are sent to Jev. Author names and ids stay local. open diagram
    Request flow: message, prefilter, cache, batch for two seconds, one Jev request, probabilities, policy, action, audit log. Messages under eight letters, from trusted authors or already judged are not sent. If Jev is unreachable the action is none with reason error_open.
    Text is NFKC-normalised and stripped of combining marks before judging and caching, so fullwidth, zalgo and zero-width tricks meet the same thresholds as plain text. open diagram
    Coverage matrix: spam, scam, harassment, nsfw, selfharm, doxxing, minors and custom rules are on by default on every surface; offtopic is available and turns on when a channel topic is set.
    8 categories (7 on by default; off-topic turns on when you set a channel topic). The questions live in one file, so every implementation asks Jev exactly the same thing. open diagram
    Failure policy: if Jev is unreachable, jevmod fails open, acts on nothing and logs one warning per batch. If the optional monthly quota (JEVMOD_MONTHLY_QUOTA, unlimited by default) is reached, judging pauses, the owner is told once, and nothing is deleted.
    Nothing is deleted while Jev is down or a quota pause is on. The quota is off unless you set JEVMOD_MONTHLY_QUOTA. Flag-only is the default until you turn on delete or timeout. open diagram

    What leaves your machine

    • The message text and the channel topic, sent to TypeSafe's API for judgment. Their terms apply to that service.
    • Nothing else. Author names and ids are never sent. There is no telemetry.
    • Locally, a decision log with category, probabilities, action and the first 300 characters of the text, kept 30 days. /mod forget or DELETE /v1/tenant removes everything at once.
    • Members whose message is removed get a direct message saying an automated system did it and how to appeal to the moderators.

    What happens when it fails

    • Jev unreachable: decisions come back with reason="error_open", nothing is acted on, one warning per batch is logged.
    • Optional monthly quota reached (JEVMOD_MONTHLY_QUOTA, unlimited by default): judging pauses, the owner is told once, nothing is deleted while paused.
    • A wrong call: react on the log entry and the threshold for that category moves a notch, so the bot follows your community's line.
    • Decisions are probabilistic. You are responsible for the thresholds and actions you configure and for the rules of the platform you run on.