One request.
Full lead profile.
Validate, enrich, and score any email address with a single API call. REST, JSON, sub-2-second responses, transparent confidence scores.
§Introduction
Leadvar turns one email address into a complete lead profile.
Send POST /webhook/enrich with an email, get back validation results,
domain intelligence, company firmographics, tech stack, social profiles, and a calibrated lead score —
all in a single response, typically under two seconds.
What you get back
- Email validation — 12 checks: syntax, MX, SMTP, disposable, role-based, catch-all, DMARC, SPF, DKIM, blacklists, typo detection, domain age
- Domain intelligence — age, SSL validity, blacklist status across multiple DNSBLs, registrar
- Company data — name, description, logo, tech stack (50+ technologies detected), social profiles, schema metadata
- Lead score — 0–100, transparent breakdown, calibrated against real-world deliverability
Built differently
Most enrichment vendors resell ZoomInfo or Clearbit data marked up 5×. Leadvar runs its own validation engine and enrichment pipeline — which means fresher data, transparent sources, and lower prices.
§Quickstart
Make your first call in 60 seconds. Replace YOUR_KEY with your API key.
1. Make a request
curl -X POST https://api.leadvar.com/webhook/enrich \ -H "x-api-key: YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{"email":"ceo@stripe.com"}'
2. Inspect the response
{
"email": {
"address": "ceo@stripe.com",
"valid": true,
"confidence": 90,
"risk": "risky",
"is_catch_all": true
},
"company": {
"name": "Stripe",
"description": "Financial services platform...",
"tech_stack": ["Next.js", "React"]
},
"lead_score": 89
}
3. Done
Now plug it into your CRM, signup form, or outreach tool. For batch processing of up to 100 emails per request, see POST /webhook/enrich/batch.
§Authentication
Every request requires your API key in the x-api-key header.
Don't have a key yet? Request one here
— you'll receive 100 free trial credits, no credit card required.
Header format
x-api-key: your_api_key_here Content-Type: application/json
Missing or invalid key
Requests without a valid key return 401:
{
"error": "Invalid or missing API key",
"code": 401
}
§Enrich a single email
Validate and enrich one email. Best for real-time lookups during signup, live form enrichment, or sales-call lookups.
Request body
| Field | Type | Description |
|---|---|---|
| email required | string | Email address to validate and enrich. Case-insensitive. |
Example request
curl -X POST https://api.leadvar.com/webhook/enrich \ -H "x-api-key: $LEADVAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"email":"ceo@stripe.com"}'
const response = await fetch( 'https://api.leadvar.com/webhook/enrich', { method: 'POST', headers: { 'x-api-key': process.env.LEADVAR_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ email: 'ceo@stripe.com' }) } ); const data = await response.json(); console.log(data.lead_score); // 89
import os import requests response = requests.post( 'https://api.leadvar.com/webhook/enrich', headers={'x-api-key': os.environ['LEADVAR_API_KEY']}, json={'email': 'ceo@stripe.com'} ) data = response.json() print(data['lead_score']) # 89
package main import ( "bytes" "encoding/json" "net/http" "os" ) func main() { body, _ := json.Marshal(map[string]string{ "email": "ceo@stripe.com", }) req, _ := http.NewRequest("POST", "https://api.leadvar.com/webhook/enrich", bytes.NewBuffer(body)) req.Header.Set("x-api-key", os.Getenv("LEADVAR_API_KEY")) req.Header.Set("Content-Type", "application/json") resp, _ := http.DefaultClient.Do(req) defer resp.Body.Close() }
§Batch enrichment
Process up to 100 emails in a single request. Best for CRM cleanup, list verification, and bulk imports.
Request body
| Field | Type | Description |
|---|---|---|
| emails required | string[] | Array of 1–100 email addresses. Duplicates are automatically removed (free of charge). |
Example request
curl -X POST https://api.leadvar.com/webhook/enrich/batch \ -H "x-api-key: $LEADVAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "emails": [ "ceo@stripe.com", "team@openai.com", "test@gmail.com" ] }'
const emails = ['ceo@stripe.com', 'team@openai.com']; const response = await fetch( 'https://api.leadvar.com/webhook/enrich/batch', { method: 'POST', headers: { 'x-api-key': process.env.LEADVAR_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ emails }) } ); const data = await response.json(); // Filter for high-quality leads const qualified = data.results.filter(r => r.lead_score >= 70); console.log(`${qualified.length} qualified leads`);
import os import requests emails = ['ceo@stripe.com', 'team@openai.com'] response = requests.post( 'https://api.leadvar.com/webhook/enrich/batch', headers={'x-api-key': os.environ['LEADVAR_API_KEY']}, json={'emails': emails} ) data = response.json() qualified = [r for r in data['results'] if r['lead_score'] >= 70] print(f"{len(qualified)} qualified leads")
Example response
{
"total": 3,
"duplicates_removed": 0,
"successful": 3,
"errors": 0,
"summary": {
"valid_emails": 2,
"business_emails": 2,
"average_lead_score": 71
},
"results": [
{ /* full enrichment for ceo@stripe.com */ },
{ /* full enrichment for team@openai.com */ },
{ /* full enrichment for test@gmail.com */ }
],
"credits_used": 3
}
§Response format
Every successful enrichment returns six top-level groups plus metadata:
| Field | Type | Description |
|---|---|---|
| object | Validation results: valid, confidence, risk, classification flags |
|
| email_security | object | DMARC, SPF, DKIM status; MX record; provider trust |
| domain | object | Domain age, SSL validity, blacklist status, registrar |
| company | object | null | Company name, description, tech stack, social profiles. null for free email providers. |
| lead_score | integer | Calibrated 0–100 score combining all signals |
| deliverability_insights | string[] | Human-readable recommendations |
| validation_checks | object | 12 individual check results with pass/fail and details |
| api_version | string | Current: v3.1 |
Full example
{
"email": {
"address": "ceo@stripe.com",
"valid": true,
"confidence": 90,
"bounce_risk_score": 25,
"risk": "risky",
"reason": "Catch-all domain...",
"is_disposable": false,
"is_role_based": false,
"is_free_email": false,
"is_catch_all": true,
"did_you_mean": null
},
"email_security": {
"has_dmarc": true,
"has_spf": true,
"has_dkim": true,
"mx_record": "aspmx.l.google.com",
"mx_provider_trusted": true
},
"domain": {
"name": "stripe.com",
"age_days": 11180,
"is_new": false,
"is_blacklisted": false,
"ssl_valid": true,
"registrar": "Safenames Ltd"
},
"company": {
"name": "Stripe",
"description": "Financial services platform...",
"tech_stack": ["Next.js", "React"],
"social_profiles": { "github": "github.com/stripe" },
"confidence": 75
},
"lead_score": 89,
"deliverability_insights": [
"Uses reputable email infrastructure",
"Catch-all domain — mailbox cannot be verified"
]
}
§Error codes
Standard HTTP status codes with consistent JSON error bodies.
| Status | Meaning | When it happens |
|---|---|---|
| 200 | OK | Request succeeded; response body contains enrichment data |
| 400 | Bad Request | Missing or malformed email field; batch >100 or empty |
| 401 | Unauthorized | API key missing, invalid, or deactivated |
| 402 | Payment Required | Insufficient credits to process the request |
| 429 | Too Many Requests | Rate limit exceeded — back off and retry |
| 503 | Service Unavailable | Validation or enrichment service down — retry with exponential backoff |
Error response shape
{
"error": "Insufficient credits",
"code": 402
}
§Rate limits
Limits apply per API key, across all endpoints. Exceeding limits returns 429.
| Endpoint | Limit | Window |
|---|---|---|
| /webhook/enrich | 5 requests | per second |
| /webhook/enrich/batch | 2 requests | per second |
Best practice
On a 429, implement exponential backoff: retry after 1s, then 2s, then 4s, capped at 30s. Don't retry immediately — you'll just hit the limit again. See the production client below for a working implementation.
§Production client
Reference implementation with retries, exponential backoff, and proper error handling.
Use this in production rather than naive fetch() calls.
import os import time import requests from typing import Optional class LeadvarClient: """Production-ready Leadvar API client with retry logic.""" BASE_URL = 'https://api.leadvar.com' def __init__(self, api_key: str, timeout: int = 30): self.api_key = api_key self.timeout = timeout self.session = requests.Session() self.session.headers.update({ 'x-api-key': api_key, 'Content-Type': 'application/json' }) def enrich(self, email: str, max_retries: int = 3) -> Optional[dict]: """Enrich a single email with exponential backoff.""" for attempt in range(max_retries): response = self.session.post( f'{self.BASE_URL}/webhook/enrich', json={'email': email}, timeout=self.timeout ) if response.status_code == 200: return response.json() if response.status_code == 429: # rate limited wait = 2 ** attempt time.sleep(wait) continue if response.status_code == 503: # service down time.sleep(5) continue # 400, 401, 402 — don't retry response.raise_for_status() return None def enrich_batch(self, emails: list, max_retries: int = 3) -> Optional[dict]: """Enrich up to 100 emails in one call.""" if len(emails) > 100: raise ValueError('Batch size cannot exceed 100') for attempt in range(max_retries): response = self.session.post( f'{self.BASE_URL}/webhook/enrich/batch', json={'emails': emails}, timeout=self.timeout ) if response.status_code == 200: return response.json() if response.status_code in (429, 503): time.sleep(2 ** attempt) continue response.raise_for_status() return None # Usage client = LeadvarClient(os.environ['LEADVAR_API_KEY']) # Single email data = client.enrich('ceo@stripe.com') if data and data['lead_score'] >= 70: print(f"High-value lead: {data['company']['name']}") # Batch batch = client.enrich_batch(['a@b.com', 'c@d.com']) print(f"{batch['summary']['valid_emails']} valid out of {batch['total']}")
§Lead scoring explained
The lead_score field is a calibrated 0–100 number combining
every signal we collect. Higher means more likely to be a deliverable, valuable lead.
Here's exactly how it's computed.
Base scores
| Email type | Base | Notes |
|---|---|---|
| Business email (verified) | 70 | Default starting point for a real business address |
| Free email (gmail, etc.) | 55 | Personal email, lower business intent |
| Invalid (other) | 20 | Failed validation but not disposable |
| Invalid (risk=invalid) | 5 | Definitely won't deliver |
| Disposable | 0 | Mailinator etc. — never deliverable |
Signal adjustments (business email only)
For a business email starting at base 70, we add or subtract:
- +5 — trusted MX provider (Google, Microsoft, ProofPoint)
- +3 — DMARC configured
- +3 — SPF configured
- +2 — DKIM configured
- +3 — company name detected
- +2 — company description available
- +2 — tech stack identified
- +3 — 2+ social profiles found
- +2 — SSL certificate valid
- +3 — domain older than 5 years
- −8 — catch-all domain
- −5 — role-based (sales@, info@, hello@)
- −15 — new domain (under 90 days)
- −20 — on email blacklist
Final score is clamped to the 0–100 range.
§FAQ
How long does a request take?
Single email: typically 200–1500ms. The variance comes from SMTP verification — if the recipient's mail server is slow to respond, we wait. We never make you wait more than ~8 seconds (we time out and return what we have).
Are responses cached?
Company enrichment data is cached for 30 days per domain — calling for two emails at the same domain in the same month uses cached company data on the second call (still 1 credit per email, since validation runs fresh). SMTP and DNS checks are always live.
What about emails I've already validated?
Email validation is always fresh; mailboxes get deleted, domains lapse, blacklists update. If you want to skip already-checked emails, deduplicate on your side before calling the API.
Do you support webhooks for batch jobs?
Not yet — for batches up to 100 emails, the synchronous response works well (responses arrive in 5–30 seconds). For larger batches, contact us and we'll discuss async webhook delivery.
How accurate is the SMTP verification?
Above 98% on non-catch-all domains. We perform a real SMTP handshake and ask the destination server if the mailbox exists.
Catch-all domains (which respond yes to every address) are flagged as is_catch_all: true —
for those, the score drops because mailbox existence cannot be verified.
Where is data stored?
Servers in Germany. We do not sell or share data. Email addresses you submit are stored only for caching (30-day TTL on company data) and rate-limit tracking. We never use submitted data for marketing, list-building, or sale to third parties. Full GDPR compliance.
Can I delete my data?
Yes. Email hello@leadvar.com with your API key and we'll purge all logs and cached data within 24 hours.
What happens when I run out of credits?
Requests return 402. You can top up at any time — new credits are usable within seconds.