@botpoison/node

Read the Botpoison docs page "@botpoison/node": https://botpoison.com/documentation/libraries/botpoison-node.md
The Botpoison agent skill is at https://botpoison.com/skills/botpoison/SKILL.md.
Use them to make this change in my project, on the stack it already uses. Ask me for keys or IDs rather than inventing them.

Install

npm install @botpoison/node

It needs Node.js 18 or later and has no dependencies. Load it with import or require: both return the class. classify() needs version 0.2.0 or later.

Creating an instance

Create an instance with your project's secret key, read from your server's environment. The same instance verifies solutions and classifies messages, because one key works with every product.

JavaScript
import Botpoison from "@botpoison/node";
// or const Botpoison = require("@botpoison/node")
// One instance for every product, with the project's secret key read from
// your server's environment.
const botpoison = new Botpoison({
secretKey: process.env.BOTPOISON_SECRET_KEY
});

verify(solution)

Verifies the _botpoison field a Challenge form sent. It resolves to { ok }: accept the form when ok is true, and reject it otherwise.

JavaScript
// 1. Import the library
import Botpoison from "@botpoison/node";
// or const Botpoison = require("@botpoison/node")
// 2. Create a new instance with your secret key, read from your server's
// environment: BOTPOISON_SECRET_KEY=sk_xxxxxxxx
const botpoison = new Botpoison({
secretKey: process.env.BOTPOISON_SECRET_KEY
});
server.post("/message", async (req, res) => {
const { message, _botpoison } = req.body;
// 3. Verify the solution
const { ok } = await botpoison.verify(_botpoison);
if (!ok) {
throw new Error("Invalid Botpoison solution");
}
// ...
});

It sends the key as secretKey in the body. It sets no timeout of its own, so wrap the call in one. A fault on our side answers ok: false with the message Internal server error. See If Botpoison is unreachable for what to accept during an outage.

classify(content, options)

Sends a submission to Classify and resolves to a verdict: { label: "spam" | "clean", score, model }. It sends the key as an Authorization: Bearer header, and the body holds only the content and the model.

JavaScript
// 1. Import the library
import Botpoison from "@botpoison/node";
// or const Botpoison = require("@botpoison/node")
// 2. Create a new instance with your secret key, read from your server's
// environment: BOTPOISON_SECRET_KEY=sk_xxxxxxxx
const botpoison = new Botpoison({
secretKey: process.env.BOTPOISON_SECRET_KEY
});
server.post("/contact", async (req, res) => {
// 3. Classify the whole form and read the label
const { label } = await botpoison.classify(req.body, {
model: "spam-standard",
timeout: 8000,
});
if (label === "spam") {
// Drop it, or file it away
return res.status(202).end();
}
// ...
});

Act on label, which is spam from a score of 0.8. model names the model that answered. See Labels and scores.

Content

The first argument is the content: the whole form as an object, such as req.body, or a string for a single message box. Send the whole form when you have it, since every field helps the verdict. A field's value may be a string, a number, a boolean, null, or a list of strings, numbers and booleans. A nested object is refused. Fields whose name starts with _ are ignored, so form controls such as _redirect can stay in. See the REST API for the limits.

Options

The second argument is required, since it names the model.

  • modelstring, required

    The model to classify with, by name: spam-standard is the only model today.

  • timeoutnumber, optional

    Milliseconds from the start of the call until it is abandoned, however the time is spent. Defaults to 8000.

An unknown model is rejected with a 400 that lists the valid models. See Models.

Without the library

Node.js 18 and later ship fetch, so you can call the REST API directly instead.

JavaScript
// Node.js 18 and later ship fetch, so there is nothing to install.
// The secret key comes from your server's environment:
// BOTPOISON_SECRET_KEY=sk_xxxxxxxx
const SECRET_KEY = process.env.BOTPOISON_SECRET_KEY;
// 1. Send the submission to Classify
const classify = async (content, model = "spam-standard") => {
const response = await fetch("https://api.botpoison.com/classify", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: `Bearer ${SECRET_KEY}`,
},
body: JSON.stringify({ content, model }),
});
// A refusal carries a JSON message, but a proxy in between may answer
// with something else, so the status is checked before the body is parsed.
if (!response.ok) {
const { message } = await response.json().catch(() => ({}));
throw new Error(message ?? `Classify answered ${response.status}`);
}
// { label: "spam" | "clean", score, model }
return response.json();
};
server.post("/contact", async (req, res) => {
// 2. Send the whole form, then read the label
const { label } = await classify(req.body);
if (label === "spam") {
// Drop it, or file it away
return res.status(202).end();
}
// ...
});

Errors

From verify()

verify() answers a rejected solution with ok: false rather than an error. It rejects when the request fails before an answer arrives, with the parsed response body when the API answers a status other than 200, and with an Error when the response is not JSON or a 200 carries no ok.

From classify()

classify() rejects with a Botpoison.ClassifyError. Its code says what went wrong, its status carries the HTTP status when Classify answered, its body carries the parsed response body, and its message says why.

CodeMeaning
httpClassify answered with an error status. status is that status, and message is the reason it sent.
timeoutNo answer arrived within the timeout.
networkThe request failed before an answer arrived.
invalid_responseClassify answered 200 with a body that is not a classification.
JavaScript
try {
const { label } = await botpoison.classify(req.body, {
model: "spam-standard",
});
// ...
} catch (error) {
if (error instanceof Botpoison.ClassifyError) {
// error.code is "http", "timeout", "network" or "invalid_response".
// error.status is the HTTP status when Classify answered.
console.error(error.code, error.status, error.message);
}
// Deliver the message as clean, so an outage never loses a real one
}

Decide what a failure means for your form. If you deliver the submission as clean, an outage never loses a real one. Three statuses are worth handling apart.

StatusMeaning
401The key is missing, unknown or rerolled. Fix the key in your environment rather than retrying.
429The key is past its rate limit, or a Free workspace has paused after its allowance. Wait as long as Retry-After says.
503The model is unavailable and the call is not counted. Retry later with backoff.

Every status code is listed in the REST API reference.

TypeScript

The package ships its own types. Beside the class, it exports these types for the Classify calls: ClassifyContent, ClassifyFields, ClassifyFieldValue, ClassifyOptions, ClassifyResult, ClassifyLabel, ClassifyError and ClassifyErrorCode. At runtime, the error class is Botpoison.ClassifyError.