Quickstart
Get a key, send one submission, act on the verdict. Five minutes in any language.
1. Get a key
Sign in with your email. The dashboard creates a key on the Free plan with 1,000 checks a month. Copy it once. Only a hash is stored on our side, so a lost key is rotated, not recovered.
2. Send a submission
Forward the fields your form collected plus the visitor’s IP address.
curl https://api.spamex.dev/v1/classify \
-H "Authorization: Bearer $SPAMEX_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Rachel Green",
"email": "[email protected]",
"subject": "Leaking kitchen faucet",
"message": "My kitchen faucet has been dripping for a week. Could someone come out this week? What are your rates?",
"ip": "198.51.100.7"
}' const res = await fetch("https://api.spamex.dev/v1/classify", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SPAMEX_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: fields.name,
email: fields.email,
subject: fields.subject,
message: fields.message,
ip: req.ip,
}),
signal: AbortSignal.timeout(5000),
});
const verdict = res.ok ? await res.json() : null; // null: deliver (fail open) import httpx
r = httpx.post(
"https://api.spamex.dev/v1/classify",
headers={"Authorization": f"Bearer {SPAMEX_KEY}"},
json={
"name": fields["name"],
"email": fields["email"],
"subject": fields["subject"],
"message": fields["message"],
"ip": request.client.host,
},
timeout=5.0,
)
verdict = r.json() if r.status_code == 200 else None # None: deliver (fail open) $ch = curl_init('https://api.spamex.dev/v1/classify');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('SPAMEX_KEY'), 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'name' => $_POST['name'] ?? '',
'email' => $_POST['email'] ?? '',
'subject' => $_POST['subject'] ?? '',
'message' => $_POST['message'] ?? '',
'ip' => $_SERVER['REMOTE_ADDR'] ?? null,
]),
]);
$res = curl_exec($ch);
$verdict = curl_getinfo($ch, CURLINFO_HTTP_CODE) === 200 ? json_decode($res, true) : null; // null: deliver The response comes back in a few milliseconds for most submissions:
{
"id": "1dab208f9c8043d8b016acd9343deb71",
"label": "ham",
"score": 0.0018,
"stage": "ml",
"reasons": ["ML_HAM"],
"latency_ms": 2.97
}
3. Act on the verdict
The simplest policy that works well:
| verdict | do |
|---|---|
spam with HONEYPOT_FILLED, FAST_SUBMIT, DUP_KNOWN_SPAM or a *_REPUTATION_BAD code | Drop silently. Return the usual “thanks” page without sending mail. These are bots. |
spam from stage ml or llm | Route to a review folder instead of the inbox. Show the visitor a fallback email address. |
ham | Deliver normally. |
Store the id next to the message. If a verdict turns out wrong, one call fixes it and teaches your site’s memory:
curl -X POST https://api.spamex.dev/v1/feedback \
-H "Authorization: Bearer $SPAMEX_KEY" -H "Content-Type: application/json" \
-d '{"id": "1dab208f9c8043d8b016acd9343deb71", "label": "ham"}'
Handling verdicts goes deeper: what to show users, when to fail open, how to wire a “not spam” button.
4. Add the free signals
Two signals from your own form catch a large share of bots before any model runs.
- A hidden trap field. Add an input bots will fill in and humans never see. Name it something a bot wants, such as
website, hide it with CSS rather thantype="hidden", and tell spamex the field name in your key settings. Send its value ashoneypot. - Timing. Put the page-load time in a hidden field and note the submit time on your server. Send them as
form_load_tsandsubmit_ts. A form completed in under two seconds is a hard spam verdict.
{
"message": "...",
"honeypot": "",
"form_load_ts": 1757000000,
"submit_ts": 1757000041,
"ip": "198.51.100.7"
}
That is the whole integration. For a complete handler in your language, see Node.js, PHP and WordPress or Python. Running a form service? Read For form platforms.