Email Verification API: JavaScript Example
Send a POST request to /v1/email/check with an Authorization: Bearer header carrying your API key and a JSON body containing the email address. The response includes a decision (allow/review/block), a risk score with confidence, and the specific signals behind it. Below is a complete, working example in JavaScript, plus the equivalent cURL and error-handling pattern.
A basic request
const response = await fetch("https://api.emailriskradar.com/v1/email/check", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.EMAIL_RISK_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
email: "user@example.com",
mode: "standard", // "fast" | "standard" | "full"
context: "signup", // "generic" | "signup" | "b2b_lead" | "crm" | "marketplace" | "payment"
}),
});
if (!response.ok) {
const { error } = await response.json();
throw new Error(`Email check failed: ${error.code} - ${error.message}`);
}
const result = await response.json();
console.log(result.decision, result.risk.score, result.risk.level);Both mode and context are optional and default to "standard" and "generic" respectively — set context explicitly whenever the form has a specific purpose (see How to Stop Fake SaaS Signups for why the signup context specifically matters).
The same request in cURL
curl https://api.emailriskradar.com/v1/email/check \
-H "Authorization: Bearer $EMAIL_RISK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com", "mode": "standard", "context": "signup"}'Reading the response
{
"email": "user@example.com",
"decision": "allow",
"risk": { "score": 5, "level": "low", "confidence": 0.8 },
"verification": { "status": "likely_valid", "domain": "verified", "mailbox": "unconfirmed", "confidence": 0.75 },
"deliverability": { "status": "likely_deliverable", "confidence": 0.75 },
"email_quality": { "score": 95, "classification": "normal" },
"signals": [],
"checks": {
"syntax_valid": true,
"domain_exists": true,
"mx_found": true,
"smtp_check": "unknown",
"disposable": false,
"free_provider": false,
"role_based": false,
"catch_all": false,
"typo_detected": false
},
"meta": { "processing_ms": 84, "cached": false, "mode": "standard", "context": "signup" }
}In practice, most application code only needs to branch on result.decision, and read result.risk and result.signals when you want to log or display why a decision was made:
switch (result.decision) {
case "allow":
await createAccount(email);
break;
case "review":
await createAccount(email, { requiresConfirmation: true });
await sendConfirmationEmail(email);
break;
case "block":
throw new Error("This email address can't be used to sign up.");
}Handling errors
Errors follow a consistent shape — { error: { code, message, request_id } } — with an HTTP status matching the error code, so you can branch on either:
| error.code | HTTP status | Typical cause |
|---|---|---|
| INVALID_REQUEST | 400 | Missing or malformed email field |
| AUTHENTICATION_REQUIRED / INVALID_API_KEY | 401 | Missing, malformed, or revoked API key |
| RATE_LIMIT_EXCEEDED | 429 | Too many requests per second for your plan — back off and retry |
| QUOTA_EXCEEDED | 402 | Monthly check quota exhausted |
| SERVICE_UNAVAILABLE | 503 | A dependency (e.g. Stripe, for billing routes) isn't configured — not expected on /email/check |
try {
const result = await checkEmail(email);
return result;
} catch (err) {
if (err.code === "RATE_LIMIT_EXCEEDED") {
await sleep(1000);
return checkEmail(email); // simple retry - use exponential backoff in production
}
if (err.code === "QUOTA_EXCEEDED") {
// fail open or closed depending on how critical the check is to your flow
console.error("Email risk quota exhausted", err.message);
return null;
}
throw err;
}API keys and environments
Keys are issued per environment (live or test) from the dashboard, and never appear anywhere in a response — store the key in an environment variable, never hard-code it, and never expose it to client-side code. Every request must be made server-side, since the key is a bearer credential with no additional per-request scoping.
Checking many addresses at once
For batches rather than one-at-a-time checks, POST /v1/email/bulk accepts an array of addresses and processes them asynchronously — poll GET /v1/email/bulk/:jobId for status, or register a webhook endpoint to be notified when the batch completes, instead of holding a connection open for a large batch.
FAQ
The API itself is just JSON over HTTP, so any TypeScript project works with the same fetch call — just type the response shape yourself, or generate types from the documented response schema.
Fast only consults cached DNS/domain data for lowest latency. Standard performs live DNS lookups. Full adds a live SMTP handshake to the mail server, which is slower but gives the strongest deliverability signal. See How SMTP Email Verification Works for what full mode adds specifically.
No — API keys are bearer credentials meant for server-side use only. Calling from the browser would expose your key to anyone viewing the page source or network requests.
Yes, per-second rate limits and monthly quotas are set by your plan. A 429 response with RATE_LIMIT_EXCEEDED means back off and retry; a 402 with QUOTA_EXCEEDED means the monthly allowance is used up.