Classify

# Getting started

Classify a submission in two steps. Your server sends the form, and Classify returns a verdict: a label and a score. It reads the content, so it works on any text a person types: contact forms, comments, signups and support email.

## 01 Send the submission from your server

After the form reaches your server, send it to `/classify` with your secret key. Send the whole form for the best verdict, or a string for a single message box. Keep the secret key on your server, never in the browser.

Node.js:

npm install @botpoison/node

JavaScript

```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_CLASSIFY_SECRET_KEY=sk_xxxxxxxx
const botpoison = new Botpoison({
  secretKey: process.env.BOTPOISON_CLASSIFY_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();
  }
  // ...
});
```

PHP:

PHP

```php
<?php
// The secret key comes from your server's environment:
// BOTPOISON_CLASSIFY_SECRET_KEY=sk_xxxxxxxx
$secret_key = getenv("BOTPOISON_CLASSIFY_SECRET_KEY");

// 1. Send the whole form to Classify
$response = file_get_contents("https://api.botpoison.com/classify", false, stream_context_create(["http" => [
    "method" => "POST",
    "header" => "Content-Type: application/json\r\nAuthorization: Bearer " . $secret_key,
    "content" => json_encode([
        "content" => $_POST,
        "model" => "spam-standard",
    ]),
    "timeout" => 8,
]]));

// 2. Read the label
if (json_decode($response)->label === "spam") {
    // Drop it, or file it away
    http_response_code(202);
    exit;
}
// ...
```

Python:

Python

```python
import os
import requests
from flask import Flask, request

# The secret key comes from your server's environment:
# BOTPOISON_CLASSIFY_SECRET_KEY=sk_xxxxxxxx
SECRET_KEY = os.environ["BOTPOISON_CLASSIFY_SECRET_KEY"]

app = Flask(__name__)

@app.post("/contact")
def contact():
    # 1. Send the whole form to Classify
    response = requests.post(
        "https://api.botpoison.com/classify",
        headers={"Authorization": f"Bearer {SECRET_KEY}"},
        json={
            "content": request.form.to_dict(),
            "model": "spam-standard",
        },
        timeout=8,
    )
    response.raise_for_status()
    # 2. Read the label
    if response.json()["label"] == "spam":
        # Drop it, or file it away
        return "", 202
    # ...
```

Go:

Go

```go
package main

import (
	"bytes"
	"encoding/json"
	"net/http"
	"os"
	"time"
)

// The secret key comes from your server's environment:
// BOTPOISON_CLASSIFY_SECRET_KEY=sk_xxxxxxxx
var secretKey = os.Getenv("BOTPOISON_CLASSIFY_SECRET_KEY")

var client = &http.Client{Timeout: 8 * time.Second}

func contact(w http.ResponseWriter, r *http.Request) {
	// 1. Send the whole form to Classify
	r.ParseForm()
	content := map[string]string{}
	for key := range r.PostForm {
		content[key] = r.PostForm.Get(key)
	}
	body, _ := json.Marshal(map[string]any{
		"content": content,
		"model":   "spam-standard",
	})
	req, _ := http.NewRequest("POST", "https://api.botpoison.com/classify", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+secretKey)
	res, err := client.Do(req)
	if err != nil || res.StatusCode != http.StatusOK {
		http.Error(w, "", http.StatusBadGateway)
		return
	}
	defer res.Body.Close()
	// 2. Read the label
	var result struct{ Label string `json:"label"` }
	json.NewDecoder(res.Body).Decode(&result)
	if result.Label == "spam" {
		// Drop it, or file it away
		w.WriteHeader(http.StatusAccepted)
		return
	}
	// ...
}
```

Rust:

Rust

```rust
use axum::{extract::Form, http::StatusCode};
use serde::Deserialize;
use serde_json::json;
use std::{collections::HashMap, time::Duration};

#[derive(Deserialize)]
struct Verdict {
    label: String,
}

// The secret key comes from your server's environment:
// BOTPOISON_CLASSIFY_SECRET_KEY=sk_xxxxxxxx
async fn contact(Form(form): Form<HashMap<String, String>>) -> StatusCode {
    let secret_key = std::env::var("BOTPOISON_CLASSIFY_SECRET_KEY").unwrap_or_default();
    // 1. Send the whole form to Classify
    let response = reqwest::Client::new()
        .post("https://api.botpoison.com/classify")
        .bearer_auth(secret_key)
        .timeout(Duration::from_secs(8))
        .json(&json!({ "content": form, "model": "spam-standard" }))
        .send()
        .await
        .and_then(|response| response.error_for_status());
    let Ok(response) = response else {
        return StatusCode::BAD_GATEWAY;
    };
    // 2. Read the label
    let Ok(verdict) = response.json::<Verdict>().await else {
        return StatusCode::BAD_GATEWAY;
    };
    if verdict.label == "spam" {
        // Drop it, or file it away
        return StatusCode::ACCEPTED;
    }
    // ...
    StatusCode::OK
}
```

Java:

Java

```java
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;

@RestController
public class ContactController {
    // The secret key comes from your server's environment:
    // BOTPOISON_CLASSIFY_SECRET_KEY=sk_xxxxxxxx
    private static final String SECRET_KEY = System.getenv("BOTPOISON_CLASSIFY_SECRET_KEY");

    private final HttpClient client = HttpClient.newHttpClient();
    private final ObjectMapper mapper = new ObjectMapper();

    @PostMapping("/contact")
    public ResponseEntity<Void> contact(@RequestParam Map<String, String> form) throws Exception {
        // 1. Send the whole form to Classify
        String body = mapper.writeValueAsString(Map.of("content", form, "model", "spam-standard"));
        HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.botpoison.com/classify"))
                .header("Content-Type", "application/json")
                .header("Authorization", "Bearer " + SECRET_KEY)
                .timeout(Duration.ofSeconds(8))
                .POST(HttpRequest.BodyPublishers.ofString(body))
                .build();
        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
        if (response.statusCode() != 200) {
            throw new ResponseStatusException(HttpStatus.BAD_GATEWAY);
        }
        // 2. Read the label
        if ("spam".equals(mapper.readTree(response.body()).path("label").asText())) {
            // Drop it, or file it away
            return ResponseEntity.accepted().build();
        }
        // ...
        return ResponseEntity.ok().build();
    }
}
```

HTTP:

Shell

```bash
curl --request POST \
  --url 'https://api.botpoison.com/classify' \
  --header 'accept: application/json' \
  --header 'content-type: application/json' \
  --header 'authorization: Bearer sk_xxxxxxxx' \
  --data '{
    "content": {
      "name": "Jo",
      "email": "jo@example.com",
      "message": "Hello, could you send me a quote?"
    },
    "model": "spam-standard"
  }'
```

In Node.js, `classify()` needs `@botpoison/node` 0.2.0 or later. Its options, its errors and a version without the library are in the [@botpoison/node reference](https://botpoison.com/documentation/libraries/botpoison-node/#classify). Every other language posts to the [REST API](https://botpoison.com/documentation/classify/rest-api/).

## 02 Read the result

JSON

```json
{
  "label": "clean",
  "score": 0.04,
  "model": "spam-standard"
}
```

- Spam Drop it, or file it away.
- Clean Deliver it as usual.

Act on `label`. It is Botpoison's recommended cut on the score, `spam` from 0.8, so the two never disagree. To hold borderline messages for a person, or to draw your own line, use `score`. See [Labels and scores](https://botpoison.com/documentation/classify/labels-and-scores/).

Test key

Send the secret key `test` to try every path without real spam. Content containing `botpoison-test-spam` returns `spam`, `botpoison-test-review` returns `clean` with a score of 0.6, and `botpoison-test-unavailable` returns a 503. Test calls never count toward your plan. See [Classify test mode](https://botpoison.com/documentation/classify/test-mode/).
