API documentation

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.

Production v3.1 Base URL · api.leadvar.com JSON over HTTPS

§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.

EU-first. Servers in Germany, GDPR-native architecture, no data resale, no shadow profiles. Built for European compliance from day one.

§Quickstart

Make your first call in 60 seconds. Replace YOUR_KEY with your API key.

1. Make a request

terminal
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

200 OK · 243ms ● 200
{
  "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

request headers
x-api-key: your_api_key_here
Content-Type: application/json
Keep your key private. Never commit it to public repositories, embed it in client-side JavaScript, or share it in screenshots. Treat it like a password — if leaked, regenerate it immediately.

Missing or invalid key

Requests without a valid key return 401:

401 Unauthorized
{
  "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.

POST https://api.leadvar.com/webhook/enrich 1 credit 5 req/sec

Request body

FieldTypeDescription
email required string Email address to validate and enrich. Case-insensitive.

Example request

terminal
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"}'
enrich.js
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
enrich.py
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
enrich.go
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.

POST https://api.leadvar.com/webhook/enrich/batch 1 credit per email 2 req/sec

Request body

FieldTypeDescription
emails required string[] Array of 1–100 email addresses. Duplicates are automatically removed (free of charge).

Example request

terminal
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"
    ]
  }'
batch.js
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`);
batch.py
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

200 OK · 3 emails · 2.1s ● 200
{
  "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
}
Free deduplication. Duplicate emails within a single batch are removed automatically and don't count against your credits.

§Response format

Every successful enrichment returns six top-level groups plus metadata:

FieldTypeDescription
email 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

complete response · ceo@stripe.com
{
  "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.

StatusMeaningWhen 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

402 Payment Required
{
  "error": "Insufficient credits",
  "code": 402
}
Failed requests are free. Any request returning a 4xx or 5xx code does not consume credits. You only pay for successful enrichments.

§Rate limits

Limits apply per API key, across all endpoints. Exceeding limits returns 429.

EndpointLimitWindow
/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.

Need higher limits? Enterprise plans include custom rate limits. Email hello@leadvar.com.

§Production client

Reference implementation with retries, exponential backoff, and proper error handling. Use this in production rather than naive fetch() calls.

leadvar_client.py
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 typeBaseNotes
Business email (verified)70Default starting point for a real business address
Free email (gmail, etc.)55Personal email, lower business intent
Invalid (other)20Failed validation but not disposable
Invalid (risk=invalid)5Definitely won't deliver
Disposable0Mailinator 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.

Honest about uncertainty. Catch-all domains (where the server accepts any address) score lower because we genuinely cannot verify if the specific mailbox exists. We tell you that, rather than pretending.

§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.