Classify migration guide

# Migrate from Akismet to Botpoison Classify

Three edits: swap the API key, point the same form-encoded request at Classify, and read a JSON label instead of a "true" or "false" body. Your handler keeps its routes, its fields and its inbox.

Last verified 16 October 2026

## Why sites move off Akismet

Akismet's free Personal plan is for personal sites. A business or commercial site needs a paid plan.

comment-check answers in plain text: `true` for spam, `false` for not, and anything else is an error that still arrives with status 200. There is no score, so there is nothing to hold a borderline message on.

Akismet grew up on blog comments, and its parameters still say so: `blog`, `comment_author`, `comment_content`. Classify takes the form as it arrived, whatever its fields are called.

## What maps to what

|  | Akismet | Classify |
| --- | --- | --- |
| Key | `api_key` in the body | A secret key, as a bearer token, an `X-Api-Key` header or `secretKey` |
| Endpoint | POST to `/1.1/comment-check` on rest.akismet.com, form-encoded | POST to `/classify` on api.botpoison.com, form-encoded, as now; JSON also works |
| What you send | `user_ip`, `comment_author`, `comment_author_email`, `comment_content` and more | The same form fields, without `user_ip`, `blog` or `comment_type` |
| Answer | `true` or `false` as text, and a pro-tip header for blatant spam | `label`, spam or clean, a `score` from 0 to 1, and the `model` |
| Errors | Status 200, with some other text in the body | A 4xx or 5xx status, with a JSON message |

## The three edits. Off Akismet, onto Classify

### Swap the key

Replace the Akismet API key with a Classify secret key, kept in your server's environment like the old one.

Shell

```diff
-AKISMET_KEY=xxxxxxxx
+BOTPOISON_CLASSIFY_SECRET_KEY=sk_xxxxxxxx
```

### Point the request at Classify

Keep the form-encoded request and its comment fields. Swap the URL and the key, name the model in _model, and drop blog, user_ip and comment_type.

Node.js:

JavaScript

```diff
-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,
+    _model: "spam-standard",
     comment_author: req.body.name,
     comment_author_email: req.body.email,
     comment_content: req.body.message,
   }),
 });
```

PHP:

PHP

```diff
-$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"),
+    "_model" => "spam-standard",
     "comment_author" => $_POST["name"],
     "comment_author_email" => $_POST["email"],
     "comment_content" => $_POST["message"],
   ]),
 ]]));
```

Python:

Python

```diff
-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"],
+    "_model": "spam-standard",
     "comment_author": request.form["name"],
     "comment_author_email": request.form["email"],
     "comment_content": request.form["message"],
 })
```

Go:

Go

```diff
-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")},
+  "_model":               {"spam-standard"},
   "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
 }
```

HTTP:

Shell

```diff
-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 "_model=spam-standard" \
   --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.

Node.js:

JavaScript

```diff
-const isSpam = (await akismet.text()) === "true";
+const isSpam = (await classify.json()).label === "spam";
 if (isSpam) return fileAway(req.body);
```

PHP:

PHP

```diff
-$is_spam = $akismet === "true";
+$is_spam = json_decode($classify)->label === "spam";
 if ($is_spam) { file_away($_POST); exit; }
```

Python:

Python

```diff
-is_spam = akismet.text == "true"
+is_spam = classify.json()["label"] == "spam"
 if is_spam:
     return file_away(request.form)
```

Go:

Go

```diff
-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
 }
```

HTTP:

Shell

```diff
-# The answer, as text: true for spam, false for clean
-true
+# The answer, as JSON: spam or clean as the label, and the score
+{"label": "spam", "score": 0.8, "model": "spam-standard"}
```

## What behaves differently

A score instead of a hint. Akismet's `X-akismet-pro-tip: discard` marks the blatant cases. Classify's score does that job with a number you can start a review queue from.

An outage answers 503. It is not counted. Deliver the message as clean, so an outage never loses a real one.

## Questions

**Is there a WordPress plugin?**

No. Classify is an API, so it fits wherever your own code handles the submission: a form handler, a comment route, a signup.

**Is there a test value, like akismet-guaranteed-spam?**

Yes. With the secret key `test`, a message containing `botpoison-test-spam` answers spam and anything else answers clean. Test calls never count. See [test mode](https://botpoison.com/documentation/classify/test-mode/).

**Moving from OOPSpam instead?**

See the guide [from OOPSpam](https://botpoison.com/migrate/from-oopspam/).

## Sources

- [comment-check](https://akismet.com/developers/detailed-docs/comment-check/) Akismet developer documentation
- [Akismet pricing](https://akismet.com/pricing/) Akismet

Akismet is a trademark of Automattic. Botpoison is not affiliated with Automattic.

## Other migration guides

### To Challenge

Invisible CAPTCHA alternative

- [From reCAPTCHA](https://botpoison.com/migrate/from-recaptcha/)
  v2 checkbox, v2 invisible or v3 score. Swap the script, the key and the siteverify call.
- [From Turnstile](https://botpoison.com/migrate/from-turnstile/)
  Drop the cf-turnstile widget. The check runs fresh on each Send and moves to one verify call.
- [From hCaptcha](https://botpoison.com/migrate/from-hcaptcha/)
  Remove the h-captcha div and the form-encoded siteverify request. Nobody labels images again.

### To Classify

Spam classifier

- [From OOPSpam](https://botpoison.com/migrate/from-oopspam/)
  A score from 0 to 6 against your threshold becomes a label, with the score kept for a review queue.
