§01Authentication

All requests authenticate with a Bearer token in the Authorization header. Keys look like cm_live_... for production and cm_test_... for sandboxing.

curl https://api.checkmail.dev/v1/verify?email=john@example.com \
  -H "Authorization: Bearer cm_live_xxxxxxxxxxxxxxxx"

Requests without a valid key return 401 invalid_api_key. Keep keys out of client-side code.

§02Rate limits

CheckMail applies a per-domain global rate limit on outbound SMTP probes. When a target mail server is being hit too frequently across the platform, CheckMail will not hammer it. The request instead resolves with status: "unknown" rather than returning HTTP 429.

This protects your key from being graylisted by the receiving server and keeps your code path predictable. Unknown results are free. You are only charged for definitive answers (valid, invalid, catch_all, disposable). Retry later for a real answer at no extra cost.

Account-level HTTP 429s only occur if you issue an extreme burst against the API itself (many hundreds of requests/second from a single key).

§03GET /v1/verify

GET /v1/verify Verify a single email address.

Query parameters

NameTypeDescription
emailstringRequired. The email address to verify.

Example request

curl https://api.checkmail.dev/v1/verify \
  -G --data-urlencode "email=john@example.com" \
  -H "Authorization: Bearer cm_live_..."

Response schema

{
  "email": "john@example.com",
  "status": "valid",
  "checks": {
    "syntax": true,
    "mx_found": true,
    "smtp_valid": true,
    "catch_all": false,
    "disposable": false,
    "role_based": false,
    "free_provider": false
  },
  "suggestion": null,
  "mx_host": "aspmx.l.google.com",
  "cached": false,
  "ms": 1243
}

Status values

StatusMeaning
validSyntax, DNS and SMTP all succeeded. Mailbox exists.
invalidAddress is syntactically broken, domain has no MX, or SMTP rejected the recipient.
catch_allDomain accepts mail for any local part. Individual mailbox cannot be confirmed.
disposableAddress belongs to a known throwaway provider (mailinator, tempmail, etc.).
unknownTarget mail server is temporarily unreachable or under per-domain rate limit. Retry later.

§04POST /v1/verify/batch

POST /v1/verify/batch Verify up to 500 emails in one request.

Request body

{ "emails": ["a@foo.com", "b@bar.com"] }

Example

curl https://api.checkmail.dev/v1/verify/batch \
  -H "Authorization: Bearer cm_live_..." \
  -H "Content-Type: application/json" \
  -d '{"emails":["a@foo.com","b@bar.com"]}'

Response

{
  "results": [
    { "email": "a@foo.com", "status": "valid", "checks": { ... }, ... },
    { "email": "b@bar.com", "status": "invalid", "checks": { ... }, ... }
  ],
  "charged": 2
}

Each element matches the /v1/verify response schema. One credit per definitive result. unknown results are not charged. The charged field in the response tells you the actual credit deduction. Batches must have 500 addresses or fewer; more returns 400 batch_too_large. Your account must have enough credits at request time to cover the worst case (all emails), but the actual charge is reduced to the definitive-result count after processing.

§05GET /v1/account

GET /v1/account Retrieve the account tied to your API key.

{
  "email": "dev@acme.com",
  "credits_remaining": 8421,
  "auto_topup": {
    "enabled": true,
    "threshold": 1000,
    "topup_credits": 10000
  }
}

§06GET /v1/domain/:domain

GET /v1/domain/:domain Pull a structured email-infrastructure report for any domain. MX records, SPF, DKIM, DMARC, mail provider, catch-all, disposable status, SMTP banner. 1 credit per request, including cache hits (cached results still bill — you're paying for the answer, not the compute). Results are cached for 24 hours and refreshed in the background after that.

The same data is exposed publicly as a server-rendered HTML page at https://checkmail.dev/domain/:domain — no API key required. The JSON endpoint is for programmatic use.

Example request

curl https://api.checkmail.dev/v1/domain/gmail.com \
  -H "Authorization: Bearer cm_live_..."

Response schema

{
  "domain": "gmail.com",
  "mx_found": true,
  "primary_mx": "gmail-smtp-in.l.google.com",
  "mx_records": [
    { "priority": 5, "host": "gmail-smtp-in.l.google.com", "ips": ["142.250.x.x"] }
  ],
  "spf": { "raw": "v=spf1 ...", "valid": true, "allPolicy": "~all", "mechanisms": [...] },
  "dmarc": { "raw": "v=DMARC1; p=reject; ...", "policy": "reject", "pct": 100, "rua": [...], "ruf": [...] },
  "has_dkim": true,
  "dkim_selectors_found": ["google"],
  "is_catch_all": false,
  "is_disposable": false,
  "is_free_provider": true,
  "mail_provider": { "name": "Google Workspace", "type": "business", "slug": "google-workspace" },
  "smtp_banner": "220 mx.google.com ESMTP ...",
  "smtp_supports_tls": true,
  "response_time_ms": 142,
  "checked_at": 1759872345,
  "ms": 8
}

Note: is_catch_all may be null when the destination MX rate-limited the catch-all probe; the credit is still consumed because every other field is populated.

§07Auto-topup

Auto-topup keeps your account from running dry. When credits_remaining drops below threshold, CheckMail charges the card on file and adds topup_credits. Because the trigger fires before you hit zero, there is no latency impact on the request that crossed the line.

Enable

POST /v1/account/auto-topup/enable

{ "threshold": 1000, "topup_credits": 10000 }

Update

POST /v1/account/auto-topup/update Same body as enable. Changes take effect immediately.

Disable

POST /v1/account/auto-topup/disable No body. Future topups are halted; existing credits remain usable.

§08Errors

Errors return a JSON body of the form { "error": "code", "message": "..." }.

HTTPCodeMeaning
400missing_emailNo email parameter supplied.
400batch_too_largeBatch exceeds 500 addresses.
401invalid_api_keyMissing, malformed, or revoked key.
402insufficient_creditsAccount out of credits; auto-topup not enabled.
402auto_topup_failedCard on file was declined during topup.
429rate_limitedAccount-level API burst limit exceeded.

§09Caching & privacy

Verification results are cached for 24 hours, keyed by the SHA-256 hash of the lowercased email. A cache hit returns "cached": true and does not contact the destination mail server.

Raw email addresses are never written to disk. Only the hash, status, checks, and MX host are stored. This keeps CheckMail GDPR-safe by design. There is no personal data to leak, export, or delete.

§10Integrations

Language-specific tutorials with runnable examples, batch verification, error handling, and retries:

  • Node.js - built-in fetch, async/await, batch helper
  • Python - requests with session reuse and typo suggestions
  • PHP - cURL with a reusable CheckMailClient class
  • Ruby - net/http, Sidekiq-friendly client
  • Go - typed structs, context-aware retries, pooled http.Client