Product Guide
Everything you need to go from a messy contact list to a clean, send-ready one. Building an integration instead? Jump to the API reference.
Getting started
Create an account and you land in the dashboard with 10,000 free credits, no card required. The sidebar is the whole product: Verify Email for one-off checks, Bulk Lists for cleaning whole files, Deliverability for domain audits, History for your activity and credit ledger, and API for programmatic access.
Verify one email
Open Verify Email, type an address and hit verify. InboxValid has a live SMTP conversation with the receiving mail server, so the answer reflects whether the mailbox exists right now, not what a stale database remembers. Each check costs 1 credit and takes about 2 seconds. A check that comes back unknown or blocked is not charged.
You get back a status pill plus the details behind it:
| Status | What to do with it |
|---|
| valid | Safe to send |
| invalid | Remove it; it will bounce |
| invalid_syntax | The address is not written as a valid email address, for example a missing @ or a character that cannot be in one. Do not send to it: fix the typo or drop the row. |
| catch_all | The domain accepts everything, so the mailbox can't be confirmed. Send cautiously. |
| risky | Deliverable but flagged, for example a role address like info@. Decide per campaign. |
| disposable | A throwaway inbox. Remove it from any long-term list. |
| unknown | The server wouldn't give a clear answer (greylisting and similar). Re-verify later. |
| blocked | Not checked on purpose. The domain's mail sits behind a security gateway that does not give reliable answers to verification (reason behind_gateway). Final, and not charged. Treat it like an address you have not verified and decide by your own risk: keep it for one to one mail you would send anyway, and leave it out of bulk sends where a bounce would hurt you. |
The trust score (0 to 100, higher is safer) combines the SMTP outcome with flags like role account, free provider and catch-all, so you can set your own cutoff instead of trusting a single label.
Clean a bulk list
Open Bulk Lists. You can paste addresses directly or upload a file: CSV, Excel (.xlsx) or JSON, up to 1,000,000 emails per job. Pasted lists are capped at 50,000 addresses; upload a file for more. For files, InboxValid auto-detects which column holds the emails; click a different column header in the preview if it guessed wrong. Duplicates and non-email values are dropped before anything is charged.
Credits are held upfront, 1 per email, and anything unused is refunded when the job settles. While the job runs you see a live progress bar and counters. When it finishes you get:
Metrics and charts. Per-status counts with percentages and a breakdown donut so you can judge list quality at a glance, for example "82% valid, 9% invalid, 6% catch-all".
Email search. Rather than scrolling thousands of rows, type any part of an address to look up its exact result, status, trust score and reason.
CSV export. Download all results, or filter the export to one status. "Valid only" gives you a send-ready file for your campaign tool.
Deliverability audits
Clean lists only matter if your own domain is set up to deliver. Open Deliverability, enter a sending domain and InboxValid grades it (A to F) across 8 checks: MX, SPF, DKIM, DMARC, PTR, DNSBL blocklist listing, MTA-STS and BIMI. Each failing check comes with a concrete recommendation, and you get a verdict on whether the domain passes the Gmail, Yahoo and Microsoft bulk-sender requirements. An audit costs 5 credits.
Spam-trap risk
A mailbox can accept mail and still be unsafe for a campaign. Read the spam-trap guide to understand pristine, recycled, typo and domain traps, and how InboxValid returns trap evidence in trap_kind and trap_confidence(typo traps and addresses on InboxValid's spam-trap list are detected today).
Credits & billing
| Action | Cost |
|---|
| Verify one email | 1 credit |
| Bulk job | 1 credit per email, held upfront, unused refunded when the job settles |
| Deliverability audit | 5 credits |
There are three ways to hold credits, and you are on exactly one at a time. Free grants its credits once, when you sign up, and never tops them up. A subscription grants its allowance every cycle and rolls the unused part into the next one, for as long as it runs; cancel and the rolled-over balance goes with it. Credit packs are bought outright under Buy Credits (via Razorpay: international cards for USD, or UPI, cards and netbanking for INR) and are valid for 12 months from purchase, with volume tiers lowering the per-credit price automatically. A subscription and credit packs cannot run at the same time, so packs are unavailable while a subscription is active.
Plan credits are always spent before bought ones, so a plan ending never takes credits you paid for. History keeps the full ledger: every grant, charge, refund and expiry with the balance after each movement, plus an activity trail of everything done on the account.
API keys
The API page in the dashboard manages your keys and includes a quickstart. Create a key, copy it when shown (it appears exactly once) and pass it as a bearer token:
Authorization: Bearer iv_live_...
Everything the dashboard does is available over the API: verify at signup, clean lists from your CRM, watch job progress over SSE and export results as CSV. See the API reference for every endpoint with request and response examples.
Try it on your own list10,000 free credits cleans a real list, not a demo.
Get started freeAPI Docs
The InboxValid REST API lets you verify emails, audit domain deliverability and run bulk jobs from your own code. Every endpoint returns JSON and is authenticated with an API key. New to the product? Start with the product guide. Changes that may need work on your side are in the changelog.
Authentication
Create an API key from the API page in the dashboard. The full key (starting with iv_live_) is shown once at creation, so store it securely. Pass it as a bearer token on every request:
Authorization: Bearer iv_live_xxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
The base URL is https://api.inboxvalid.com. Requests without a valid key return 401 with { "error": "invalid api key" }.
Credits & errors
Verification is metered in credits: 1 credit per single verify, 1 per email in a bulk job (held upfront, unused credits refunded) and 5 per deliverability audit. New accounts start with 10,000 free credits.
Errors always have the shape { "error": "message" }:
| Status | Meaning |
|---|
| 400 | Bad request, for example a malformed email or domain |
| 401 | Missing or invalid API key |
| 402 | Insufficient credits (code: "insufficient_credits") |
| 403 | Organization suspended |
| 404 | Resource not found |
| 429 | Rate limit exceeded, or your plan's daily credit cap is spent (code: "daily_cap_reached", with cap) |
| 502 | Verification engine temporarily unavailable |
Verify an email
POST/v1/verify
Runs a live check on a single address: syntax, MX lookup and an SMTP conversation with the receiving server, plus catch-all, disposable, role-account and free-provider detection. Costs 1 credit. An unknown or blocked result is not charged.
curl -X POST https://api.inboxvalid.com/v1/verify \
-H "Authorization: Bearer iv_live_..." \
-H "Content-Type: application/json" \
-d '{"email": "sara@acmecorp.com"}'
{
"email": "sara@acmecorp.com",
"domain": "acmecorp.com",
"status": "valid",
"sub_status": null,
"trust_score": 88,
"risk_score": 12,
"mx_found": true,
"mx_host": "aspmx.l.google.com",
"provider": "Gmail",
"is_role": false,
"is_disposable": false,
"is_catch_all": false,
"trap_kind": null,
"trap_confidence": null,
"free": false,
"verified_at": "2026-06-12T09:00:00Z"
}
verified_at is when the mailbox was last probed live. A single check is probed live unless an earlier check found the address invalid recently (see Bulk jobs for the windows), so this can be older than the request. Send "fresh": true to force a live check.
Google Workspace domains accept mail for any address, so a plain check of an address there comes back catch_all. Send "deepVerify": true to ask Google itself whether the mailbox exists: when it answers, the result is valid or invalid and carries "deep_verified": true. Each address the oracle resolves costs 3 credits extra; one it cannot resolve costs nothing extra.
curl -X POST https://api.inboxvalid.com/v1/verify \
-H "Authorization: Bearer iv_live_..." \
-H "Content-Type: application/json" \
-d '{"email": "sara@acmecorp.com", "deepVerify": true}'
status is one of:
| Status | Meaning |
|---|
| valid | The mailbox exists and accepts mail |
| invalid | The mailbox was rejected by the server |
| invalid_syntax | The address is not written as a valid email address, for example a missing @ or a character that cannot be in one. Do not send to it: fix the typo or drop the row. |
| catch_all | The domain accepts mail for any address |
| risky | Deliverable but flagged, for example a role account |
| disposable | A temporary or throwaway domain |
| unknown | Inconclusive, for example greylisting; does not count against your list quality |
| blocked | Not checked on purpose. The domain's mail sits behind a security gateway that does not give reliable answers to verification (reason behind_gateway). Final, and not charged. Treat it like an address you have not verified and decide by your own risk: keep it for one to one mail you would send anyway, and leave it out of bulk sends where a bounce would hurt you. |
sub_status adds detail when available (for example mailbox_not_found or mailbox_full), and trust_score is 0 to 100, higher is safer; risk_score is its deprecated inverted alias (100 - trust_score). When maintained intelligence finds a trap signal, the top-level status remains risky and the optional trap_kind and trap_confidence fields describe the evidence. No verifier can guarantee detection of every private trap. When the status is blocked, the optional mail_gateway field holds the security gateway's name on a single check result and on a finder result; it is absent unless the status is blocked. A finder lookup can also end with status blocked. A blocked or unknown result carries a neutral trust score (50 for blocked, usually 50 for unknown) that says nothing about the address, so filter by status, not by score.
Deliverability audit
POST/v1/deliverability
Grades a sending domain across 8 checks: MX, SPF, DKIM, DMARC, PTR, DNSBL listing, MTA-STS and BIMI, and tells you whether it passes the Gmail, Yahoo and Microsoft bulk-sender rules. Costs 5 credits.
curl -X POST https://api.inboxvalid.com/v1/deliverability \
-H "Authorization: Bearer iv_live_..." \
-H "Content-Type: application/json" \
-d '{"domain": "acmecorp.com"}'
{
"domain": "acmecorp.com",
"score": 85,
"grade": "A",
"passes_bulk_rules": true,
"provider": "Gmail",
"checks": {
"spf": { "state": "pass", "detail": "SPF record found: v=spf1 ...", "recommendation": null },
"dkim": { "state": "pass", "detail": "DKIM selectors found", "recommendation": null },
"dmarc": { "state": "pass", "detail": "DMARC policy: reject", "recommendation": null },
"mx": { "state": "pass", "detail": "MX records: aspmx.l.google.com (10)" },
"ptr": { "state": "warn", "detail": "PTR record not found for sending IP",
"recommendation": "Configure reverse DNS on your sending IP" },
"dnsbl": { "state": "pass", "detail": "Not listed on common blocklists" },
"mta_sts": { "state": "warn", "detail": "No MTA-STS policy" },
"bimi": { "state": "warn", "detail": "No BIMI record" }
}
}
Each check is pass, warn or fail, with a human-readable detail and a fix recommendation where one applies.
Bulk jobs
POST/v1/jobs/bulk
Verifies up to 1,000,000 emails per job, in a JSON body of at most 16 MB; for a bigger list, upload the file. The list is de-duplicated automatically and credits are held upfront, 1 per email; anything unused is refunded when the job settles.
curl -X POST https://api.inboxvalid.com/v1/jobs/bulk \
-H "Authorization: Bearer iv_live_..." \
-H "Content-Type: application/json" \
-d '{"name": "Q3 leads", "emails": ["a@acme.com", "b@example.org"]}'
{ "id": "job_cuid", "total": 2, "status": "queued" }
To carry extra columns (e.g. name, company) through to your results and CSV export, send rows instead of emails, plus metaColumns listing the keys to include:
curl -X POST https://api.inboxvalid.com/v1/jobs/bulk \
-H "Authorization: Bearer iv_live_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Q3 leads",
"metaColumns": ["first_name", "company"],
"rows": [
{"email": "a@acme.com", "meta": {"first_name": "Ada", "company": "Acme"}},
{"email": "b@example.org", "meta": {"first_name": "Ben", "company": "Example"}}
]
}'
Verdicts are reused from an earlier check when one is recent enough: 7 days for valid and risky, 30 days for invalid, 90 days for catch_all and disposable. Every result carries verified_at, the time the mailbox was last probed live, so you can always see how old an answer is. To re-probe every address instead, send "fresh": true. It costs the same credits and takes longer.
curl -X POST https://api.inboxvalid.com/v1/jobs/bulk \
-H "Authorization: Bearer iv_live_..." \
-H "Content-Type: application/json" \
-d '{"name": "Q3 leads re-check", "emails": ["a@acme.com"], "fresh": true}'
Send "deepVerify": true to also resolve the job's Google Workspace catch_all rows the way a single deep check does, in one pass after every other row is done. The extra credits are held when that pass starts and settled against the rows it resolved. Each address the oracle resolves costs 3 credits extra; one it cannot resolve costs nothing extra.
POST/v1/jobs/upload
For large lists, upload the file itself (multipart) instead of a JSON body, the server streams it, so a file can carry up to 1,000,000 emails per job, far more than fits in a 16 MB JSON body. Send the file plus optional name, emailColumn (header name; omit for a plain .txt list) and metaColumns (JSON array of headers to keep) as form fields. A gzipped file such as leads.csv.gz uploads faster and is decompressed on the server; the 1 GB file limit applies to its decompressed size. The server counts the rows, holds the credits, and queues verification; the response includes the record total.
curl -X POST https://api.inboxvalid.com/v1/jobs/upload \
-H "Authorization: Bearer iv_live_..." \
-F "name=Q3 leads" \
-F "emailColumn=email" \
-F 'metaColumns=["first_name","company"]' \
-F "file=@leads.csv"
{ "id": "job_cuid", "status": "queued", "total": 1500 }
To check the count and credit cost before committing, add -F "preflight=true". The job is created as pending and the response adds cost, balance and sufficient; nothing is charged until you POST /v1/jobs/{id}/confirm, which also accepts { "fresh": true } to re-probe every address rather than reuse cached verdicts, and { "deepVerify": true } to deep check its Google Workspace catch-alls.
{ "id": "job_cuid", "status": "pending", "total": 1500,
"cost": 1500, "balance": 5000, "sufficient": true }
POST/v1/jobs/{id}/deep
Deep checks the Google Workspace catch_all rows of a completed job that are still unresolved, for example in a job that ran without deepVerify. Send { "emails": [...] } to check only some of them; omit it for all. The job goes back to running, and the response carries requeued (the eligible rows) and estimatedCost (the most the pass can cost). A job with no eligible rows returns 409. Each address the oracle resolves costs 3 credits extra; one it cannot resolve costs nothing extra.
GET/v1/jobs/{id}
Returns the job with live counters. blocked counts the addresses we did not check because their mail sits behind a security gateway; they are not charged, and the job.completed webhook carries the same count:
{
"id": "job_cuid",
"name": "Q3 leads",
"status": "running",
"total": 2000,
"processed": 1250,
"valid": 980, "invalid": 170, "risky": 40,
"catchAll": 35, "unknown": 8, "blocked": 12, "disposable": 5,
"createdAt": "2026-06-12T09:00:00.000Z",
"finishedAt": null
}
GET /v1/jobs lists your jobs, newest first. Supports take (max 100, default 100) and skip for pagination, and returns a total count alongside the jobs array.
GET/v1/jobs/{id}/results
Pages through per-email results. Supports ?status=valid|invalid|invalid_syntax|risky|catch_all|unknown|disposable|blocked, q (case-insensitive email search), take (max 1000, default 200) and skip for pagination. The response includes a total count of matching rows. Each row has mailGateway, the security gateway's name on a blocked row and null otherwise; it is on job rows, on single check history items (GET /v1/verify/history) and on finder history rows (GET /v1/finder/history).
curl "https://api.inboxvalid.com/v1/jobs/job_cuid/results?status=valid&take=500" \
-H "Authorization: Bearer iv_live_..."
GET/v1/jobs/{id}/export
Downloads the full results as a CSV file (columns: email, domain, status, sub_status, risk_score, provider, is_catch_all, is_role, is_disposable, free, verified_at, trust_score, mail_gateway, then any kept columns). Mapped upload columns move one place to the right. trust_score (higher is safer) is the current field; risk_score is its deprecated inverted alias. Add ?status=valid to export only one status, for example your cleaned send-ready list.
curl -OJ "https://api.inboxvalid.com/v1/jobs/job_cuid/export?status=valid" \
-H "Authorization: Bearer iv_live_..."
GET/v1/jobs/{id}/events
A Server-Sent Events stream of live progress. Each message is a JSON snapshot of the counters above; the stream closes when the job completes or fails.
data: {"processed": 1250, "total": 2000, "status": "running", "valid": 980, ...}
data: {"processed": 2000, "total": 2000, "status": "completed", ...}
Account & usage
GET/me
Returns your organization, plan and current credit balance.
GET/v1/stats
{
"creditBalance": 950,
"creditsUsed": 50,
"emailsVerified": 50,
"jobsRun": 1,
"deliverabilityAudits": 0
}
GET/v1/ledger
The append-only credit ledger: grants, charges and refunds with the balance after each movement.
GET/v1/activity
The audit trail of everything done on the account, via dashboard or API key. Filter with ?action=verify and page with take (max 500).
Ready to integrate?Create an account, grab an API key and verify your first 10,000 emails free.
Get your API key