Verifyre

API documentation

One request to verify any address.

REST over HTTPS, JSON in and out, a bearer key, and a verdict in about a second. Single checks, bulk jobs up to 50,000 addresses, signed webhooks and a drop-in form widget.

Get an API key Status and reason codesLive JSON reference

Quick start

Create a free account, open Settings, then API keys, and create a key. Every new account carries 100 credits; one credit buys one live verification, and a repeat check of the same address inside 24 hours is free and flagged cached.

Base URL: https://verifyre.co/api/v1. All responses are JSON with Cache-Control: no-store. The API is CORS-enabled, but keep your key on a server: a browser can only safely use the widget key flow.

Verify one address

bash
curl "https://verifyre.co/api/v1/verify?email=jane.doe%40example.com" \
  -H "Authorization: Bearer vfy_live_YOUR_API_KEY"
json
{
  "email": "jane.doe@example.com",
  "normalized": "jane.doe@example.com",
  "status": "deliverable",
  "reason": "accepted_email",
  "reason_text": "The mail server confirmed this mailbox exists and accepts mail.",
  "score": 99,
  "deliverable": true,
  "checks": {
    "syntax": true, "domain": true, "mx": true, "smtp": true, "mailbox": true,
    "catch_all": false, "disposable": false, "free": false, "role": false,
    "spf": true, "dmarc": true
  },
  "mx": [{ "exchange": "aspmx.l.google.com", "priority": 1 }],
  "provider": "Google Workspace",
  "did_you_mean": null,
  "cached": false,
  "ms": 1240,
  "credits_charged": 1,
  "credits_remaining": 9871
}

Authentication

Send the key as a bearer token, or in an X-Api-Key header if your HTTP client makes that easier. Keys start with vfy_live_, are shown once when created, and are stored on our side only as a SHA-256 hash. Revoke and rotate them from the account at any time; a revoked key answers 401 invalid_key immediately.

http
GET https://verifyre.co/api/v1/account HTTP/1.1
Authorization: Bearer vfy_live_YOUR_API_KEY

# or
X-Api-Key: vfy_live_YOUR_API_KEY

Give each system its own key (website widget, CRM sync, data warehouse) so usage is attributable and a leak is contained to one surface.

Rate limits

Each key may make 120 requests per minute by default (higher on request for enterprise accounts). The widget endpoint is limited separately to 30 requests per minute per key and visitor IP. Every response carries the current window:

http
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 117

# when exceeded
HTTP/1.1 429 Too Many Requests
Retry-After: 23
{ "error": { "code": "rate_limited", "message": "Rate limit of 120 requests per minute reached. Retry in 23s.", "retry_after": 23 } }

Bulk jobs are the right tool for anything over a few hundred addresses: one request creates the job, the worker verifies with per-domain politeness, and a webhook or callback tells you when it is done.

Errors

Errors use a conventional HTTP status and a JSON body with a stable code you can branch on and a message written for a human.

json
{
  "error": {
    "code": "out_of_credits",
    "message": "Your account has no credits left. Buy a pack at https://verifyre.co/pricing to continue."
  }
}
StatusCodeMeaning
400invalid_requestA parameter is missing or malformed (no email, an empty emails array, a bad callback_url).
401missing_key / invalid_keyNo key was sent, it does not start with vfy_, or it has been revoked.
402out_of_creditsThe balance cannot cover the request. For bulk jobs the body includes credits_needed and credits_available.
403account_suspendedThe account behind the key is suspended.
404not_foundNo bulk job with that id belongs to this account.
413too_many_emailsA bulk request had more than 50,000 addresses.
429rate_limitedThe per-key limit was reached. Honour Retry-After (seconds).
500verification_failedThe check failed on our side. Nothing was charged; retry.

Verify one address

GETPOSThttps://verifyre.co/api/v1/verify

Runs the full pipeline: syntax, DNS and MX, disposable and role lists, an SMTP handshake with the mailbox's own server, catch-all detection and scoring. Typical latency is 0.5 to 2 seconds; providers that refuse handshakes (some Yahoo and Outlook hosts) answer faster with unknown / unverifiable.

ParameterTypeDescription
emailstring, requiredThe address to verify. GET takes it as a query parameter, POST as a JSON body {"email": "..."}.

Response fields

ParameterTypeDescription
statusstringdeliverable, undeliverable, risky or unknown.
reasonstringThe reason code behind the status, e.g. accepted_email, mailbox_not_found, catch_all. See the reference below.
reason_textstringThe reason explained in one sentence, suitable for showing to users.
scoreinteger 0-100Confidence the address will deliver; 0 is certainly dead, 100 is confirmed.
deliverablebooleanShorthand for status == deliverable.
checksobjectEach pipeline stage: syntax, domain, mx, smtp, mailbox, catch_all, disposable, free, role, spf, dmarc. null means not determinable.
mxarrayThe domain's mail exchangers, highest priority first.
providerstring | nullThe mailbox provider when recognised (Google Workspace, Microsoft 365, Gmail, ...).
did_you_meanstring | nullA corrected domain when the given one looks like a typo of a well-known provider (gmai.com becomes gmail.com).
normalizedstringLower-cased, provider-aware canonical form (Gmail dots and plus tags removed) for de-duplication.
cachedbooleantrue when the verdict was reused from a check inside the last 24 hours; no credit charged.
msintegerTime the check took, in milliseconds.
credits_charged / credits_remainingintegerWhat this call cost and the balance after it.
bash
curl "https://verifyre.co/api/v1/verify?email=jane.doe%40example.com" \
  -H "Authorization: Bearer vfy_live_YOUR_API_KEY"

Create a bulk job

POSThttps://verifyre.co/api/v1/bulk

Submits up to 50,000 addresses in one request. Addresses are de-duplicated on their normalised form; duplicates and syntactically invalid rows are free, and the unique, well-formed remainder is charged when the job is created. The job runs in the background; poll the status URL or supply a callback_url.

ParameterTypeDescription
emailsstring[], requiredUp to 50,000 addresses. Objects of the form {"email": "..."} are accepted too.
namestringA label shown in your dashboard. Defaults to "API list" plus the timestamp.
callback_urlstringA public https URL that receives a POST {event: "job.completed", job: {...}} when the job finishes. For signed delivery use webhooks instead.
bash
curl -X POST "https://verifyre.co/api/v1/bulk" \
  -H "Authorization: Bearer vfy_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"October newsletter","emails":["jane.doe@example.com","info@acme.co","typo@gmai.com"],"callback_url":"https://example.com/hooks/verifyre"}'
json
HTTP/1.1 202 Accepted
{
  "id": "cmh3k9x1a0001v8l2q7u4d9fz",
  "name": "October newsletter",
  "status": "queued",
  "total": 3,
  "duplicates": 0,
  "credits_charged": 3,
  "callback_url": "https://example.com/hooks/verifyre",
  "status_url": "https://verifyre.co/api/v1/bulk/cmh3k9x1a0001v8l2q7u4d9fz",
  "results_url": "https://verifyre.co/api/v1/bulk/cmh3k9x1a0001v8l2q7u4d9fz/results",
  "created_at": "2026-10-22T09:14:03.000Z"
}

Job status

GEThttps://verifyre.co/api/v1/bulk/{id}

Counts update as the worker progresses. status is one of queued, running, paused, done, failed or cancelled; progress is a percentage. Poll no faster than every few seconds, or let a webhook tell you.

bash
curl "https://verifyre.co/api/v1/bulk/cmh3k9x1a0001v8l2q7u4d9fz" \
  -H "Authorization: Bearer vfy_live_YOUR_API_KEY"
json
{
  "id": "cmh3k9x1a0001v8l2q7u4d9fz",
  "name": "October newsletter",
  "status": "done",
  "total": 3,
  "processed": 3,
  "progress": 100,
  "counts": { "deliverable": 2, "undeliverable": 1, "risky": 0, "unknown": 0 },
  "duplicates": 0,
  "credits_charged": 3,
  "error": null,
  "results_url": "https://verifyre.co/api/v1/bulk/cmh3k9x1a0001v8l2q7u4d9fz/results",
  "created_at": "2026-10-22T09:14:03.000Z",
  "started_at": "2026-10-22T09:14:04.000Z",
  "finished_at": "2026-10-22T09:14:09.000Z"
}

Job results

GEThttps://verifyre.co/api/v1/bulk/{id}/results

Rows in their original order. JSON is paginated; ?format=csv streams every row as a file with the same columns. Rows not yet processed show status: "pending", so you can read a running job.

ParameterTypeDescription
pageinteger1-based page number for JSON (default 1).
limitintegerRows per page, 1 to 1000 (default 1000).
statusstringOnly rows with this status: deliverable, undeliverable, risky or unknown.
formatcsvReturn every matching row as CSV instead of JSON.
bash
curl "https://verifyre.co/api/v1/bulk/cmh3k9x1a0001v8l2q7u4d9fz/results?page=1&limit=1000" \
  -H "Authorization: Bearer vfy_live_YOUR_API_KEY"
json
{
  "job_id": "cmh3k9x1a0001v8l2q7u4d9fz",
  "job_status": "done",
  "page": 1,
  "limit": 1000,
  "total": 3,
  "pages": 1,
  "results": [
    {
      "row": 1,
      "email": "jane.doe@example.com",
      "status": "deliverable",
      "reason": "accepted_email",
      "reason_text": "The mail server confirmed this mailbox exists and accepts mail.",
      "score": 99,
      "normalized": "jane.doe@example.com",
      "provider": "Google Workspace",
      "did_you_mean": null,
      "disposable": false, "role": false, "free": false, "catch_all": false,
      "checked_at": "2026-10-22T09:14:06.000Z"
    }
  ]
}

Account

GEThttps://verifyre.co/api/v1/account

The balance, this month's usage and the key that made the call. Handy for a low-balance alert in your own monitoring.

bash
curl "https://verifyre.co/api/v1/account" \
  -H "Authorization: Bearer vfy_live_YOUR_API_KEY"
json
{
  "email": "you@company.com",
  "credits": 9871,
  "usage": {
    "month": { "since": "2026-10-01T00:00:00.000Z", "single_checks": 412, "bulk_jobs": 3, "bulk_credits": 14200, "credits_spent": 14612 },
    "all_time": { "single_checks": 2210, "bulk_jobs": 19, "bulk_credits": 91000 }
  },
  "key": { "name": "Production", "prefix": "vfy_live_a8Kd3", "calls": 2622, "created_at": "2026-09-02T10:00:00.000Z" },
  "active_keys": 2,
  "rate_limit": { "per_minute": 120, "remaining": 119 }
}

Status codes

GEThttps://verifyre.co/api/v1/status-codes

No key required. Returns every status with its label and blurb, every reason code with its sentence, and the score bands, so your own UI can stay in step with ours without hard-coding copy.

bash
curl https://verifyre.co/api/v1/status-codes

Webhooks

Add endpoints under Settings, then Webhooks. Each endpoint gets a secret; every delivery is a POST with the JSON body below, signed with HMAC-SHA256 over the raw bytes and sent in X-Verifyre-Signature (lower-case hex). Compare in constant time and reject anything that does not match. Deliveries time out after 8 seconds and are not retried, so answer quickly (2xx) and do the work afterwards.

ParameterTypeDescription
job.completedeventA bulk job finished. data carries the counts and a link to the list in the dashboard.
http
POST https://example.com/hooks/verifyre
Content-Type: application/json
X-Verifyre-Event: job.completed
X-Verifyre-Signature: 4f6c0c2a...   (hex HMAC-SHA256 of the raw body, keyed with your webhook secret)

{
  "event": "job.completed",
  "data": {
    "id": "cmh3k9x1a0001v8l2q7u4d9fz",
    "name": "October newsletter",
    "total": 14200,
    "deliverable": 12910,
    "undeliverable": 740,
    "risky": 410,
    "unknown": 140,
    "url": "https://verifyre.co/app/lists/cmh3k9x1a0001v8l2q7u4d9fz"
  },
  "sentAt": "2026-10-22T09:14:09.000Z"
}

Verify the signature

javascript
import { createHmac, timingSafeEqual } from "node:crypto";

// Express: use express.raw({ type: "application/json" }) so req.body is the raw bytes.
app.post("/hooks/verifyre", (req, res) => {
  const expected = createHmac("sha256", process.env.VERIFYRE_WEBHOOK_SECRET).update(req.body).digest("hex");
  const given = req.get("X-Verifyre-Signature") || "";
  if (given.length !== expected.length || !timingSafeEqual(Buffer.from(given), Buffer.from(expected))) {
    return res.status(401).end();
  }
  const { event, data } = JSON.parse(req.body);
  if (event === "job.completed") console.log("List done:", data.id, data.deliverable, "deliverable");
  res.status(204).end();
});

Form widget

One script tag adds live validation to any email field on any site: a green tick for a confirmed mailbox, a "Did you mean" prompt for typos, and a red warning for addresses that cannot receive mail. It writes a hidden verifyre_status input into the form and, with data-block="true", stops the form submitting an undeliverable address. The widget is 9 KB, has no dependencies and works with React, Vue, Shopify, WordPress and plain HTML alike.

html
<script src="https://verifyre.co/widget.js"
  data-key="vfy_live_YOUR_API_KEY"
  data-selector="input[type=email]"
  data-block="true"
  async></script>
ParameterTypeDescription
data-keyrequiredAn API key from your account. Make one just for the widget: it is visible in your page source, so keep it separate from server keys and revoke it if it leaks.
data-selectordefault input[type=email]Which inputs to watch. Any CSS selector; new matching inputs (modals, SPAs) are picked up automatically.
data-blocktrue | falsePrevent submission when the address is undeliverable. The visitor sees the hint and can correct it.
data-debouncedefault 600Milliseconds after typing stops before a check runs.
data-text-good, data-text-bad, data-text-suggest, data-text-usestringsOverride the hint copy, for translations.

Each uncached check spends one credit from the key's account; the widget never checks a field that does not look like a complete address, and repeated checks of the same address are free. Listen for the verifyre event or call Verifyre.verify() from your own code:

javascript
document.addEventListener("verifyre", (e) => {
  // e.target is the input; e.detail is { status, reason, reason_text, did_you_mean, score }
  if (e.detail.status === "undeliverable") analytics.track("bad_email_caught");
});

// Programmatic use: returns the same object (or null if the check failed).
const verdict = await Verifyre.verify("someone@example.com");

The widget endpoint is limited to 30 checks a minute per key and visitor, and a key can be restricted to the widget by revoking it the moment it is misused: it is a normal key with the normal controls.

Status reference

Four statuses, fifteen reason codes, one score. The full table with what to do about each is on the status codes page.

Deliverable

Safe to send. The mailbox exists and accepts mail.

Undeliverable

Do not send. Mail to this address will bounce.

Risky

Might deliver, might bounce. Send with care or suppress.

Unknown

The mail server would not confirm either way. Re-check later.

Build it in an afternoon.

100 free credits on signup, no card. Your first verified address is one curl away.

Get an API key See pricing