# MailValid — AI Agent Integration Guide

> Everything an AI agent needs to verify email addresses with MailValid. Paste
> this whole document into your agent's context. Base URL: https://mailvalid.io

MailValid is an email-verification API: real-time single and bulk verification
with syntax, DNS/MX, and SMTP mailbox checks, plus disposable-domain,
role-account, free-provider, and catch-all detection. Pay-as-you-go credits;
new accounts include free credits.

## Authentication

1. Get an API key at https://mailvalid.io (dashboard -> API Keys). Keys look like `mv_live_...`.
2. Send it on every authenticated request in the `X-API-Key` header
   (or `Authorization: Bearer mv_live_...`).

1 credit per completed result (valid/invalid/catch-all/do-not-mail). Unknown
results, cached repeats, and duplicates are free — as are reads (status, lists,
balance) and the demo endpoint.

## Two ways to integrate

### A) MCP server (recommended for AI agents/assistants)

Remote MCP server over the Streamable HTTP transport at `https://mailvalid.io/mcp`. Its tools
run through the same verification engine and credit system as the REST API.

Easiest — the `@mailvalid/mcp` launcher (works in any MCP client, including
stdio-only ones like Claude Desktop). Add to your client's MCP config:

    {
      "mcpServers": {
        "mailvalid": {
          "command": "npx",
          "args": ["-y", "@mailvalid/mcp"],
          "env": { "MAILVALID_API_KEY": "mv_live_your_key_here" }
        }
      }
    }

Or connect directly (clients with native remote MCP, e.g. Claude Code):

    claude mcp add --transport http mailvalid https://mailvalid.io/mcp/ --header "X-API-Key: mv_live_your_key_here"

MCP tools (auth = needs API key):
- `verify_email(email)` — verify one address. 1 credit; `unknown`/cached are free. [auth]
- `submit_bulk_verification(emails, webhook_url?)` — queue a list; reserves N credits. [auth]
- `get_bulk_job(job_id)` — status, progress, and results. Free. [auth]
- `list_bulk_jobs(page?, page_size?, status?)` — list your jobs. Free. [auth]
- `cancel_bulk_job(job_id)` — cancel and release reserved credits. Free. [auth]
- `get_credit_balance()` — balance + lifetime usage. Free. [auth]
- `verify_email_demo(email)` — syntax/disposable/role/free checks only (no SMTP). No key. Free.
- `get_api_discovery()` — machine-readable discovery URLs. No key. Free.

### B) REST API

Base: `https://mailvalid.io/api/v1`. Authenticate with the `X-API-Key` header.

**Verify one email** — `POST /api/v1/verify/single` — 1 credit (`unknown`/cached free)

    curl -X POST https://mailvalid.io/api/v1/verify/single \
      -H "X-API-Key: mv_live_your_key_here" -H "Content-Type: application/json" \
      -d '{"email": "user@example.com"}'

Response: `{"success": true, "credits_used": 1, "result": { ...VerificationResult... }}`

**Verify many (async)** — `POST /api/v1/verify/bulk` — reserves N credits
Body: `{"emails": ["a@x.com", "b@y.com"], "webhook_url": "https://..."}`
(`webhook_url` optional — POSTed when the job completes; max 10000 emails per job.)
Response: `{"job_id": "...", "status": "pending", "total_emails": 2, "credits_reserved": 2}`
Final charge = 1 credit per fresh billable result; duplicates, cached, and `unknown` are free.

**Bulk job status** — `GET /api/v1/verify/bulk/{job_id}` — free
Response: `{"job_id", "status", "total_emails", "processed_emails", "valid_count",
"invalid_count", "progress_percentage", "results", "error_message", "webhook_url",
"webhook_delivered", "started_at", "completed_at", "created_at"}`.
`results` (array of VerificationResult) is populated once `status` is `completed` or `cancelled`.

**List bulk jobs** — `GET /api/v1/verify/bulk?page=1&page_size=20&status=` — free
Response: `{"jobs": [...summaries...], "total", "page", "page_size", "has_more"}`

**Cancel a job** — `DELETE /api/v1/verify/bulk/{job_id}` — free (releases reserved credits)
Response: `{"success": true, "message": "...", "job_id": "..."}`

**Download results** — `GET /api/v1/verify/bulk/{job_id}/download?format=json|csv` — free

**Credit balance** — `GET /api/v1/credits` — free
Response: `{"balance": 1234, "lifetime_used": 56}`

**Submit bounce/delivery events (optional, improves future accuracy)** —
`POST /api/v1/webhooks/bounce` — free
Body: `{"events": [{"email": "...", "event_type": "hard_bounce", "bounce_code": "550",
"bounce_message": "...", "timestamp": "<ISO 8601, optional>"}]}`
`event_type` is one of: `hard_bounce`, `soft_bounce`, `delivered`, `complaint`,
`unsubscribe`. Max 1000 events per request.
Response: `{"success": true, "processed": N, "errors": 0, "message": "..."}`

**Demo (no key, rate-limited)** — `POST /api/demo/verify`
Body: `{"email": "..."}` -> basic validation only (no SMTP). `credits_used: 0`.

## The VerificationResult object

| field | type | meaning |
|---|---|---|
| email | string | the address checked (lowercased) |
| status | string | `valid` / `invalid` / `catch_all` / `unknown` / `do_not_mail` |
| is_valid | bool | convenience flag for a deliverable address |
| syntax_valid | bool | passes RFC syntax |
| domain | string \| null | the domain part |
| domain_valid | bool | domain resolves |
| has_mx | bool | domain has MX records |
| mx_records | array | `[{"priority": int, "host": string}]` |
| smtp_checked | bool | an SMTP mailbox probe was performed |
| smtp_response_code | int \| null | SMTP code (e.g. 250, 550) |
| is_disposable | bool | temporary/throwaway domain |
| is_role_based | bool | role address (info@, support@, ...) |
| is_catch_all | bool | domain accepts all addresses |
| is_free_provider | bool | free mailbox provider (gmail, etc.) |
| confidence_score | int | 0-100 |
| status_reason | string | machine-readable sub-status (see below) |
| provider | string \| null | detected provider (google, microsoft, ...) |
| verification_time_ms | number \| null | time taken |
| cached | bool | served from a recent cached result (free) |

### status meanings
- `valid` — SMTP confirmed the mailbox exists. Safe to send.
- `invalid` — will bounce (bad syntax, no domain, or mailbox not found).
- `catch_all` — domain accepts all addresses; the individual mailbox can't be confirmed.
- `unknown` — could not determine (server blocked, timeout, provider blocks SMTP). Not charged.
- `do_not_mail` — may exist but is risky (disposable or role-based).

### status_reason values
`mailbox_confirmed`, `mailbox_not_found`, `failed_syntax_check`, `no_dns_entries`,
`no_mx_records`, `disposable`, `role_based`, `accept_all`, `antispam_system`,
`failed_smtp_connection`, `greylisting`, `mail_server_temporary_error`.

## Billing
- Single verify: 1 credit for a billable result (`valid`/`invalid`/`catch_all`/`do_not_mail`).
  `unknown` results and recent cached repeats are free.
- Bulk: up to N credits are reserved at submit; the final charge is 1 credit per
  fresh billable result. Duplicates, cached, and `unknown` are free; cancelling
  releases the reservation.
- Status checks, listing, downloads, credit balance, and the demo endpoint are free.

## Errors
On error the API returns a JSON body with a `detail` message and an HTTP status:
- `401` — missing or invalid API key.
- `402` — insufficient credits.
- `404` — resource not found (e.g. unknown job id).
- `413` — bulk job exceeds your plan's per-job limit.
- `422` — invalid request body.
- `429` — rate limited; wait for the `Retry-After` header's seconds and retry.

## Rate limits
Per API key, per minute, by plan. Responses include `X-RateLimit-Limit`,
`X-RateLimit-Remaining`, and `X-RateLimit-Bucket`. On `429`, back off for the
`Retry-After` duration.

## Machine-readable discovery
- OpenAPI spec: https://mailvalid.io/api/openapi.json
- MCP discovery: https://mailvalid.io/.well-known/mcp.json
- MCP server card: https://mailvalid.io/.well-known/mcp/server-card.json
- This guide (full): https://mailvalid.io/llms-full.txt
- Concise index: https://mailvalid.io/llms.txt
- Human documentation: https://mailvalid.io/docs
