Product GuideAPI Reference

API Reference

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" }:

StatusMeaning
400Bad request, for example a malformed email or domain
401Missing or invalid API key
402Insufficient credits (code: "insufficient_credits")
403Organization suspended
404Resource not found
429Rate limit exceeded, or your plan's daily credit cap is spent (code: "daily_cap_reached", with cap)
502Verification 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:

StatusMeaning
validThe mailbox exists and accepts mail
invalidThe mailbox was rejected by the server
invalid_syntaxThe 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_allThe domain accepts mail for any address
riskyDeliverable but flagged, for example a role account
disposableA temporary or throwaway domain
unknownInconclusive, for example greylisting; does not count against your list quality
blockedNot 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