Verification API

Verify a single email address in real-time and get detailed results including deliverability status, risk flags, and enrichment data.

Endpoint

POST/v1/verify

Request Body

ParameterTypeRequiredDescription
emailstringYesEmail address to verify
include_activitybooleanNoInclude engagement activity data (default: false)
options.deep_verifybooleanNoRequest extended checks (default: true). The current production workers attempt SMTP mailbox probing when deep verification is requested and the target mail server supports the check; otherwise Kawaa falls back to syntax, domain, MX, and AI risk analysis. Every option is a JSON boolean — a string such as "false" is rejected with 400 INVALID_OPTION.
options.include_aibooleanNoInclude AI-powered analysis (default: true)
options.skip_cachebooleanNoForce a fresh check instead of serving a recent cached result (default: false). A cached result costs 0.5 credits and sets from_cache: true; a fresh one costs 1.
options.enrichbooleanNoReturn the enrichment block shown in the example response (default: false). Without it the response has no enrichment key. Requires the Business plan or higher — on a lower plan the whole request is rejected with 403 FORBIDDEN, so omit it unless your plan includes it.
options.timeout_msintegerNoHow long, in milliseconds, this request waits for a fresh (cache-miss) check to finish before answering with status: "pending" (default: 24,000). Values are clamped to 5,000–60,000; a non-number is rejected with 400. It only bounds the wait on this endpoint — the check itself continues and GET /v1/jobs/{job_id} returns the result. Not accepted on the batch or file routes.

Any other field, at the top level or inside options, is rejected with 400 BAD_REQUEST naming the supported fields, so a misspelled option can never be silently ignored and charged for.

Example Request

cURL
curl -X POST https://api.kawaa.com/v1/verify \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "john@example.com",
    "include_activity": true,
    "options": {
      "deep_verify": true,
      "include_ai": true
    }
  }'

This request runs on every plan. To also receive the enrichment block, add "enrich": true inside options — that option requires the Business plan or higher, and sending it without one returns 403 instead of a verification result.

Response

Shown with every optional block populated. enrichment appears only when you send options.enrich on a Business-or-higher plan, activity only with include_activity, and ai only when AI analysis returns something to report.

200 OK
{
  "success": true,
  "data": {
    "job_id": "5e809a8a-1d1a-4c92-86f8-2b7395e5d803",
    "email": "john@example.com",
    "status": "risky",
    "sub_status": "catch_all",
    "quality_score": 72,
    "verification": {
      "syntax_valid": true,
      "domain_exists": true,
      "mx_found": true,
      "mx_records": ["mx1.example.com", "mx2.example.com"],
      "smtp_check": true,
      "catch_all": true,
      "greylisted": false,
      "smtp_code": 250,
      "smtp_message": "250 2.1.5 Recipient OK"
    },
    "flags": {
      "disposable": false,
      "role_account": false,
      "free_provider": false,
      "spam_trap": false,
      "abuse_email": false,
      "honeypot": false,
      "complainer": false,
      "bot_generated": false
    },
    "domain_info": {
      "domain": "example.com",
      "provider_type": "corporate",
      "auth_score": 84,
      "auth_grade": "B",
      "has_spf": true,
      "spf_policy": "SoftFail",
      "has_dmarc": true,
      "dmarc_policy": "Quarantine",
      "has_dkim": true
    },
    "enrichment": {
      "first_name": "John",
      "full_name": "John",
      "gender": "male",
      "country": "United States",
      "country_code": "US"
    },
    "ai": {
      "typo_suggestion": null,
      "typo_confidence": null,
      "catch_all_confidence": 15
    },
    "activity": {
      "level": "active",
      "score": 78,
      "last_activity_at": "2026-01-15T10:30:00Z",
      "engagement": { "open_count": 12, "click_count": 5, "reply_count": 1 },
      "deliverability": { "bounce_count": null, "complaint_count": null },
      "confidence": 80,
      "data_points": 18
    },
    "credits_used": 1,
    "credits_remaining": 4998,
    "from_cache": false,
    "processing_time_ms": 1345,
    "verified_at": "2026-02-03T14:22:15.123Z",
    "request_id": "2a4104cd-ef74-4412-89e2-a4859a72bd11"
  }
}

Response Fields

Every field a completed result can carry. A field marked as omitted is absent from the JSON rather than null, so read it with a default. Fields are added without a version change; unknown ones are informational.

Top-level fields

job_idstring
Identifier of this check. GET /v1/jobs/{job_id} returns the same result together with its billing status.
emailstring
The address that was verified.
statusstring
The verdict. See Status Values below.
sub_statusstring
The specific reason behind status. See Sub-status below. Omitted when there is nothing more specific to say.
quality_scoreinteger 0–100
Overall quality. See Quality Score below.
verificationobject
The individual checks that produced the verdict. Fields below.
flagsobject
Risk signals about the address. Fields below.
domain_infoobject
The domain’s provider type and email-authentication posture. Fields below. Omitted when the domain could not be looked up.
aiobject
AI analysis: typo_suggestion and typo_confidence when the address looks like a typo of a known domain, catch_all_confidence on a catch-all domain. Present only when there is something to report.
enrichmentobject
first_name, last_name, full_name, gender, country and country_code where they can be inferred. Present only when options.enrich was sent on a plan that includes it.
activityobject
Engagement activity for the address. Present only when include_activity was sent; see the Activity API.
domain_reputationobject
Reputation of the domain across Kawaa’s activity data: reputation_score, reputation_grade and engagement metrics, as GET /v1/activity/domain/{domain} returns them. Present only when include_activity was sent and there is data for the domain.
credits_usednumber
What this request cost: 1 for a fresh check, 0.5 for a cached result.
credits_remainingnumber
Your credit balance after this request.
from_cacheboolean
true when the result was served from a recent check of the same address rather than a fresh one. Send options.skip_cache to force a fresh check.
cache_age_secondsinteger
How old the cached result is. Present only when from_cache is true.
processing_time_msinteger
Time the verification worker spent on the check, in milliseconds.
verified_atstring (ISO 8601)
When the result was produced. For a cached result this is the time of the original check, not of this request.
request_idstring
Identifier of this API request. Quote it when contacting support.

verification

syntax_validboolean
The address is syntactically valid.
domain_existsboolean
The domain has DNS records.
mx_foundboolean
The domain publishes MX records, so it can receive mail.
mx_recordsstring[]
The MX hostnames found. Omitted when there are none.
smtp_checkboolean
Whether the SMTP mailbox probe succeeded. Omitted when no probe was attempted (for example a domain with no MX records).
mailbox_existsboolean
The mail server accepted the mailbox (RCPT TO). Omitted when the probe did not reach that point.
catch_allboolean
The domain accepts mail for any address, so an accepted mailbox proves nothing on its own.
provider_accept_allboolean
The provider accepts every address at the provider level, which makes the mailbox probe inconclusive. Present only when true.
greylistedboolean
The mail server deferred the probe with a temporary rejection.
smtp_codeinteger
The mail server’s last SMTP reply code. Omitted when no probe ran.
smtp_messagestring
The mail server’s last SMTP reply. Omitted when no probe ran.

flags

disposableboolean
The domain is a disposable or temporary mail provider.
role_accountboolean
A role address such as info@ or support@ rather than a person’s.
free_providerboolean
A free consumer provider such as Gmail or Yahoo.
spam_trapboolean
A known spam trap. Do not send.
abuse_emailboolean
The address or domain is associated with abuse reports.
honeypotboolean
The address appears to be a honeypot set up to catch senders.
complainerboolean
The address is known to mark mail as spam.
bot_generatedboolean
The address looks machine-generated.

domain_info

domainstring
The domain part of the address.
provider_typestring
One of free, corporate, educational, government or unknown.
auth_scoreinteger 0–100
How completely the domain has set up SPF, DKIM and DMARC. Omitted when the lookup failed.
auth_gradestring
auth_score as a letter: A (90–100), B (80–89), C (70–79), D (50–69) or F. Omitted when the lookup failed.
has_spfboolean
The domain publishes an SPF record.
spf_policystring
What the SPF record says about senders it does not list: Pass (+all), Fail (-all), SoftFail (~all) or Neutral (?all). Omitted when there is no SPF record.
has_dmarcboolean
The domain publishes a DMARC record.
dmarc_policystring
The DMARC policy: None, Quarantine or Reject. Omitted when there is no DMARC record.
has_dkimboolean
A DKIM key was found at a common selector.

Status Values

StatusDescription
validSMTP accepted the address and no catch-all/provider-limited caveat was detected during this run
invalidNo valid mail server or SMTP rejected the mailbox
riskyMay receive mail, but risk signals or incomplete proof remain
unknownCould not determine deliverability
catch_allDomain accepts all emails - individual mailbox unverifiable
disposableTemporary email address
roleRole-based email (info@, support@)
spam_trapKnown spam trap - do not send
pendingNo result yet. The check is still running, so the response carries job_id and credits_held instead of a result. Poll GET /v1/jobs/{job_id}.

Sub-status

sub_status is the specific reason behind status — one of mailbox_exists, mailbox_reject, catch_all, provider_accept_all, policy_block, greylisted, temporary_failure, connection_failure, dns_only, no_mx, domain_not_found or syntax_error. Branch on status for the decision and read sub_status for the explanation; new sub-status values may be added without a version change, so treat unknown ones as informational.

Pending Responses

A cache-miss verification that has not finished within the request window returns 200 with status: "pending" rather than holding the connection open. There is no quality_scoreor result block on this response — treat it as "ask again shortly", not as a verdict:

{
  "success": true,
  "data": {
    "job_id": "8f14e45f-ceea-467a-9a1f-2b0c8f9c4a11",
    "email": "user@example.com",
    "status": "pending",
    "message": "Verification is still in progress. 1 credit is held while processing; if the worker fails before producing a result, job reconciliation refunds it. Use GET /v1/jobs/{job_id} to check status.",
    "credits_held": 1
  }
}

Poll GET /v1/jobs/{job_id} until it reports a terminal status. The held credit is refunded by job reconciliation if the check never produces a result, so a pending response is not a charge for nothing.

Quality Score

The quality_score is a 0-100 score indicating overall email quality:

  • 90-100: Excellent - lowest observed risk
  • 70-89: Good - Generally safe
  • 50-69: Fair - Review recommended
  • 0-49: Poor - High risk of issues

For catch-all domains, policy blocks, and provider-limited responses, see Understanding Results & Provider Limits.

Credits

  • 1 credit per verification (cache miss)
  • 0.5 credits for cached results (within 24 hours)
  • Async cache-miss checks hold 1 credit while processing
  • Held credits are refunded by job reconciliation if the worker fails before producing a result
  • Activity data included at no extra cost when available