Email Domain Check
Checks the domain behind an email address: MX records, SPF and DMARC policies, and whether the address uses a disposable domain, a free mailbox provider or a role mailbox such as info@ or support@. It never contacts the mail server, so it cannot confirm that a mailbox exists.
POST https://YOUR_API_HOST/v1/email-domain-check
Credits and caching
- Success billing. 1 credit per address checked. You are charged only when the lookup finds something; a not-found result is free.
- Results are cached per account for 7 days. Repeating the same lookup in that window is free and returns
cache_hit: true. - Invalid input, rate limits and errors are never charged.
- Read the billing rules for the full picture.
Request
Send a JSON body with Authorization: Bearer <key>. The body can be up to 64 KB.
| Field | Type | Required | Notes |
|---|---|---|---|
email | string | no | Up to 1000 characters. |
domain | string | no | Up to 2048 characters. |
Example request
curl -X POST https://YOUR_API_HOST/v1/email-domain-check \
-H "Authorization: Bearer $LOOKVALID_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email":"info@example.com"}'
const response = await fetch("https://YOUR_API_HOST/v1/email-domain-check", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.LOOKVALID_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"email": "info@example.com"
}),
});
const result = await response.json();
if (!response.ok) throw new Error(`${result.error.code}: ${result.error.fix}`);
console.log(result.found, result.data, result.credits_charged);
import os
import requests
response = requests.post(
"https://YOUR_API_HOST/v1/email-domain-check",
headers={"Authorization": f"Bearer {os.environ['LOOKVALID_API_KEY']}"},
json={
"email": "info@example.com",
},
timeout=30,
)
result = response.json()
if not response.ok:
raise RuntimeError(f"{result['error']['code']}: {result['error']['fix']}")
print(result["found"], result["data"], result["credits_charged"])
Response
The status is 200 whether or not anything was found. Check found. Every response also carries the X-Request-Id, X-Credits-Charged, X-Credits-Remaining and X-RateLimit-* headers.
Envelope
| Field | Type | Notes |
|---|---|---|
request_id | string | - |
product | "email-domain-check" | - |
found | boolean | False means the lookup ran and found nothing; it is free. |
reason | string or null | Why nothing was found; null when found. |
sources | array of string | Where the data came from, including any attribution the licence requires. |
credits_charged | integer | 0 for a cache hit, a not-found result or an error. |
credits_remaining | integer | - |
cache_hit | boolean | True when the account already paid for this lookup within the cache window. |
cache | object | - |
The data object
Present when found is true, otherwise null.
| Field | Type | Notes |
|---|---|---|
email | string or null | Normalized address (lowercase, punycode domain); null for a domain-only lookup |
syntax_valid | boolean or null | True for an email that passed the syntax check; null for a domain-only lookup (bad syntax is rejected as invalid input) |
domain | string | - |
mx_records | array of object | Sorted by priority, then host name |
mx_records[].exchange | string | - |
mx_records[].priority | integer | - |
has_mx | boolean | - |
null_mx | boolean | True when the domain publishes a null MX ("0 ."), meaning it accepts no mail |
implicit_mx | boolean | True when there is no MX but an A or AAAA record, so senders fall back to the host itself |
spf | object | - |
spf.record | string or null | - |
spf.record_count | integer | - |
spf.policy | string or null | Qualifier on the "all" mechanism, after following redirect= |
spf.valid | boolean | True when exactly one SPF record exists |
spf.issues | array of string | - |
dmarc | object | - |
dmarc.record | string or null | - |
dmarc.record_count | integer | - |
dmarc.policy | string or null | - |
dmarc.subdomain_policy | string or null | - |
dmarc.pct | integer or null | - |
dmarc.rua | array of string | - |
dmarc.valid | boolean | - |
dmarc.issues | array of string | - |
disposable | boolean | - |
free_provider | boolean | - |
role_account | boolean or null | Null for a domain-only lookup |
smtp_checked | false | The mail server is never contacted, so mailbox existence is not verified |
deliverability_signal | string | One of good, risky, undeliverable. |
deliverability_reasons | array of object | - |
deliverability_reasons[].code | string | - |
deliverability_reasons[].message | string | - |
Captured example (2026-10-18)
A real run of this lookup on 2026-10-18, using the request above, which is a public input. The envelope fields are left out.
{
"product": "email-domain-check",
"found": true,
"data": {
"email": "info@example.com",
"syntax_valid": true,
"domain": "example.com",
"mx_records": [],
"has_mx": false,
"null_mx": true,
"implicit_mx": false,
"spf": {
"record": "v=spf1 -all",
"record_count": 1,
"policy": "-all",
"valid": true,
"issues": []
},
"dmarc": {
"record": "v=DMARC1;p=reject;sp=reject;adkim=s;aspf=s",
"record_count": 1,
"policy": "reject",
"subdomain_policy": "reject",
"pct": null,
"rua": [],
"valid": true,
"issues": []
},
"disposable": false,
"free_provider": false,
"role_account": true,
"smtp_checked": false,
"deliverability_signal": "undeliverable",
"deliverability_reasons": [
{
"code": "null_mx",
"message": "The domain publishes a null MX record, which means it does not accept email."
},
{
"code": "role_account",
"message": "The address is a shared role mailbox such as info@ or support@, not a person."
}
]
},
"sources": [
"Public DNS (MX, TXT)",
"Open disposable-email-domain and free-provider lists"
]
}
Errors
400invalid_input: The request input is invalid.401unauthorized: The API key is missing, malformed, revoked or unknown.402insufficient_credits: The account does not have enough credits for this lookup. Error code: insufficient_credits. During a trial whose allowance is used up the code is trial_allowance_exhausted: nothing is charged and the customer starts the plan from the billing page.405method_not_allowed: This endpoint does not accept that HTTP method.413payload_too_large: The request body is too large.429rate_limited: Too many requests for this API key.500internal_error: An unexpected error occurred.503provider_unavailable: The data source for this product is unavailable right now.