Last updated: September 2026
Content moderation with Jev
You declare one typed question per policy rule, send the message once, and get a probability for every rule back in a single parallel pass. Seven rules plus a severity level works out on paper to roughly $0.04 per 1,000 messages, estimated from the request body below at TypeSafe's published $0.042 per 1M input tokens, with output tokens free. That price is what makes a first-pass filter over all submitted content affordable at all.
What follows is the whole build: designing the category list, choosing between one Noul per rule and one Choice across categories, setting thresholds, running the human review queue, and the token math behind the cost. Then the parts Jev cannot do, because there are several and they matter.
Step 1. Write the category list as testable statements
This is the step people skip, and it is the step that decides whether the rest works. Jev reads instructions literally. A rule phrased as a topic produces a vague probability. A rule phrased as a sentence that is either true or false about a piece of text produces a usable one.
- One statement, one rule. Not "spam and self-promotion". Those are two rules with two different consequences.
- Phrase it so true means "this breaks the policy". TypeSafe's own limitations page warns that instructions contradicting the criteria degrade the answer, and that high values should mean yes.
- Put the boundary case in
criteria. A Noul takes an optionalcriteriaobject withtrueandfalsekeys. The false key is where you earn your accuracy: it is where you say that a founder linking their own product is not spam. - Avoid indirection. Double negatives, nested references and multi-hop conditions are all documented by TypeSafe as degrading answer quality. Split them into separate questions instead.
Prior art with a preset list worth stealing
The Sweep browser extension ships seven preset rules for X, Reddit, LinkedIn and Hacker News: AI reply, slop, engagement bait, crypto, brag, rage bait, only high quality. It sends one Noul per rule, batched into one request per post, and blurs anything over threshold with a label showing the rule and the probability. It is the clearest existing example of the pattern on this page, and its reported cost of about $0.02 per 1,000 posts with three rules matches the token math below.Step 2. One Noul per rule, or one Choice across categories
Both work. They fail differently, and the difference is not about accuracy. It is about whether your categories compete for probability mass.
For moderation the answer is usually Nouls, because real policy violations co-occur. The message in the example below is spam and a scam, and a Choice would have forced the model to pick one and would have reported low confidence for doing so. Keep a Choice for the routing decision that comes after: which queue, which team, which severity tier. You can send both in the same request, because every declared question is evaluated in one pass.
One detail that trips up almost every third-party write-up: a Noul answer contains only the noul float. There is no confidence field on it. Only Choice and Score return confidence. For a Noul, the probability is the signal, and uncertainty shows up as a value near 0.5.
Step 3. The real request
Seven rules and one severity Score, one round trip, against the pinned model id. Pin jev-1.13.0 rather than jev-latest in production, so your calibrated thresholds do not drift the day the alias moves.
{
"state": {
"message": "Free airdrop, first 100 wallets only. Connect here before it closes.",
"surface": "community reply"
},
"model": "jev-1.13.0",
"questions": {
"spam": {
"type": "noul",
"instructions": "The message is unsolicited bulk promotion, or exists mainly to drive clicks to an external destination.",
"criteria": {
"true": "Referral links, giveaway or airdrop bait, follow-for-follow, copy that reads as mass-posted.",
"false": "A person talking about their own work, even when it names a product or links to it."
}
},
"scam": {
"type": "noul",
"instructions": "The message tries to obtain money, credentials, or wallet access under a false premise.",
"criteria": {
"true": "Fake support, seed-phrase or wallet-connect requests, guaranteed returns, impersonation of a brand.",
"false": "A real paid offer with an identifiable seller, even a badly written one."
}
},
"harassment": {
"type": "noul",
"instructions": "The message attacks, demeans, or threatens a specific person or a group.",
"criteria": {
"true": "Slurs, threats, sustained insults aimed at someone, calls for others to pile on.",
"false": "Blunt criticism of an argument, a product, or a company."
}
},
"sexual_content": {
"type": "noul",
"instructions": "The message contains explicit sexual description or solicitation."
},
"self_harm": {
"type": "noul",
"instructions": "The message describes the author's own intent to self-harm, or encourages another person to."
},
"doxxing": {
"type": "noul",
"instructions": "The message publishes private identifying information about a person without consent.",
"criteria": {
"true": "Home address, phone number, employer plus real name, private account handles.",
"false": "Information the person publishes about themselves on the same platform."
}
},
"off_topic": {
"type": "noul",
"instructions": "The message is unrelated to the community's stated subject, which is self-hosted software."
},
"severity": {
"type": "score",
"instructions": "How much harm this message does if it stays visible for another hour",
"criteria": [
"None. Even if it breaks a rule, nobody is harmed by the delay.",
"Low. Clutter or mild annoyance.",
"Moderate. Someone will lose money, be targeted, or leave.",
"Severe. Immediate risk to a person's safety or finances."
]
}
}
}curl -X POST https://api.typesafe.ai/v1/systemone \
-H "Authorization: Bearer $TYPESAFE_API_KEY" \
-H "Content-Type: application/json" \
-d @request.jsonAn illustrative response in the documented shape. The values are written to show the fields, not captured from a call. Note the shape difference: every Noul is a bare float, the Score carries its legend, its full distribution and a confidence number.
{
"model": "jev-1.13.0",
"answers": {
"spam": { "type": "noul", "noul": 0.981 },
"scam": { "type": "noul", "noul": 0.944 },
"harassment": { "type": "noul", "noul": 0.004 },
"sexual_content": { "type": "noul", "noul": 0.001 },
"self_harm": { "type": "noul", "noul": 0.001 },
"doxxing": { "type": "noul", "noul": 0.002 },
"off_topic": { "type": "noul", "noul": 0.612 },
"severity": {
"type": "score",
"score": 2.09,
"legend": {
"0": "None. Even if it breaks a rule, nobody is harmed by the delay.",
"1": "Low. Clutter or mild annoyance.",
"2": "Moderate. Someone will lose money, be targeted, or leave.",
"3": "Severe. Immediate risk to a person's safety or finances."
},
"probabilities": { "0": 0.01, "1": 0.17, "2": 0.54, "3": 0.28 },
"confidence": 0.688
}
},
"usage": { "input_tokens": 948, "output_tokens": 112 }
}Do not copy the request shape from secondary sources
Several widely-shared write-ups useoptions for a Choice and min and max for a Score. Those fields do not exist. The real API uses criteria: a map for Choice, an ordered array of 2 to 10 levels for Score, and an optional true and false object for Noul. The API reference is the only shape to trust.Step 4. Two thresholds per rule, not one
TypeSafe refuses to publish universal numbers, and it is right to. Their guidance is to start conservative, test against your own data and adjust as you observe results. What generalises is the structure: every rule needs a high cut point above which software acts, and a lower one above which a person looks. The band between them is your review queue, and its width is a budget decision.
// Two cut points per rule, not one. The band between them is the human queue.
const RULES = {
scam: { hide: 0.90, review: 0.55 },
doxxing: { hide: 0.85, review: 0.45 },
harassment: { hide: 0.92, review: 0.60 },
self_harm: { hide: 1.01, review: 0.40 }, // never auto-hide, always a person
sexual_content: { hide: 0.95, review: 0.65 },
spam: { hide: 0.97, review: 0.70 },
off_topic: { hide: 1.01, review: 0.80 }, // review only, never auto-hide
} as const;
type Action = 'hide' | 'review' | 'allow';
export function decide(answers: Record<string, { noul: number }>, severity: number) {
const hits = Object.entries(RULES)
.map(([rule, t]) => ({ rule, p: answers[rule].noul, t }))
.filter((h) => h.p >= h.t.review);
if (hits.length === 0) return { action: 'allow' as Action, hits, severity };
const action: Action = hits.some((h) => h.p >= h.t.hide) ? 'hide' : 'review';
// Queue order is severity first, then how sure the model was. Both come from
// the same request, so ranking the queue costs nothing extra.
const priority = severity * Math.max(...hits.map((h) => h.p));
return { action, hits, severity, priority };
}Two things in that snippet are deliberate. A cut point of 1.01 means the rule can never auto-action, because no probability reaches it: that is how you express "always a human" without a second code path. And priority multiplies severity by the strongest rule hit, so the queue sorts itself from data you already paid for in the same request.
How to pick the actual numbers: label 200 messages by hand, run them through the exact ruleset, then plot your false-positive and false-negative rates against the returned probability for each rule separately. Do not transpose a threshold between rules. Doxxing at 0.85 and spam at 0.85 are unrelated statements about unrelated distributions, and TypeSafe explicitly warns against assuming structural relationships hold across separate questions. Full method on setting a Jev confidence threshold.
Step 5. The human review queue
The queue is the product. A moderation pass that auto-actions everything is a liability, and one that queues everything saves nobody any time. Four things make the queue work:
- Show the reason and the number. Sweep labels a blurred post with the rule name and the probability, for example "AI reply 94%". A moderator who can see scam 0.62 reviews faster than one who sees "flagged".
- Log the pinned model id with every decision. Store
jev-1.13.0plus the fullanswersobject and your thresholds at the time. Without the version, an audit six months later cannot reproduce anything. - Sample the auto-actioned path too. Review 1% of what was hidden without a human. False positives never appear in the queue by definition, so sampling is the only place you find them.
- Re-score history when you change the ruleset. Adding a rule is cheap. At $0.042 per 1M input tokens, re-running 100,000 archived messages through a new ruleset costs an estimated $4 at the published list price, so there is no reason to guess whether a rule change would have helped.
Ship it in shadow mode first
Keep your existing behavior, run Jev alongside it, and log full answers for a week without changing a single outcome. Then compare against what your moderators actually did. This is the single highest-value hour in the whole build, and it is the step teams skip because the demo looked convincing.The cost at scale, with the token math
Moderation is the use case where price decides feasibility, because the first pass has to run on everything, not on a sample. Here is where the tokens actually go for the request above.
Token counts are estimates for the exact request body shown above. The usage.input_tokens value in the illustrative response is 948, which is itself an estimate, not a captured run. Price from docs.typesafe.ai/models, September 2026.
950 input tokens at $0.042 per 1M is $0.0000399 per message. That is about $0.04 per 1,000 messages and $40 per million. Output tokens are free, so the response costs nothing regardless of how many rules you declared.
The lever is not the message length. It is the ruleset: an estimated 74% of every request is rule text you re-send every time. Trim from seven rules to three and you land near 460 input tokens, about $0.02 per 1,000, which is exactly what Sweep reports for its three-rule configuration. Adding an eighth rule costs an estimated $0.004 per 1,000 messages forever. That is the number to weigh when someone asks for a new category.
For comparison, the free post scorer at will it go viral sends seven questions on a short draft. Same published price sheet, smaller state, fewer rules, and you can run it yourself for free and without signing up.
Latency is the other half of feasibility. TypeSafe reports 70ms to 500ms end to end in its launch post, and the only independent test so far, Mike Taylor's at Every, reported 777 judgments back in under 0.7 seconds for an estimated quarter of a cent. His accuracy result is the part worth keeping in view: Jev caught 6 of 7 planted defects where Claude Fable 5.1 caught 7 of 7, and he called it useful "as an early warning system" rather than a verdict. That is the right mental model for a moderation first pass.
What Jev cannot do here
All six of these are documented by TypeSafe, not inferred. A moderation system built without accounting for them will fail in production, quietly.
The one that surprises people most is the first. Jev is text only: no image, audio or video input, per the model docs. On a social surface that means the majority of the hardest moderation cases, the screenshot, the meme, the image with text baked in, never reach your rules at all. Handle them elsewhere or accept the gap explicitly.
The second most important is the one people repeat wrongly. "Jev cannot hallucinate" is a statement about schema conformance, not about factual accuracy. A rule you declared as a Noul always comes back as a float between 0 and 1, and that is guaranteed by construction. It can still be the wrong float. TypeSafe's own launch post says the 0% figure "is not empirical", and its CEO acknowledged on Hacker News that a schema-valid answer can be factually wrong. Zero hallucinations is not zero errors.
And the one that is a design principle rather than a bug: a probability TypeSafe describes as calibrated is not a policy decision. Jev returns how likely it thinks the statement is true. Whether 0.82 justifies removing a post, warning an author or banning an account is yours, and the three should not share a threshold. Hiding a reply is reversible in a click. A ban is not.
Then publish the things that survive
A moderation pass produces two outputs: a list of things to hide, and a list of things worth responding to. Jev decides. An LLM writes the response, because Jev generates no text at all. OpenTweet ships it.
The publish call is one request to /api/v1/posts, which targets X, Bluesky and LinkedIn from the same body:
curl -X POST https://opentweet.io/api/v1/posts \
-H "Authorization: Bearer ot_your_key" \
-H "Content-Type: application/json" \
-d '{
"text": "New community rules are live. Shorter, and enforced the same way for everyone.",
"publish_now": true,
"platforms": ["x", "bluesky", "linkedin"]
}'For the triage side of the same workflow, see Jev for community management, which covers bucketing incoming replies and mentions, the replies feed they land in, and the honest constraint on reply delivery. For the account-level signals Jev cannot see, see spam and bot detection.
7-day free trial. Cancel anytime.
Frequently asked questions
Can Jev do content moderation?
Yes, for text. You declare one typed question per policy rule and Jev returns a probability for each in a single parallel pass. It cannot moderate images, audio or video, because Jev accepts text only. It also cannot write a takedown notice or an appeal response, because it generates no text at all.
How much does it cost to moderate 1,000 messages with Jev?
Estimated at about $0.04 with seven rules plus a severity level, at roughly 950 input tokens per message and TypeSafe’s published $0.042 per 1M input tokens. Output tokens are free. Cut the ruleset to three rules and the estimate drops to about $0.02 per 1,000, which is the figure the Sweep browser extension reports for its own three-rule setup.
Should I use one Noul per rule or one Choice across categories?
One Noul per rule when a message can break several rules at once and each rule needs its own threshold. One Choice when you need exactly one destination, such as which review queue a report goes to. Nouls are independent, so adding an eighth rule does not move the other seven. Choice probabilities sum to 1 across the options, so adding an option reshuffles the whole distribution and your old thresholds no longer mean what they meant.
What confidence threshold should I use for moderation?
There is no universal number and TypeSafe declines to publish one. A Noul answer has no confidence field at all, only the probability itself, so the uncertain region is the band around 0.5 rather than a separate signal. Set two cut points, not one: a high one above which you act automatically, and a lower one above which you queue for a human. Pick both from your own labeled data.
Does a probability from Jev mean Jev decides my policy?
No. Jev returns how likely it thinks a statement is true about the text. Whether 0.82 is enough to remove a post is a policy decision you own, and it should differ by how reversible the action is. Hiding a reply and banning an account deserve different cut points even when they read the same rule.
Can Jev hallucinate a moderation label?
It cannot return a value outside the schema you declared, so a rule you defined as a Noul always comes back as a float between 0 and 1. It can still be wrong about the text. TypeSafe says the 0% hallucination figure is not empirical and its CEO has acknowledged that a schema-valid answer can be factually incorrect.
Keep exploring
The rest of the decision layer, and the tool that already runs it.
Spam and bot detection with Jev
Combine text judgment with account age, follower ratio and link count. The composite scoring function.
Rage bait detection with Jev
A definition precise enough to score, the Noul rubric, and where the thresholds land.
Jev for community management
Triage replies and mentions into buckets, skip the noise, publish the answers through OpenTweet.
Choice, Score and Noul
The three primitives with real request and response bodies for each.
Setting a Jev confidence threshold
Why a Noul has no confidence field, and how to pick cut points from your own data.
Will it go viral, free post scorer
A free post scorer built on Jev. No signup.
Jev decides. OpenTweet publishes.
One REST API and 43 MCP tools for X, Bluesky and LinkedIn, from $11.99 a month. Bring your own judgment layer.
7-day free trial. Cancel anytime.