Warmerlydocs
API reference

Verify

Check whether an email address is deliverable before you send to it — syntax, MX records, disposable/role-based detection, and a live SMTP probe, with results cached so repeat checks are free. Verify single addresses in real time, or submit a list as a bulk job for asynchronous processing.

Verify endpoints are scoped to your user account, not a project — they don't require the X-Project-Id header. Single checks and bulk batches are recorded against your currently active workspace automatically.

The verification object

JSON
{
  "email": "jane@example.com",
  "status": "valid",
  "confidence": 92,
  "reason": "smtp_accepted",
  "checks": {
    "syntax": true,
    "mx": ["aspmx.l.google.com"],
    "smtpConnectable": true,
    "smtpAccepted": true,
    "catchAll": false,
    "disposable": false,
    "roleBased": false,
    "freeProvider": false,
    "provider": "google",
    "didYouMean": null,
    "smtpCode": 250,
    "smtpMessage": "2.1.5 OK"
  },
  "durationMs": 812,
  "checkedAt": "2026-07-02T10:14:03.000Z",
  "cached": false,
  "retryable": false
}
FieldDescription
statusOne of valid, invalid, risky, catch_all, unknown.
confidence0–100 score. Used with status to decide whether the address is safe to send to.
reasonShort machine-readable reason code (e.g. smtp_accepted, mailbox_not_found, disposable_domain).
checksThe individual signals behind the status/confidence — syntax validity, MX records, SMTP connect/accept results, catch-all detection, disposable/role-based/free-provider flags, and the raw SMTP response.
cachedtrue if this result was served from cache rather than a fresh SMTP probe. Cached results don't count against your quota.
retryabletrue if the check failed for a transient reason (e.g. SMTP timeout) and re-running with force: true may produce a different answer.

Verify a single email

POST/verify

Runs (or reuses a cached) verification for one address. Cached results are returned instantly and are not billable; a fresh check consumes one verification from your plan's quota.

FieldTypeRequiredDescription
emailstringYesThe address to verify.
forcebooleanNoSkip the cache and run a fresh SMTP probe even if a cached result exists.
Shell
curl https://app.warmerly.com/api/v1/verify \
  -X POST \
  -H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "email": "jane@example.com" }'
JSON
{
  "result": {
    "email": "jane@example.com",
    "status": "valid",
    "confidence": 92,
    "reason": "smtp_accepted",
    "checks": {
      "syntax": true,
      "mx": ["aspmx.l.google.com"],
      "smtpConnectable": true,
      "smtpAccepted": true,
      "catchAll": false,
      "disposable": false,
      "roleBased": false,
      "freeProvider": false,
      "provider": "google",
      "didYouMean": null,
      "smtpCode": 250,
      "smtpMessage": "2.1.5 OK"
    },
    "durationMs": 812,
    "checkedAt": "2026-07-02T10:14:03.000Z",
    "cached": false,
    "retryable": false
  },
  "checkId": "b2b7c9a0-1e3f-4a2b-9c1d-4e8f0a2b3c4d"
}

Every call — cached or not — is saved to your history as checkId. Errors: 400 bad_request for an invalid body, 402 payment_required if you're out of verification quota.

JSON
{ "error": { "code": "payment_required", "message": "quota_exceeded", "details": { "used": 5000, "limit": 5000 } } }

Bulk verification

Submit a list of addresses as an asynchronous batch, then poll for results. Quota for the full batch size is checked up front, before any items are processed.

Submit a batch

POST/verify/bulk
FieldTypeRequiredDescription
namestringNoOptional label for the batch.
emailsstring[]NoArray of addresses.
textstringNoA pasted blob of addresses separated by whitespace, commas, or semicolons.

Provide emails, text, or both — they're merged and de-duplicated. A batch may contain at most 50,000 addresses.

Shell
curl https://app.warmerly.com/api/v1/verify/bulk \
  -X POST \
  -H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Q3 leads", "emails": ["jane@example.com", "john@example.com"] }'
JSON
{
  "batch": {
    "id": "6f2c6c8e-9b1d-4e3a-9c7f-2d1e8b4a6c9e",
    "total": 2,
    "status": "processing"
  }
}

201 Created on success. Errors: 400 bad_request (no_valid_emails or batch_too_large_max_50000), 402 payment_required (quota_exceeded, with requested/used/limit/remaining in details).

Get batch status and results

GET/verify/bulk/{id}

Returns the batch's progress and, for items that have been processed, their verification results.

Shell
curl https://app.warmerly.com/api/v1/verify/bulk/6f2c6c8e-9b1d-4e3a-9c7f-2d1e8b4a6c9e \
  -H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
JSON
{
  "batch": {
    "id": "6f2c6c8e-9b1d-4e3a-9c7f-2d1e8b4a6c9e",
    "name": "Q3 leads",
    "status": "processing",
    "total": 2,
    "processed": 1,
    "createdAt": "2026-07-02T10:00:00.000Z",
    "completedAt": null
  },
  "items": [
    {
      "email": "jane@example.com",
      "recommendation": "Safe to send",
      "status": "valid",
      "confidence": 92,
      "reason": "smtp_accepted"
    },
    {
      "email": "john@example.com",
      "recommendation": "Pending",
      "status": null,
      "confidence": null,
      "reason": null
    }
  ]
}

batch.status is processing or completed. Each item's recommendation is Safe to send, Send with caution, Do not send, Unknown, or Pending (not yet processed) — a human-readable summary derived from status/confidence/checks. 404 not_found if the batch doesn't exist or belongs to another user.

Pass ?format=csv to download the results as a CSV file (email,recommendation,status,confidence,reason) instead of JSON:

Shell
curl "https://app.warmerly.com/api/v1/verify/bulk/6f2c6c8e-9b1d-4e3a-9c7f-2d1e8b4a6c9e?format=csv" \
  -H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -o results.csv

Verification history

GET/verify/history

Returns your recent single verifications (from POST /verify), most recent first, cursor-paginated.

Query paramTypeRequiredDescription
cursorISO 8601 timestampNoFetch results older than this timestamp. Use the previous response's nextCursor.
limitintegerNoPage size, 1–100. Default 25.
Shell
curl "https://app.warmerly.com/api/v1/verify/history?limit=25" \
  -H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
JSON
{
  "checks": [
    {
      "id": "b2b7c9a0-1e3f-4a2b-9c1d-4e8f0a2b3c4d",
      "email": "jane@example.com",
      "status": "valid",
      "confidence": 92,
      "reason": "smtp_accepted",
      "checks": { "...": "..." },
      "confirmedStatus": null,
      "sendTestStatus": null,
      "createdAt": "2026-07-02T10:14:03.000Z"
    }
  ],
  "nextCursor": "2026-07-02T09:58:11.000Z"
}

confirmedStatus and sendTestStatus reflect a follow-up bounce-probe test send kicked off from the dashboard to confirm an uncertain SMTP result (valid or invalid once resolved); both are null if no test was run. nextCursor is null once you've reached the end of history.

Send test (deliverability-grade check)

Some addresses cannot be settled by SMTP probing alone — a catch-all domain accepts everything, so "accepted" proves nothing. A send test resolves those the only way that actually works: it sends one real message from one of your own warmup mailboxes and watches for a bounce.

 
POST /verify/send-test
GET  /verify/send-test/{id}
FieldTypeRequiredDescription
emailstringYesAddress to test.
checkIdstring (UUID)NoLinks the result back to an earlier verification, so the original check is refined rather than duplicated.
JSON
{
  "test": {
    "id": "9f1c2d3e-4a5b-4c6d-8e7f-0a1b2c3d4e5f",
    "email": "sam@example.com",
    "status": "awaiting_bounce",
    "fromAccount": "jane@yourdomain.com",
    "bounceCode": null,
    "bounceMessage": null,
    "failureReason": null,
    "deadlineAt": "2026-09-12T10:15:00.000Z"
  }
}

GET /verify/send-test/{id} is the poll: each call does a throttled IMAP check for the bounce and returns the current status — awaiting_bounce, valid, invalid or failed. The watch window is about five minutes (deadlineAt). A bounce inside it makes the test invalid. A reply from the address, or reaching deadlineAt with no bounce, makes it valid. So valid here means "no bounce came back in the window", not proof the mailbox is read. A poll made after deadlineAt is what settles the test, so keep polling until the status is no longer awaiting_bounce.

Notes worth knowing before you wire this up:

  • It requires a connected sending mailbox. With none, the test comes back immediately as failed with failureReason: "no_account" rather than erroring.
  • It sends real email from your mailbox, so it affects that mailbox's reputation like any other send. Use it to settle a handful of ambiguous addresses, not as a bulk strategy.
  • It is not billed as a separate verification — it refines a check that was already counted.

Usage / quota

GET/verify/usage

Returns your current verification usage against your plan's limit for the billing period. Checking usage does not itself consume quota.

Shell
curl https://app.warmerly.com/api/v1/verify/usage \
  -H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
JSON
{
  "usage": {
    "used": 128,
    "limit": 500,
    "remaining": 372,
    "plan": "growth"
  }
}

Summarize with AI

Markdown version for LLMsllms.txt

Something unclear or out of date? Ask Warmi in the chat, or email support@warmerly.com.