Classify
Spam classifier. Catch spam, whoever wrote it
Send a message, get back spam or clean. Our own model, trained on two years of real form submissions, reads what was written.
No credit card required.
Fig. 1Contact form, this morning
- I noticed your site is not on page one for your main keywords. We can fix that this month for a flat fee.0.8+Spam
- Hi, could you send a quote for 40 seats? We start in March.0.03Clean
- Your domain listing expires in 48 hours. Pay the renewal invoice at the link below to keep it.0.8+Spam
- Is there a nonprofit discount? Our site is in my signature.0.62HeldClean
- We build web apps for 40% less than local agencies. Can I send you our portfolio?0.8+Spam
Before and after. Eight messages in. Three worth reading
The same morning's inbox, once Classify has read it.
ABefore: 8 messages
- Marcus, SEO consultantI noticed your site is not on page one for your main keywords. We can fix that this month for a flat fee.
- Jordan LeeHi, could you send a quote for 40 seats? We start in March.
- Domain ServicesYour domain listing expires in 48 hours. Pay the renewal invoice at the link below to keep it.
- Alex MoreauIs there a nonprofit discount? Our site is in my signature.
- Priya, DevOffshoreWe build web apps for 40% less than local agencies. Can I send you our portfolio?
- Agentur WeberWir steigern Ihre Google-Bewertungen in nur 7 Tagen. Antworten Sie für ein kostenloses Angebot.
- Daniel KimThe download link in my receipt does not work. Order 10482.
- Lena K.Great article! I wrote about the same thing on my blog, worth a look for your readers.
BAfter: 3 to read
- Jordan LeeCleanHi, could you send a quote for 40 seats? We start in March.0.03
- Alex MoreauHeldCleanIs there a nonprofit discount? Our site is in my signature.0.62
- Daniel KimCleanThe download link in my receipt does not work. Order 10482.0.05
The other 5 carry the spam label. File them away, out of sight, in case you ever want to look.
How it works. One request between a form and a verdict
Your server sends the submission
POST the form's fields as they arrived, or one string. Your secret key goes in a header or the body.Classify reads it
A label comes back,spamorclean, with the score behind it and the model that answered.You decide what happens
Deliver clean messages, file spam away, and hold the borderline ones for a person if you want a review queue.
What gets through. Spam that reads like a real message
Some are typed by people, some by bots good enough to pass a test for humans. Classify reads what they say, so it catches both. Most arrive in English, not all of them.
SEO pitch
English- I noticed your site is not on page one for your main keywords. We can fix that this month for a flat fee.
- Spam
Scam
English- Your domain listing expires in 48 hours. Pay the renewal invoice at the link below to keep it.
- Spam
Cold outreach
English- We build web apps for 40% less than local agencies. Can I send you our portfolio?
- Spam
Link drop
English- Great article! I wrote about the same thing on my blog, worth a look for your readers.
- Spam
Reviews offer
German- Wir steigern Ihre Google-Bewertungen in nur 7 Tagen. Antworten Sie für ein kostenloses Angebot.
- Spam
Link selling
French- Bonjour, nous proposons des backlinks de qualité à prix réduit pour votre site.
- Spam
Written by us in the style of real submissions.
Never lose a lead. Put the borderline ones in front of a person
Spam from 0.8 is filed away. Below it, you choose where a review queue starts, and see what it will hold before you ship.
Choose your review queue
- Spam, filed away
- 0.8 and above
- Held for a person
- 0.5 to 0.8
- Delivered
- below 0.5
- Good messages held
- about 1 in 111
- At your volume
- about 5 a month
1,000 good messages
- Delivered
- Held for review
Rates across all the good form submissions in Botpoison's reference set. A single site's own rate is usually higher, most of all on a new site.
Private by design. Read in the EU, by our own model
Our own model
Runs in the EU
Nothing in the browser
Test mode
Switching. Off your spam filter in three edits
Swap the key, the request and the answer. Your form handler keeps its fields, its routes and its inbox.
Swap the key
Replace the Akismet API key with a Classify secret key, kept in your server's environment like the old one.
AKISMET_KEY=xxxxxxxxBOTPOISON_CLASSIFY_SECRET_KEY=sk_xxxxxxxxPoint the request at Classify
Keep the form-encoded request and its comment fields. Swap the URL and the key, and drop blog, user_ip and comment_type.
const akismet = await fetch("https://rest.akismet.com/1.1/comment-check", {const classify = await fetch("https://api.botpoison.com/classify", { method: "POST", body: new URLSearchParams({ api_key: process.env.AKISMET_KEY, blog: "https://example.com", user_ip: req.ip, comment_type: "contact-form", secretKey: process.env.BOTPOISON_CLASSIFY_SECRET_KEY, comment_author: req.body.name, comment_author_email: req.body.email, comment_content: req.body.message, }),});$akismet = file_get_contents("https://rest.akismet.com/1.1/comment-check", false, stream_context_create(["http" => [$classify = file_get_contents("https://api.botpoison.com/classify", false, stream_context_create(["http" => [ "method" => "POST", "header" => "Content-Type: application/x-www-form-urlencoded", "content" => http_build_query([ "api_key" => getenv("AKISMET_KEY"), "blog" => "https://example.com", "user_ip" => $_SERVER["REMOTE_ADDR"], "comment_type" => "contact-form", "secretKey" => getenv("BOTPOISON_CLASSIFY_SECRET_KEY"), "comment_author" => $_POST["name"], "comment_author_email" => $_POST["email"], "comment_content" => $_POST["message"], ]),]]));akismet = requests.post("https://rest.akismet.com/1.1/comment-check", data={ "api_key": os.environ["AKISMET_KEY"], "blog": "https://example.com", "user_ip": request.remote_addr, "comment_type": "contact-form",classify = requests.post("https://api.botpoison.com/classify", data={ "secretKey": os.environ["BOTPOISON_CLASSIFY_SECRET_KEY"], "comment_author": request.form["name"], "comment_author_email": request.form["email"], "comment_content": request.form["message"],})ip, _, _ := net.SplitHostPort(r.RemoteAddr)req, _ := http.NewRequest("POST", "https://rest.akismet.com/1.1/comment-check", strings.NewReader(url.Values{ "api_key": {os.Getenv("AKISMET_KEY")}, "blog": {"https://example.com"}, "user_ip": {ip}, "comment_type": {"contact-form"},req, _ := http.NewRequest("POST", "https://api.botpoison.com/classify", strings.NewReader(url.Values{ "secretKey": {os.Getenv("BOTPOISON_CLASSIFY_SECRET_KEY")}, "comment_author": {r.FormValue("name")}, "comment_author_email": {r.FormValue("email")}, "comment_content": {r.FormValue("message")},}.Encode()))req.Header.Set("Content-Type", "application/x-www-form-urlencoded")res, err := http.DefaultClient.Do(req)if err != nil { http.Error(w, "", http.StatusBadGateway) return}curl "https://rest.akismet.com/1.1/comment-check" \ --data-urlencode "api_key=$AKISMET_KEY" \ --data-urlencode "blog=https://example.com" \ --data-urlencode "user_ip=203.0.113.7" \ --data-urlencode "comment_type=contact-form" \curl "https://api.botpoison.com/classify" \ --data-urlencode "secretKey=$BOTPOISON_CLASSIFY_SECRET_KEY" \ --data-urlencode "comment_author=Jo" \ --data-urlencode "comment_author_email=jo@example.com" \ --data-urlencode "comment_content=Hello, could you send me a quote?"Read the label instead of the text body
Akismet answers "true" for spam in the response body. Classify answers JSON, with spam or clean as the label.
const isSpam = (await akismet.text()) === "true";const isSpam = (await classify.json()).label === "spam";if (isSpam) return fileAway(req.body);$is_spam = $akismet === "true";$is_spam = json_decode($classify)->label === "spam";if ($is_spam) { file_away($_POST); exit; }is_spam = akismet.text == "true"is_spam = classify.json()["label"] == "spam"if is_spam: return file_away(request.form)reply, _ := io.ReadAll(res.Body)isSpam := string(reply) == "true"var out struct{ Label string `json:"label"` }json.NewDecoder(res.Body).Decode(&out)isSpam := out.Label == "spam"if isSpam { fileAway(r.PostForm) return}# The answer, as text: true for spam, false for cleantrue# The answer, as JSON: spam or clean as the label, and the score{"label": "spam", "score": 0.8, "model": "standard-2026-10-10"}Better together. Classify reads the message. Challenge stops the bots first
Use one, or both. Together they stop bulk spam before it is sent, and read what still gets through after.
01Before the submit
Challenge
Invisible CAPTCHA alternative
Turns away scripts that skip the check, and makes every bot submission cost CPU time. Nobody sees a puzzle.
02After the submit
Classify
Spam classifier
Reads what got through and flags the pitches and scams in it, whoever wrote them.
Free to start. One plan covers both products
Every plan carries an allowance for each product. Using one never eats into the other, and no credit card is needed to start.
See plans and pricesclassifications a month
- Free
- 50
- Standard
- 2,500
- Plus
- 25,000
- Pro
- 250,000
In short. The facts about Classify
Botpoison Classify is a spam classifier API for form submissions. Your server sends the message and gets back spam or clean, with a score.
It runs Botpoison's own model, trained on two years of real form submissions.
The model runs in the EU (Ireland), on Botpoison's own infrastructure. No outside AI vendor sees the message.
The Free plan includes 50 classifications a month, with no credit card.
The spam label starts at a score of 0.8. It flags about 1 in 1,000 good messages at 0.8, counted across all the traffic in our reference set. A single site's own rate is usually higher. You choose a different trade-off by choosing a different score: the scale is published, and every pinned model is fitted to it.
A CAPTCHA asks whether a person is there. For an SEO pitch a person wrote, one is. Classify asks a different question: what does the message say.
The form's fields as a map, the way they arrived, or one string. Classify reads each field by what it holds, so field names do not matter.
A form with no free text, such as an email-only signup, answers clean with a score of 0. A review queue never holds a signup for having nothing in it.
The request returns a 503 and is not counted. The docs show the fallback: deliver the message as clean, so an outage never loses a real one.