---
title: "Verify one email address."
method: POST
path: "/v1/verify"
---

# Verify one email address.

`POST /v1/verify`

## Request

- Base URL: `https://api.bounceless.io`
- URL: `https://api.bounceless.io/v1/verify`
- Auth: API key in header `X-Api-Key`

## Headers

- `Idempotency-Key` string, required

## Request body

- VerifyInput
  - `email` string, email, required
  - `timeoutMs` integer

## Response `200`

Completed.

- VerifyResult
  - `status` 'completed', required
  - `resultCategory` 'deliverable' | 'catch_all' | 'unknown' | 'undeliverable' | 'risky' | 'recheck' | 'pending' | 'other': Observed result category, independent of the recommendation and billing.
  - `verifyRequestId` string, uuid: Present when async results are enabled for the account.
  - `createdAt` string, date-time
  - `decision` object
  - `presend` ClientDecisionEnvelopeV1
    - `contract_version` 1, required
    - `status` 'running' | 'completed' | 'deferred' | 'recheck_scheduled' | 'failed' | 'rejected', required
    - `outcome` 'pending' | 'actionable_verdict' | 'non_verdict', required
    - `decision` 'send' | 'suppress' | 'send_with_caution' | 'enrich_first' | 'recheck_later' | 'manual_review', nullable, required
    - `verdict` 'DELIVERABLE' | 'UNDELIVERABLE_NO_MAILBOX' | 'UNDELIVERABLE_NO_MAIL_SERVER' | 'UNDELIVERABLE_BOUNCED' | 'CATCH_ALL' | 'MAILBOX_FULL' | 'DISPOSABLE' | 'ROLE' | 'SPAMTRAP' | 'RISKY_SPAMTRAP' | 'BLACKLISTED' | 'GREYLISTED' | 'TRANSIENT' | 'UNKNOWN' | 'UNKNOWN_WEBMAIL', nullable, required
    - `provider_family` string, nullable, required
    - `confidence` number, nullable, required
    - `reasons` string[], required
    - `evidence_class` string, nullable, required
    - `retry` object, nullable, required
      - `eligible_at` string
      - `expires_at` string
      - `affinity_required` boolean
    - `billing` object, required
      - `disposition` 'held' | 'debited' | 'released', required
      - `credits` integer, nullable, required: Actually settled normal verification credits; null when exact historical row attribution is unavailable. New standard jobs include catch-all resolution with no supplemental debit; historical jobs retain their accepted quote. Catch-all charges are reported separately.
    - `history` object, nullable, required
  - `evidence` ControlEvidence[]: Optional per-check evidence (BL-11576). Present only for checks the engine actually reported; the key is omitted when none were. A missing check means unavailable, never pass. Never derived from the verdict.
    - `kind` 'format' | 'mx' | 'mailbox' | 'catch_all' | 'risk', required
    - `state` 'pass' | 'fail' | 'unknown' | 'not_run', required: Taken from the engine classification of that check (SMTP matched rule, catch-all reason), never the raw SMTP code class. unknown = ran, inconclusive (incl. greylist, or a block of our sending IP); not_run = engine attested it did not run; fail = ran with an unfavourable observation.
    - `reason` string, required: Stable machine token, e.g. rcpt_accepted, rcpt_rejected, mailbox_full, rcpt_inconclusive, probe_not_attempted, catch_all_detected, catch_all_ruled_out, catch_all_unconfirmed, list_match.
    - `observedAt` string, date-time, nullable, required: Engine observation instant (cycle attestation, else engine verified_at). null when the engine did not provide one.
    - `provenance` object, nullable, required
      - `mechanism` string, nullable, required
      - `mechanismRevision` string, required
      - `freshness` 'fresh_probe' | 'fresh_cache', required
    - `measurement` object: Allowlisted measurements (smtpCode, enhancedCode, probes, accepts, quorum, list).
  - `checks` Checks: Per-control checks (BL-11693, engine wire BL-11692). Always four keys. States are the engine's, passed through; a check the engine did not emit is {state:"unknown", reason_code:"evidence_unavailable"}. Never derived from the verdict.
    - `syntax` Check, required
      - `state` 'passed' | 'failed' | 'unknown' | 'not_run' | 'error', required
      - `reason_code` string, required: Machine token; evidence_unavailable when the engine emitted nothing usable.
      - `observed_at` string, date-time
      - `detail` string: Machine token only; free text is never forwarded.
    - `mail_records` Check, required
      - `state` 'passed' | 'failed' | 'unknown' | 'not_run' | 'error', required
      - `reason_code` string, required: Machine token; evidence_unavailable when the engine emitted nothing usable.
      - `observed_at` string, date-time
      - `detail` string: Machine token only; free text is never forwarded.
    - `mailbox` Check, required
      - `state` 'passed' | 'failed' | 'unknown' | 'not_run' | 'error', required
      - `reason_code` string, required: Machine token; evidence_unavailable when the engine emitted nothing usable.
      - `observed_at` string, date-time
      - `detail` string: Machine token only; free text is never forwarded.
    - `risk` Check, required
      - `state` 'passed' | 'failed' | 'unknown' | 'not_run' | 'error', required
      - `reason_code` string, required: Machine token; evidence_unavailable when the engine emitted nothing usable.
      - `observed_at` string, date-time
      - `detail` string: Machine token only; free text is never forwarded.
  - `isRole` boolean, nullable: Engine role label (BL-11811). Not a check, never gating; null when the engine did not emit it.
  - `engine` VerifyEngine
    - `verifiedAt` string, nullable
    - `attempts` integer, nullable
    - `providerFamily` string, nullable
  - `requestId` string, required

## Other responses

- `202`: The bounded synchronous wait ended before a verdict. When async results are enabled for the account, the request is persisted: the response carries verifyRequestId, the credit hold stays held, and GET /v1/verify/{id} returns the result (billed once, at the first terminal result; non-verdicts are not billed). A replay with the same Idempotency-Key answers from that stored request without a new verification; the same key with another address domain returns 409 idempotency_key_reused. Otherwise no verifyRequestId is returned, no credits are charged and the hold is released: retry POST /v1/verify with the same Idempotency-Key and body, using a bounded client retry policy.
- `400`: Error response.
- `401`: Error response.
- `402`: Error response.
- `409`: Error response.
- `429`: Rate limit exceeded.
- `502`: Error response.
- `503`: Error response.

---

[API](https://skmtc.dev/bounceless/apis/bounceless-api.md) - [All operations](https://skmtc.dev/bounceless/apis/bounceless-api/llms.txt) - [OpenAPI document](https://skmtc.dev/bounceless/apis/bounceless-api/revisions/d80696082abb?raw)
