@botpoison/node
The server library for Botpoison. It does two jobs:
- Challenge: verify the solution a form sends, so a submission from a
script that skipped the puzzle is turned away. The browser side is
@botpoison/browser. - Classify: send a form's message and get back
spamorcleanwith a score, which catches the spam people type by hand. The model runs in Botpoison's own AWS account in Ireland, and Classify does not store the message to answer it.
It needs Node 18 or later and has no dependencies. Keys come from a project on botpoison.com, which is free to start, and one project's secret key works with both products.
npm install @botpoison/node
Challenge
const Botpoison = require("@botpoison/node");
// or: import Botpoison from "@botpoison/node";
const botpoison = new Botpoison({
secretKey: process.env.BOTPOISON_SECRET_KEY,
});
const { ok } = await botpoison.verify(req.body._botpoison);
if (!ok) {
return res.status(403).end();
}
verify rejects when Botpoison cannot be reached, and answers ok: false
with the message Internal server error when the fault is on Botpoison's
side. Decide what your form does with those submissions, and never accept one
for a missing _botpoison, which is what every bot sends. verify sets no
timeout of its own, so wrap it in one. See
the library reference.
Classify
Labels a form submission spam or clean, with a score.
const botpoison = new Botpoison({
secretKey: process.env.BOTPOISON_SECRET_KEY,
});
// The whole form, as your server received it.
const result = await botpoison.classify(req.body, { model: "spam-standard" });
// { label: "clean", score: 0.04, model: "spam-standard" }
if (result.label === "spam") {
// drop it
}
label is Botpoison's recommended cut on score: spam from 0.8, clean
below it, so the two never disagree. Acting on label is the default.
score is for custom thresholds and review queues. It runs from 0 to 1 and is
not a probability. Two points on it stay fixed as the model is updated. The
spam label flags about 1 in 1,000 good messages at 0.8, and about 1 in 100
good messages score 0.5 or more, which makes 0.5 a common place to start
holding messages for a person. Both rates count all the traffic in our
reference set together; a single site's own rates are usually several times
higher, so log the score while you tune:
if (result.label === "spam") {
// drop it
} else if (result.score >= 0.5) {
// hold it for a person
}
A form with nothing written in it, such as an email-only sign-up, answers
clean with a score of 0 unless the model is very confident it is spam.
The first argument is the content: a string for a single message box, or an
object with one entry per form field. Send the whole form when you have it:
the name, email and other fields are signals too. Values may be strings,
numbers, booleans or lists of those; a nested object is refused. Fields whose
name starts with _ are ignored, so form controls such as _redirect can stay
in. A submission may hold up to 100 fields and 20,000 characters.
await botpoison.classify("Hi, do you ship to Belgium?", {
model: "spam-standard",
});
The second argument is required, with model and an optional timeout.
Models
model names the model to classify with. spam-standard is the only model
today. Botpoison updates it in place, and result.model names the model that
answered. An unknown model is rejected with a 400 that lists the valid ones.
Errors
classify rejects with a Botpoison.ClassifyError, whose code says what
failed:
code | Meaning |
|---|---|
http | The API answered with an error. status and message say why. |
timeout | No answer arrived in time. |
network | The request failed before an answer arrived. |
invalid_response | The API answered 200 with a body that is not a classification. |
body holds the parsed response body when there is one. A 503 means
classification is temporarily unavailable; decide whether your form fails open
or closed rather than retrying in a loop.
let result;
try {
result = await botpoison.classify(message, { model: "spam-standard" });
} catch (error) {
if (!(error instanceof Botpoison.ClassifyError)) throw error;
// Fail open: accept the submission unclassified, or queue it for later.
result = undefined;
}
TypeScript users can import the types: ClassifyContent, ClassifyFields,
ClassifyFieldValue, ClassifyOptions, ClassifyResult, ClassifyLabel,
ClassifyError and ClassifyErrorCode.
Timeout
The call is abandoned 8000 ms after it starts by default, however the time is spent. Override it per call:
await botpoison.classify(content, { model: "spam-standard", timeout: 3000 });
Testing
Use the secret key test. Content containing botpoison-test-spam is
labelled spam with a score of 1, botpoison-test-review is clean with
a score of 0.6, and botpoison-test-unavailable answers 503. Anything else is clean with a score of 0.