Workflows / JevModel
Spam Detection API for Forms and Comments: Turnstile, Akismet, or a Jev Decision Layer
Where a challenge (Cloudflare Turnstile), a spam-reputation service (Akismet), and a typed Jev Choice/Noul check each fit against contact-form, comment, and signup spam, with a quarantine queue instead of silent deletes.
Last updated:
Quick answer: three layers, and Jev is only the last one
- Form and comment spam comes in three kinds: automated bot submissions, known spam patterns, and human-written content you still do not want (off-topic sales pitches, link drops). Each needs a different tool.
- Challenge layer: Cloudflare Turnstile only protects a form if your server validates each token with the Siteverify API; tokens are valid for 300 seconds and can be validated once.
- Reputation layer: Akismet’s comment-check takes api_key, blog, and user_ip (required) plus content and author fields, and answers "true" (spam) or "false"; any other body is an error, not a verdict.
- Decision layer: JevModel reads only the text you send and returns typed answers, such as a Choice between real_inquiry / sales_pitch / spam and a Noul probability for “needs a human look”. It has no IP reputation or shared spam database, so use it after the first two layers and quarantine rather than delete.
Three kinds of form and comment spam
“Spam detection API” covers different problems, and mixing them up is how real leads get deleted. First, automated submissions: scripts that fill every form they find. These are best stopped before the text is ever read, with a challenge such as Cloudflare Turnstile. Second, known spam: messages and senders that match patterns seen across many sites, which a reputation service such as Akismet is built for. Third, human-written messages you still do not want: a cold sales pitch through your contact form, an SEO agency offer under a blog post, a signup whose bio is only a link. They pass a challenge because a person sent them and may not look like classic spam, so the decision depends on your own policy. That third kind is the only one where a text-decision model like Jev adds something. This guide covers forms, comments, and signups; for spam in an inbox, leave filtering to your mail provider, as the email classification guide explains.
Use Jev for review queuesEmail Classification with AI: Rules, LLM Prompts, or a Typed Decision API for Inbound MailScore leads on a useful rubricLayer 1: block automated submissions with Cloudflare Turnstile
Turnstile puts a widget on the form that produces a token, and Cloudflare’s documentation is explicit that server-side validation is mandatory: the client widget alone does not protect anything. Your handler sends POST https://challenges.cloudflare.com/turnstile/v0/siteverify with your secret key and the token (the visitor IP is optional) and reads the JSON success field. Tokens are valid for 300 seconds, can be validated only once (a replay returns timeout-or-duplicate), and are at most 2048 characters. Reject the submission when validation fails, before you spend anything else on it. This layer says nothing about what the message means; it only answers whether a browser session passed the challenge.
Layer 2: check known spam with Akismet
Akismet’s comment-check endpoint, POST https://rest.akismet.com/1.1/comment-check, takes form-encoded fields. api_key, blog (your site URL), and user_ip are required; comment_content, comment_author, comment_author_email, user_agent, and comment_type are optional, and Akismet’s docs say more data generally means better accuracy. comment_type is not limited to blog comments: documented values include comment, forum-post, reply, contact-form, signup, and message. The response body is the literal string "true" for spam or "false" for not spam; anything else, such as "invalid", is an error and can arrive with HTTP 200, so do not treat it as a verdict. An X-akismet-pro-tip: discard header marks blatant spam you can drop without review. For testing, Akismet documents comment_author akismet-guaranteed-spam (always spam), user_role administrator (never spam), and is_test=1 to mark test calls.
Evaluate a Jev workflowLayer 3: a Jev decision for messages that are human but unwanted
What is left after layers 1 and 2 is mostly a policy question: is this a real inquiry, a sales pitch, or spam by your definition? JevModel answers that as typed output. You send the message as state and ask named questions: a Choice whose criteria define real_inquiry, sales_pitch, and spam in your own words, and a Noul (yes/no) question such as “a person should look at this before it is dropped”. The response holds the chosen label with a probability per label and a probability for the Noul, which you can route on. Be clear about what this layer is not. Jev reads only the state you send: it has no IP or sender reputation, no shared database of spam seen on other sites, and JevModel does not train a model on your reports; custom training is not offered. It does not replace a challenge or Akismet, and it should not be the only reason a message is deleted.
Choice, Score, and NoulJev probability, confidence and human-review thresholdsIntent Classification for Product & Support Text: Rules, LLMs, and Typed APIsTurnstile vs Akismet vs Jev: what each layer answers
The table compares inputs and output contracts from each vendor’s documentation. It is not an accuracy benchmark; measure any layer on your own submissions.
Jev or rules?How to classify text with the JevModel API| Dimension | Cloudflare Turnstile | Akismet | JevModel (Jev) |
|---|---|---|---|
| Question answered | Did this browser session pass a challenge? | Does this content and sender look like known spam? | Which of your labels fits this text, by your definitions? |
| Input | Widget token + secret key (visitor IP optional) | api_key, blog, user_ip required; content, author, type optional | Only the state you send, plus named questions |
| Output | JSON with success and error codes | "true" / "false"; other bodies are errors; optional discard hint | Typed answers: Choice with per-label probabilities, Noul probability, Score |
| Uses signals beyond the text | Yes, browser challenge signals | Yes, IP and sender fields you pass | No, nothing outside the state |
| Best place in the flow | First: reject before reading | Second: drop or flag known spam | Last: sort what remains into your own queues |
Code example: a contact-form handler with a Jev decision
The sketch below runs after Turnstile and Akismet have passed the submission. It sends the message as an object state, so the model sees which field is which, and asks two questions in one request: a Choice for the kind of message and a Noul for whether a person should review it. It follows the documented contract: POST /api/v1/systemone with a Bearer key from a verified account, state plus named questions, and a response with code 0 and data.answers. There is no model field because the site always calls typesafe/jev-1.13 through OpenRouter. The API has no idempotency key, so do not retry a request that may already have succeeded; a failed run is refunded. Send only the fields the decision needs: name, email, and phone usually add nothing to “is this a sales pitch”.
JevModel API: request and responseChoice, Score, and NoulAI Decision Flows with Jev and human review// Illustrative only. Runs after Turnstile and Akismet have passed the submission.
// JEVMODEL_API_KEY comes from JevModel Console -> API keys (verified account).
async function decide(form) {
const res = await fetch("https://jevmodel.app/api/v1/systemone", {
method: "POST",
headers: {
Authorization: "Bearer " + process.env.JEVMODEL_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
// Send only what the decision needs; no name, email or phone.
state: { form: "contact", subject: form.subject, message: form.message },
questions: {
kind: {
type: "choice",
instructions: "What kind of contact-form message is this, by our definitions?",
criteria: {
real_inquiry: "A person asking about our product, an order, or working with us as a customer.",
sales_pitch: "Someone selling us a service: SEO, leads, development, marketing.",
spam: "Gibberish, link dropping, scams, or text unrelated to any business purpose.",
},
},
review: {
type: "noul",
instructions: "Should a person read this before it is moved out of the inbox?",
criteria: {
true: "It might be a real customer or partner, or the intent is unclear.",
false: "It is clearly an unsolicited pitch or spam.",
},
},
},
}),
});
const body = await res.json();
if (body.code !== 0) return { route: "inbox", reason: body.message }; // fail open
const { kind, review } = body.data.answers;
return {
kind: kind.choice,
probabilities: kind.probabilities,
needsReview: review.noul, // probability that the answer is true
};
}
Routing: quarantine, do not delete
A false positive on a contact form is a lost customer you never hear about, so design the routing around that. A workable policy: real_inquiry goes to the inbox or CRM as usual; spam with a low review probability goes to a quarantine folder that someone skims on a schedule; sales_pitch goes to its own folder rather than the trash; anything with a high review probability, or where no label clearly wins, goes to the inbox with a tag. If the Jev call fails (a non-zero code, a timeout, INSUFFICIENT_TOKENS), fail open and deliver the message; a spam filter that eats leads when it is down is worse than none. Set the thresholds from your own data: label a few hundred past submissions by hand, run them through the same questions, and pick the point where wrong quarantines become rare enough for you. The Choice confidence TypeSafe describes, (p − 1/n)/(1 − 1/n), adjusts for the number of labels but is not calibrated accuracy. Re-check after changing definitions.
Jev probability, confidence and human-review thresholdsEvaluate a Jev workflowAI Decision Flows with Jev and human reviewChecklist before you turn it on
1) Validate Turnstile tokens on the server for every submission; never trust the widget alone. 2) Treat any Akismet body other than "true" or "false" as an error, and use is_test=1 plus the documented test values while building. 3) Write Jev criteria in your own words, with real examples of pitches you do not want, and keep a label for real inquiries that is at least as specific. 4) Strip personal fields that the decision does not need. 5) Quarantine instead of deleting, and fail open when any layer is unavailable. 6) Hand-label a sample, measure wrong quarantines, and re-measure after changes; do not publish an accuracy figure you have not measured. 7) Budget tokens: each submission that reaches layer 3 is one ordinary request, and verified accounts start with 100,000 free tokens. For moderation of user posts with a review queue, see content-moderation; for scoring sales leads that are real, see lead-scoring.
Use Jev for review queuesScore leads on a useful rubricJevModel pricing and free Jev runsPrivate runs and historyEmail Classification with AI: Rules, LLM Prompts, or a Typed Decision API for Inbound MailIntent Classification for Product & Support Text: Rules, LLMs, and Typed APIsJev probability, confidence and human-review thresholdsEvaluate a Jev workflowJevModel API: request and responseFrequently asked questions
Can Jev replace Akismet as a spam detection API?
No. Akismet uses the IP and sender fields you pass and spam patterns it sees across many sites; Jev reads only the text you send and has no shared spam database or IP reputation. Use Jev after a challenge and a reputation check, for the policy question of whether a human-written message is a real inquiry, a pitch, or spam by your definitions.
Should I use Jev to filter email spam?
No. Leave inbox spam filtering to your mail provider, which sees sender authentication and reputation signals Jev does not. Jev is useful after delivery, for routing legitimate mail into your own queues; see the email classification guide.
How do I avoid deleting real leads?
Quarantine instead of deleting, give sales pitches their own folder, send anything with a high review probability to a person, and fail open when a layer errors. Set thresholds from a hand-labeled sample of your own past submissions and recheck them after you change definitions.
Does JevModel see the visitor’s IP or learn from my spam reports?
It sees only the state you send. If you do not put an IP, email, or name in it, JevModel does not have them. There is no feedback endpoint for spam reports and no custom training; improve results by refining your label definitions. Runs are kept in your history, which the privacy guide explains how to remove.
JevModel is independent and not affiliated with TypeSafe AI.