Verification API
Verify a single email address in real-time and get detailed results including deliverability status, risk flags, and enrichment data.
Endpoint
/v1/verifyRequest Body
| Parameter | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Email address to verify |
include_activity | boolean | No | Include engagement activity data (default: false) |
options.deep_verify | boolean | No | Request 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_ai | boolean | No | Include AI-powered analysis (default: true) |
options.skip_cache | boolean | No | Force 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.enrich | boolean | No | Return 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_ms | integer | No | How 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 -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.
{
"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.
| Field | Type | Description |
|---|---|---|
job_id | string | Identifier of this check. GET /v1/jobs/{job_id} returns the same result together with its billing status. |
email | string | The address that was verified. |
status | string | The verdict. See Status Values below. |
sub_status | string | The specific reason behind status. See Sub-status below. Omitted when there is nothing more specific to say. |
quality_score | integer 0–100 | Overall quality. See Quality Score below. |
verification | object | The individual checks that produced the verdict. Fields below. |
flags | object | Risk signals about the address. Fields below. |
domain_info | object | The domain’s provider type and email-authentication posture. Fields below. Omitted when the domain could not be looked up. |
ai | object | 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. |
enrichment | object | 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. |
activity | object | Engagement activity for the address. Present only when include_activity was sent; see the Activity API. |
domain_reputation | object | 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_used | number | What this request cost: 1 for a fresh check, 0.5 for a cached result. |
credits_remaining | number | Your credit balance after this request. |
from_cache | boolean | 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_seconds | integer | How old the cached result is. Present only when from_cache is true. |
processing_time_ms | integer | Time the verification worker spent on the check, in milliseconds. |
verified_at | string (ISO 8601) | When the result was produced. For a cached result this is the time of the original check, not of this request. |
request_id | string | 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.
| Field | Type | Description |
|---|---|---|
syntax_valid | boolean | The address is syntactically valid. |
domain_exists | boolean | The domain has DNS records. |
mx_found | boolean | The domain publishes MX records, so it can receive mail. |
mx_records | string[] | The MX hostnames found. Omitted when there are none. |
smtp_check | boolean | Whether the SMTP mailbox probe succeeded. Omitted when no probe was attempted (for example a domain with no MX records). |
mailbox_exists | boolean | The mail server accepted the mailbox (RCPT TO). Omitted when the probe did not reach that point. |
catch_all | boolean | The domain accepts mail for any address, so an accepted mailbox proves nothing on its own. |
provider_accept_all | boolean | The provider accepts every address at the provider level, which makes the mailbox probe inconclusive. Present only when true. |
greylisted | boolean | The mail server deferred the probe with a temporary rejection. |
smtp_code | integer | The mail server’s last SMTP reply code. Omitted when no probe ran. |
smtp_message | string | 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.
| Field | Type | Description |
|---|---|---|
disposable | boolean | The domain is a disposable or temporary mail provider. |
role_account | boolean | A role address such as info@ or support@ rather than a person’s. |
free_provider | boolean | A free consumer provider such as Gmail or Yahoo. |
spam_trap | boolean | A known spam trap. Do not send. |
abuse_email | boolean | The address or domain is associated with abuse reports. |
honeypot | boolean | The address appears to be a honeypot set up to catch senders. |
complainer | boolean | The address is known to mark mail as spam. |
bot_generated | boolean | 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.
| Field | Type | Description |
|---|---|---|
domain | string | The domain part of the address. |
provider_type | string | One of free, corporate, educational, government or unknown. |
auth_score | integer 0–100 | How completely the domain has set up SPF, DKIM and DMARC. Omitted when the lookup failed. |
auth_grade | string | 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_spf | boolean | The domain publishes an SPF record. |
spf_policy | string | 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_dmarc | boolean | The domain publishes a DMARC record. |
dmarc_policy | string | The DMARC policy: None, Quarantine or Reject. Omitted when there is no DMARC record. |
has_dkim | boolean | A DKIM key was found at a common selector. |
Status Values
| Status | Description |
|---|---|
valid | SMTP accepted the address and no catch-all/provider-limited caveat was detected during this run |
invalid | No valid mail server or SMTP rejected the mailbox |
risky | May receive mail, but risk signals or incomplete proof remain |
unknown | Could not determine deliverability |
catch_all | Domain accepts all emails - individual mailbox unverifiable |
disposable | Temporary email address |
role | Role-based email (info@, support@) |
spam_trap | Known spam trap - do not send |
pending | No 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