Integrate email-integrity screening in minutes. Submit addresses, get back a risk score, an action (allow / verify / review), a deliverability advisory, and a final disposition — over a simple REST API.
Base URL: /api · all endpoints below are under /v1. The full machine reference (try-it-out) lives at /api/docs.
Create an API key in the backoffice under API keys (shown once — store it securely). Authenticate every request with either header:
X-API-Key: sk_live_xxx.your_secret # or Authorization: Bearer sk_live_xxx.your_secret
Each key is scoped to your workspace and rate-limited (HTTP 429 when exceeded). All data you read or write is automatically isolated to your tenant.
Screen a batch of addresses:
curl -X POST /api/v1/emails \
-H "X-API-Key: $SIEVE_API_KEY" \
-H "content-type: application/json" \
-d '{ "emails": ["[email protected]", "[email protected]"] }'Response — one result per address:
[
{
"id": "…", "email": "[email protected]", "valid": true,
"canonical": "[email protected]",
"score": 0, "action": "allow", "disposition": "accepted",
"reasons": [], "nearCount": 0, "cluster": null,
"verification": { "status": "deliverable", "exists": true, "disposable": false }
},
{
"id": "…", "email": "[email protected]", "valid": true,
"score": 70, "action": "review", "disposition": "pending",
"reasons": ["structural", "near_duplicates"],
"cluster": { "id": "…", "size": 4 },
"verification": { "status": "deliverable", "exists": true, "disposable": false }
}
]action — the automated risk tier: allow (clean), verify (soft flag), review (looks bot-enumerated / multi-accounting).
disposition — the final outcome you act on: accepted, pending (awaiting a human decision), or declined.
review mode decides how review-tier emails resolve:
manual — review-tier → pending (you accept/decline later).automated — the engine decides: review-tier → declined, otherwise accepted.Resolution precedence: per-request reviewMode → per-list default → workspace default. Override per call:
{ "emails": [...], "listId": "…", "reviewMode": "automated" }lists/filters — scope detection to a cohort so duplicates are only compared within that list. Pass listId; each list has its own tuning (fuzzy radius, cluster threshold) and review-mode default.
POST /v1/emails Submit one or a batch (≤ 1000). Body:
{ emails: string[], listId?, reviewMode? }
GET /v1/emails List stored records (?listId, ?action, ?limit, ?offset)
GET /v1/emails/:id Full detail for one record
POST /v1/emails/:id/decision Accept/decline a pending record { decision, note? }
GET /v1/reviews The pending-review queue
GET /v1/clusters Detected near-duplicate clusters (?listId)
GET /v1/clusters/:id Cluster detail + first 200 members
GET /v1/analytics Aggregate metrics (?listId)
POST /v1/emails/bulk Async ingest (≤ 25k) → { jobId }
GET /v1/jobs/:id Bulk job status
GET /v1/lists List your lists (newest first)
POST /v1/lists Create a list. Body:
{ name, description?, maxDistance?,
clusterThreshold?, reviewMode?,
reflagPriorOnDuplicate? }
GET /v1/lists/:id Get a list + its current settings
PATCH /v1/lists/:id Update a list (any subset of fields)
DELETE /v1/lists/:id Delete a list (204; records detached)
GET /v1/lists/:id/records Records on this list (same filters as
GET /v1/emails — the listId is pinned
from the path)
GET /v1/warmup-inbox/access Slot quota + add-on / plan source
GET /v1/warmup-inbox/inboxes List your warming mailboxes
POST /v1/warmup-inbox/inboxes Add a new SMTP mailbox. Body:
{ email, senderFirst, senderLast,
timezone?, smtp:{username,password,
host,port,tls}, imap:{...} }
(Google + Office mailboxes are
portal-only — interactive OAuth.)
GET /v1/warmup-inbox/inboxes/:id Local cache snapshot
GET /v1/warmup-inbox/inboxes/:id/metrics Live placement metrics from WUI
GET /v1/warmup-inbox/inboxes/:id/details Live settings + health
PATCH /v1/warmup-inbox/inboxes/:id Update mailbox settings
POST /v1/warmup-inbox/inboxes/:id/start Resume warming
POST /v1/warmup-inbox/inboxes/:id/pause Pause (slot reserved)
DELETE /v1/warmup-inbox/inboxes/:id Delete (slot freed; 204)
GET /v1/billing/summary Plan + add-ons + estimated monthly total
GET /v1/billing/subscription Current subscription state
GET /v1/billing/addons Active add-on tiers + monthly cost
GET /v1/billing/usage Month-to-date usage by feature
GET /v1/billing/invoices Recent invoices (?limit, default 12)
GET /v1/billing/invoices/:id One invoiceThese are the API-key-authenticated /v1 endpoints — anything you can hit from your integration code. Tenant add-ons (Phone Validation, Inbox Placement, Domain Health, Blacklist Checks) are configured + consumed in the workspace backoffice — see Workspace features below.
For large uploads, enqueue a job and poll for completion. Detection (clustering / scoring) and mailbox-level verification both run server-side; addresses are shipped to the mailbox-level verifier in one batch instead of one HTTP call per address (faster, identical credit cost). Webhooks + notification channels fire on results.
POST /v1/emails/bulk { "emails": [ ...up to 25,000... ], "listId": "…" }
→ 202 { "jobId": "42", "count": 25000, "status": "queued" }
GET /v1/jobs/42
→ { "id": "42", "state": "completed", "progress": 100,
"result": { "processed": 25000, "review": 318 } }The endpoint pre-validates against your remaining SieveGuard-Credits wallet and returns 402 if the upload alone would exceed it — split the file, top up with a credit pack, or upgrade.
In manual mode, review-tier emails are pending. Fetch the queue and resolve each — your decision can also fire a webhook (below):
GET /v1/reviews
POST /v1/emails/{id}/decision { "decision": "declined", "note": "bot pattern" }Deciding an already-resolved record returns 409.
Three of the four add-ons ship public /v1 API endpoints (same API key, same rate limits as the screening API). Every billable action draws from your monthly SieveGuard-Credits wallet at the per-action cost listed below — same cost table the operator tunes in Admin → Credits, exposed to the public via GET /platform/billing-mode. Top up mid-month with a one-shot credit pack from Portal → Billing → Credit packs.
SieveGuard SPF / DKIM / DMARC / MX checker, plus an optional blacklist pass (when the Blacklist Checks add-on is on). Every domain gets a 0–100 score; results are cached for 24h. Repeat lookups within the TTL don't consume credits.
GET /v1/domain-health/access Effective access (plan + add-on gates) GET /v1/domain-health List cached snapshots GET /v1/domain-health/bulk?domains=a,b Bulk lookup (cached only, no refresh) GET /v1/domain-health/:domain Auto-refresh on expiry POST /v1/domain-health/:domain/refresh Force-refresh from DNS
Sends a tracking-coded test from your production sender to a set of seed addresses and reports where it lands (Inbox / Promotions / Spam / Missing) per ESP. Each test consumes a fixed number of SieveGuard Credits from your wallet (live cost from GET /platform/billing-mode → creditCosts.inboxPlacement; auto-refunded if no delivery is detected). 30/60/90-day sender-reputation trend is derived from completed tests.
GET /v1/inbox-placement/access Effective access + quota
GET /v1/inbox-placement List tests
GET /v1/inbox-placement/trend 30/60/90-day reputation trend
POST /v1/inbox-placement Create test → { code, seedEmails }
GET /v1/inbox-placement/:id Poll for results
POST /v1/inbox-placement/:id/refresh Force-refresh from providerPhone-number validation — country, carrier, line type (mobile / VoIP / toll-free / fixed). Cache-first: repeat lookups of the same E.164 are free. Bulk uploads explode into the cache so subsequent single-number lookups are also free.
GET /v1/phone-validation/access Effective access + quota
GET /v1/phone-validation Recent cached lookups
POST /v1/phone-validation/verify Single number → { e164, phoneType, carrier, ... }
POST /v1/phone-validation/bulk Upload CSV → { id, status }
GET /v1/phone-validation/bulk List bulk jobs
GET /v1/phone-validation/bulk/:id Job status + per-row countsBlacklist aggregation (50+ industry blacklists) on every domain / IP you ingest. Each check consumes a fixed number of SieveGuard Credits from your wallet (live cost from GET /platform/billing-mode → creditCosts.blacklist). The credit weight is a fixed worst-case figure — cache hits within the TTL don't re-charge. There's no separate endpoint — results land on the matching /v1/domain-health/:domain snapshot alongside the auth posture (the BL pass runs automatically when the add-on is active and the cache row is refreshed).
Top up your wallet mid-month with a one-shot credit pack: POST /billing/credit-packs/checkout with { packId } returns a Stripe Checkout URL the tenant completes; the webhook handler credits Tenant.purchasedCredits on success. GET /billing/credit-packs lists the active catalog. Wallet balance + per-feature monthly spend: GET /credit/wallet. Full OpenAPI at /api/docs.
Configure endpoints in the backoffice under Webhooks and subscribe per event:
emails.scored — fired once per ingest request with the results.email.reviewed — fired when a pending record is accepted/declined.email.duplicate_detected — fired when a newly-ingested address canonicalises to a mailbox that already has prior records on the same list (Gmail alias gaming, +tag stripping, etc.). Lets your system react retrospectively — revoke access on the earlier accepted record, flag for fraud review, etc.inbox_placement.test_completed — fired when an Inbox Placement test transitions from running → complete. Replaces polling GET /v1/inbox-placement/:id — subscribe to this and you only call the GET endpoint once, to fetch the recipient-level detail you actually want.phone_validation.bulk_completed — fired when an async bulk-CSV phone job hits the completed stage. Replaces polling GET /v1/phone-validation/bulk/:id — payload carries the row totals so most clients won't even need a follow-up GET.warmup_inbox.added · .paused · .resumed · .deleted — mailbox lifecycle events. Fire from both API calls and portal actions, so an automation can stay in sync with whatever happens to a warming mailbox. Payload carries the inbox id, email, current status, plan tier, and last-known reputation score.billing.addon_tier_changed — fired whenever an add-on slider moves (in the portal or via an upcoming write endpoint). Payload carries the add-on kind, previous quota, new quota, and the Stripe subscription item id.billing.plan_changed — main plan or billing interval changed. Fires from the Stripe webhook so it covers admin-portal cancels too, not just portal-driven upgrades. Payload: previous + new plan ids/names and intervals.billing.invoice_paid · billing.invoice_failed — mirrors Stripe's invoice.payment_succeeded / invoice.payment_failed and lets your finance pipeline react without subscribing to Stripe's webhook directly. Payload includes the invoice id, amount, currency, hosted-invoice URL, and (on failure) the decline reason.domain.blacklisted — a previously-clean domain just landed on a blacklist. Fires only on the transition (clean → flagged), so repeated checks that find the domain still listed stay silent. Payload carries the domain, hit count, the listings array, and the current composite score.domain_health.score_changed — composite SPF / DKIM / DMARC / MX score dropped a band (75 → 50, 50 → 25). Upward recoveries don't fire — automation usually cares about regressions, not improvements. Payload: previous + new score, previous + new band, checked-at.billing.invoice_upcoming — Stripe's ~72h heads-up before an invoice draws. Requires the invoice.upcoming event to be enabled on the Stripe dashboard webhook config; without that, this event never fires. Payload: invoice id, amount due, currency, due date, hosted-invoice URL.warmup_inbox.warmed_up — reputation crossed the warmed-up threshold (default 80) for the first time. Fires exactly once per inbox lifecycle. Useful for "ready to start outreach" automation.warmup_inbox.reputation_dropped — reputation regressed by ≥ 10 points vs the prior sync. Includes the delta and the previous score so your automation can size the response.warmup_inbox.daily_report — per-mailbox snapshot fired by each sync. Includes status, reputation, ESP breakdown, warmedUpAt. Webhook only — too high-volume for human-readable alerting.quota.threshold_crossed — monthly usage crossed 80% or 100% of allowance on a metered feature OR the wallet itself (when ≥80% of the monthly credit allowance is spent). At most once per (scope, month, threshold) combo. Payload: feature, used, allowance, usedPct, threshold.Each delivery is a POST with an HMAC-SHA256 signature over {timestamp}.{rawBody} using your endpoint secret. Deliveries retry with backoff. Verify authenticity:
// headers: X-Sieve-Event, X-Sieve-Delivery, X-Sieve-Signature: t=<ts>,v1=<hmac>
import { createHmac } from 'node:crypto';
function verify(rawBody, header, secret) {
const [t, v1] = header.split(',').map((p) => p.split('=')[1]);
const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
return expected === v1;
}Example payloads — one per event type:
// emails.scored — fired once per ingest request
{ "id": "<delivery-id>", "event": "emails.scored", "createdAt": "…",
"data": { "listId": "…",
"records": [ { "id": "…", "email": "…", "action": "review",
"disposition": "pending", "score": 70, "reasons": [...] } ] } }
// email.reviewed — fired on accept/decline of a pending record
{ "id": "<delivery-id>", "event": "email.reviewed", "createdAt": "…",
"data": { "record": { "id": "…", "disposition": "declined", ... },
"decision": "declined", "reviewedBy": "<user-id>" } }
// email.duplicate_detected — fired when a new address folds onto a prior canonical
{ "id": "<delivery-id>", "event": "email.duplicate_detected", "createdAt": "…",
"data": { "newRecordId": "…", "canonical": "[email protected]",
"priorRecordIds": ["…", "…"], "listId": "…" } }
// inbox_placement.test_completed — fired when an async test transitions to complete
{ "id": "<delivery-id>", "event": "inbox_placement.test_completed", "createdAt": "…",
"data": { "testId": "…", "name": "Acme launch", "status": "complete",
"senderDomain": "acme.com", "detectedSender": "[email protected]",
"summary": { "inbox": 17, "promotions": 4, "spam": 2,
"waiting": 0, "missing": 1 },
"completedAt": "…" } }
// phone_validation.bulk_completed — fired when an async bulk job lands completed
{ "id": "<delivery-id>", "event": "phone_validation.bulk_completed", "createdAt": "…",
"data": { "jobId": "…", "name": "Q3 list", "status": "completed",
"rowsTotal": 12000, "rowsBillable": 11820,
"rowsValid": 9874, "rowsInvalid": 1846, "rowsSyntaxError": 100,
"completedAt": "…" } }Webhooks are HTTP-out for server-to-server integration. Alerts are the same events delivered to a human channel — Slack, an email address, or Intercom. Tenant owners configure channels under Notifications in the portal and pick which events should fire each one.
Channels can subscribe to these events:
emails.scored — same trigger as the webhook event. Channels additionally apply a per-record disposition filter (any of accepted / pending / declined).email.reviewed — fires alongside the webhook event when a pending record is decided.inbox_placement.test_completed — "your test is done" with the inbox / promotions / spam count baked into the alert summary.inbox_placement.reputation_regressed — 30-day Inbox Placement trend dropped 5+ points vs the prior half. The killer "your deliverability is sliding" alert.phone_validation.bulk_completed — bulk-CSV job finished, with a short summary of the row totals in the channel message.verifier_credit.threshold_crossed — monthly verifier-credit usage crossed the alert threshold. Comes with an upgrade CTA.billing.plan_changed — plan / interval changed. Informational; useful for finance + ops channels.billing.invoice_paid · billing.invoice_failed — payment outcome. invoice_failed is the urgent one; invoice_paid is a finance confirmation.domain.blacklisted — sending-reputation crisis alert. Fires only on the clean → flagged transition, so the channel doesn't get noisy on repeated checks.domain_health.score_changed — composite score crossed a UI band boundary downward. Useful for proactive "your authentication posture just regressed" alerts.billing.invoice_upcoming — finance-channel heads-up that a charge is coming in ~72h.warmup_inbox.warmed_up · .reputation_dropped — the milestone + regression alerts on warming mailboxes. .daily_report stays webhook-only by design — too high- volume for a Slack channel on tenants with many mailboxes.quota.threshold_crossed — "you're at 80% of monthly allowance on this feature". Ideal ops-channel ping.The vocabulary is intentionally the same as webhooks — every event listed here is also a webhook event you can subscribe an HTTP endpoint to. Pick whichever delivery surface (or both) fits the consuming workflow.
Not every webhook event is also an alert event. email.duplicate_detected fires once per alias-fold — useful for an automation script, too noisy for a Slack channel, so it stays webhook-only.
401 Missing or invalid API key 402 Plan quota exceeded (emails/month, lists, or API keys) 404 Not found (or not owned by your tenant) 409 Record already decided 429 Rate limit exceeded — back off and retry
Errors return { "statusCode", "message" }. Sync ingest is capped at 1000 addresses/request (use bulk above for more). See the full schema at /api/docs.