// documentation
// integrate with AI
Let your AI agent do the integration
Download the agent-ready guide and drop it into Claude Code, Cursor, or any coding assistant. It has every rule and snippet needed to wire Sendworth into your forms — no copy-pasting from these docs.
01 / authentication
Authentication
Every request needs your API key in the Authorization header as a bearer token. Keys look like ig_live_… and are created from your dashboard. Call the API from your server — never ship a key to the browser.
Authorization: Bearer ig_live_your_key02 / the endpoint
POST /api/v1/check
Submit a single form submission for classification. At least one of email or message is required — more context yields better categorization.
| Field | Required | Notes |
|---|---|---|
| one of email / message | Sender address. Checked against the disposable-domain blocklist and abuse database. | |
| name | optional | Sender name. Extra context for the classifier. |
| message | one of email / message | The form message body. What the AI layer actually reads. |
| source_site | optional | Label for which site or form sent it. Shown in your dashboard. |
curl https://sendworth.dev/api/v1/check \
-H "Authorization: Bearer ig_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"email": "jane@example.com",
"name": "Jane Doe",
"message": "Hi, do you offer annual billing?",
"source_site": "acme-marketing"
}'const res = await fetch("https://sendworth.dev/api/v1/check", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SENDWORTH_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
email: form.email,
name: form.name,
message: form.message,
source_site: "acme-marketing",
}),
})
const result = await res.json()
// { id, status, category, isSpam, confidence, reason, signals, pending, usage }03 / the response
Response
Classification runs in two stages. The free layers — disposable-domain blocklist and sender reputation — answer immediately. If they catch something, status is resolved and the verdict is final. Otherwise you get a provisional category with pending: true, and the AI layer refines it in the background — the response never waits on the LLM. The final category shows up in your dashboard moments later.
{
"id": "5b0d6a7e-9c1f-4f7a-9a44-1f2c3d4e5f6a",
"status": "pending",
"category": "lead",
"isSpam": false,
"confidence": 0.5,
"reason": "Passed fast checks — awaiting content classification.",
"signals": {
"disposableDomain": false,
"senderReputation": "clean",
"sfsConfidence": 0,
"llmUsed": false
},
"pending": true,
"usage": { "checks_used": 42, "checks_limit": 10000 }
}A simple rule of thumb: treat isSpam: true as junk and everything else as deliverable. usagereports your month-to-date check count against your plan's limit.
04 / categories
Categories
Every account starts with these four. Pro accounts can add custom categories and enable or disable any of them — the classifier only ever picks from the categories you have turned on.
spam
Junk, abusive, or automated garbage. Do not send or store.
solicitor
Cold sales pitches, agencies, and outreach trying to sell you something.
lead
A genuine potential customer worth following up with.
support-request
An existing user or customer who needs help.
05 / with resend
Using it with Resend
The whole point: branch on the category so you never spend a Resend send on junk.
import { Resend } from "resend"
const resend = new Resend(process.env.RESEND_API_KEY)
const check = await fetch("https://sendworth.dev/api/v1/check", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SENDWORTH_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
email: submission.email,
name: submission.name,
message: submission.message,
}),
}).then((r) => r.json())
// Only spend a Resend send on things worth sending.
if (check.category === "lead" || check.category === "support-request") {
await resend.emails.send({
from: "forms@yourdomain.com",
to: "you@yourdomain.com",
subject: `New ${check.category} from ${submission.name}`,
text: submission.message,
})
}06 / errors
Errors & limits
400Invalid JSON body, or neither email nor message was provided.
401Missing, invalid, or revoked API key.
429Rate limit exceeded (burst), or your monthly check quota is used up. The body tells you which.
503Service not configured (self-hosted installs only).
Quotas reset on the first of each month (UTC). Rate limits are per API key with a sliding window — a 429 from the rate limiter includes limit, remaining, and reset so you can back off cleanly.
// last step
Ready to get a key?
Start free with 100 checks a month. No credit card.