Skip to content

Verification API

One endpoint. Send an email, get a verdict. Same engine as the dashboard — mailbox-level SMTP checks included. 1 credit per call; unknown results are refunded automatically.

Quickstart

  1. Create an account — 100 free credits, no card.
  2. Dashboard → API keys → create a key. You'll see the full key exactly once.
  3. Call the endpoint. That's the whole API.

Authentication

Bearer token in the Authorization header. Keys look like nb_live_1a2b3c4d_… — we store only a SHA-256 hash, so save the plaintext when you create it.

Request

POST /api/v1/verify

cURL
curl -X POST https://null-bounce.com/api/v1/verify \
  -H "Authorization: Bearer nb_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email": "dana@acmefoundry.com"}'
Node.js (fetch)
const res = await fetch("https://null-bounce.com/api/v1/verify", {
  method: "POST",
  headers: {
    Authorization: "Bearer nb_live_YOUR_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ email: "dana@acmefoundry.com" }),
});

const { result } = await res.json();
if (result.status === "valid") {
  console.log("safe to send:", result.email);
} else if (result.didYouMean) {
  console.log("typo? try:", result.didYouMean);
}
Python (requests)
import requests

res = requests.post(
    "https://null-bounce.com/api/v1/verify",
    headers={
        "Authorization": "Bearer nb_live_YOUR_KEY",
        "Content-Type": "application/json",
    },
    json={"email": "dana@acmefoundry.com"},
)
res.raise_for_status()
result = res.json()["result"]
print(result["status"], result["score"])

Body: { "email": "…" } — one address per call, max 254 chars. Bulk belongs in the dashboard (CSV up to 50,000 rows) or your own batching against this endpoint.

Response

{
  "result": {
    "email": "dana@acmefoundry.com",
    "normalized": "dana@acmefoundry.com",
    "status": "valid",
    "subStatus": "mailbox_exists",
    "score": 1,
    "didYouMean": null,
    "freeProvider": false,
    "roleAccount": false,
    "disposable": false,
    "mx": ["mx1.acmemail.net", "mx2.acmemail.net"],
    "reason": "mailbox confirmed via SMTP handshake"
  }
}
status
valid · invalid · risky · unknown · do_not_mail
score
0–1 confidence the mailbox can receive mail
didYouMean
Suggested correction when the domain looks like a typo
mx
Mail exchangers found for the domain
freeProvider / roleAccount / disposable
Classification flags
reason
Human-readable explanation of the verdict

Verdict statuses

StatusMeaningSend?
validMailbox confirmed via SMTP handshakeYes
invalidSyntax, dead domain, or rejected mailboxNo
riskyCatch-all / implicit-MX / typo-suspectYour call
unknownServer refused to answer (greylist, block)Refunded
do not_mailDeliverable but disposable or role accountNo

Errors

HTTPCodeWhen
401unauthorizedMissing/invalid API key or session.
402insufficient_creditsBalance is 0. Top up in the dashboard.
422invalid_inputBody failed validation. Message says what's wrong.
429rate_limitedToo many requests. Check Retry-After header.
503engine_unavailableEngine temporarily down. Credit refunded — retry safely.

Rate limits

API keys: 60 requests/minute burst, sustained load spreads automatically. Anonymous demo widget: 5 checks/day per IP. On 429, honorRetry-After.