// 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.

Download guide

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
Authorization: Bearer ig_live_your_key

02 / 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.

FieldRequiredNotes
emailone of email / messageSender address. Checked against the disposable-domain blocklist and abuse database.
nameoptionalSender name. Extra context for the classifier.
messageone of email / messageThe form message body. What the AI layer actually reads.
source_siteoptionalLabel for which site or form sent it. Shown in your dashboard.
curl
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"
  }'
fetch.ts
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.

200 OK
{
  "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.

form-handler.ts
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

400

Invalid JSON body, or neither email nor message was provided.

401

Missing, invalid, or revoked API key.

429

Rate limit exceeded (burst), or your monthly check quota is used up. The body tells you which.

503

Service 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.

Get your API key