API reference

Sigilpost verify APItwo endpoints, same verdict shape.

Paste a curl, get a yes or no. The single endpoint is anonymous for visitors (no signup, three free checks per session). The bulk endpoint expects a signed-in caller with an API key.

The verdict

Three bands. Pick one per address.

Every check lands in one of the three bands below. The single endpoint and the bulk endpoint use the same shape, so you can mix and match.

  • deliverable

    Send — the address should accept mail. Includes real addresses that look fine on the surface.

  • risky

    Be careful — couldn’t fully confirm. Often a server that accepts anything it gets.

  • undeliverable

    Skip — the address is bad, blocked, or a throwaway inbox you don’t want to hit.

POST/v1/verify
HTTPS · 200 OK

One email at a time. Anonymous calls work — signed-in callers get their checks logged against their account.

Request body
FieldTypeRequiredExample
emailstringYesjordan@stripe.com
Response (200)
FieldTypeMeaning
emailstringThe email you sent — echoed back.
status'valid' | 'risky' | 'invalid'The verdict band above.
scoreinteger (0–100)How confident we are — higher is safer to send.
is_disposablebooleanTrue for throwaway inbox domains (mailinator, guerrillamail, …).
mx_foundbooleanTrue when we located a mail server that should accept mail for the address.

Optional
Authorization: Bearer $SIGILPOST_KEY — log this caller against your account. The endpoint still works without it.

cURL
bash
curl -X POST https://api.sigilpost.dev/v1/verify \
  -H "Content-Type: application/json" \
  -d '{"email":"jordan@stripe.com"}'
Node.js
javascript
// Node 18+ — built-in fetch, no SDK needed.
const res = await fetch("https://api.sigilpost.dev/v1/verify", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ email: "jordan@stripe.com" }),
});
const verdict = await res.json();
console.log(verdict);
Python
python
import json
import urllib.request

req = urllib.request.Request(
    "https://api.sigilpost.dev/v1/verify",
    method="POST",
    headers={"Content-Type": "application/json"},
    data=json.dumps({"email": "jordan@stripe.com"}).encode(),
)
with urllib.request.urlopen(req) as resp:
    verdict = json.loads(resp.read())
print(verdict)
Status codes
CodeWhenBody
200Verdict returned.VerifyOutput (above)
400Body failed validation.{ "errors": { "email": "Email is too short" } }
500Internal server error.{ "error": "Internal Server Error" }
POST/v1/verify/bulk
Auth required

One to fifty emails in a single call. The response carries a per-row verdict array plus a four-bucket summary so dashboards have something to render without a second round trip.

Sign-in required
Anonymous callers get 401 Unauthorized. Sign in once, mint a key, then add it as Authorization: Bearer ….

Request body — pick one shape
Shape A · quick
A bare JSON array.
["jordan@stripe.com", "ceo@some-startup.io"]
Shape B · with webhook
Object envelope. webhook_url and webhook_secret must come together or not at all.
{
  "emails": [
    "jordan@stripe.com",
    "ceo@some-startup.io",
    "throwaway@mailinator.com"
  ],
  "webhook_url": "https://your-app.com/hooks/sigilpost",
  "webhook_secret": "at-least-8-chars-long"
}
Response (200)
FieldTypeMeaning
resultsarray (1–50 rows)Same per-row shape as the single endpoint.
summary.deliverableintegerCount where status = valid.
summary.riskyintegerCount where status = risky.
summary.undeliverableintegerCount where status = invalid AND not disposable.
summary.disposableintegerCount where is_disposable = true.
webhookobject | omittedPresent when you sent a webhook_url. Reports batch_id + delivery status.

Summary mapping — every per-row verdict lands in exactly one bucket. The row counter adds up to emails.length.

cURL
bash
curl -X POST https://api.sigilpost.dev/v1/verify/bulk \
  -H "Authorization: Bearer $SIGILPOST_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "emails": [
      "jordan@stripe.com",
      "ceo@some-startup.io",
      "throwaway@mailinator.com"
    ]
  }'
Node.js
javascript
// Node 18+ — built-in fetch, no SDK needed.
const res = await fetch("https://api.sigilpost.dev/v1/verify/bulk", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": `Bearer ${process.env.SIGILPOST_KEY}`,
  },
  body: JSON.stringify({
    emails: [
      "jordan@stripe.com",
      "ceo@some-startup.io",
      "throwaway@mailinator.com",
    ],
  }),
});
const batch = await res.json();
console.log(batch.summary);
Python
python
import json
import os
import urllib.request

req = urllib.request.Request(
    "https://api.sigilpost.dev/v1/verify/bulk",
    method="POST",
    headers={
        "Content-Type": "application/json",
        "Authorization": f"Bearer {os.environ['SIGILPOST_KEY']}",
    },
    data=json.dumps({
        "emails": [
            "jordan@stripe.com",
            "ceo@some-startup.io",
            "throwaway@mailinator.com",
        ],
    }).encode(),
)
with urllib.request.urlopen(req) as resp:
    batch = json.loads(resp.read())
print(batch["summary"])
Status codes
CodeWhenBody
200Batch verdicts returned.VerifyBulkResponse (above)
400Body failed validation (empty list, > 50 emails, bad URL, …).{ "errors": { ... } }
401No signed-in session.{ "error": "Unauthorized" }
500Internal server error.{ "error": "Internal Server Error" }

Webhook deliveries signed with X-Sigilpost-Signature over the raw body in a stable key order — validate the header server-side before trusting the payload.

Rate limits · reads straight from billing

How many checks per month?

Every plan includes both endpoints. Higher tiers raise the cap and unlock bulk + webhooks for every caller.

PlanChecks / moWhat you get
Starter5,000For side projects. One key, one mailbox at a time.
Growth25,000For small teams. A few keys, more checks, EU servers optional.
Scale100,000For senders who live in the API. Higher caps, dedicated support.
Need a key?

Tried the curl, hit 401 on bulk?

Email us and we'll set you up with a key, walkthrough, and credits to seed your first batch.