Letterjudge API
Letterjudge validates email addresses through a multi-step pipeline: format check, MX DNS lookup, live SMTP probe, catch-all detection, disposable domain blocking, and role address flagging. Every check produces a weighted confidence score from 0.0 to 1.0.
All requests are REST + JSON over HTTPS. No SDK required. A single curl command gets you started.
Base URL
https://api.letterjudge.com
Authentication
All requests to /validate and /usage require an API key passed in
the X-Api-Key request header. You receive your key when you create an account.
X-Api-Key: lj_live_[.....]
Key format
API keys are formatted as: the prefix lj_live_ followed by encoded characters.
| Part | Example | Notes |
|---|---|---|
| Prefix | lj_live_ | Always present. Identifies the key as a live Letterjudge key. |
| Secret | xKj4mN8pQr3v… | encoded characters (A–Z, a–z, 0–9, -, _). Never stored in plaintext. |
Validate an email address
Runs the full validation pipeline and returns a structured result with a confidence score. Each step only executes if the previous one passed. Bad format exits immediately; SMTP only fires when the domain has MX records.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| string | required | Email address to validate. Maximum 254 characters (RFC 5321). | |
| smtp | boolean | optional | Run SMTP probe. Default true. Set false for sub-100ms format + MX-only checks. Starter plan and above only — Free accounts always receive smtp_reachable: null and catch_all: null regardless of this parameter. |
| threshold | float | optional | Score cutoff between 0.0 and 1.0. When set, the response includes threshold_pass: true if the score meets or exceeds this value, false if not. |
Request headers
| Header | Type | Required | Description |
|---|---|---|---|
| X-Api-Key | string | required | Your API key (lj_live_…). |
curl "https://api.letterjudge.com/[email protected]" \ -H "X-Api-Key: lj_live_xKj4mN8pQr3vL7wEsTuBcDa"
curl "https://api.letterjudge.com/[email protected]&smtp=false" \ -H "X-Api-Key: lj_live_xKj4mN8pQr3vL7wEsTuBcDa"
curl "https://api.letterjudge.com/[email protected]&threshold=0.5" \ -H "X-Api-Key: lj_live_xKj4mN8pQr3vL7wEsTuBcDa"
smtp=false to
get a result in under 100ms. Upgrade to full SMTP verification for bulk list cleaning or
transactional sending flows.
Response schema
All responses are JSON. The validation response includes every check result and the computed score.
| Field | Type | Description |
|---|---|---|
| string | Normalised address as submitted (lowercased, trimmed). | |
| format_valid | bool | Passes RFC 5321 regex check on email structure. |
| mx_found | bool | Domain has at least one valid MX record. |
| mx_records | string[] | MX hostnames sorted by priority. Empty if none found. |
| smtp_reachable | bool | null | true = 250 accepted · false = rejected · null = inconclusive (greylisted, timeout, or smtp=false). |
| smtp_detail | string | null | Human-readable SMTP outcome or error message. |
| catch_all | bool | null | Domain accepts mail for any address. Score penalised −0.10. |
| disposable | bool | Domain found in the 17k+ throwaway domain blocklist. |
| free_provider | bool | Gmail, Outlook, Yahoo, iCloud, Proton, and similar. |
| role_address | bool | Generic prefix: admin, info, noreply, support… Score penalised −0.10. |
| sub_addressed | bool | Local part contains a + tag (e.g. [email protected]). Informational only — no score impact. |
| suspicious_tld | bool | TLD is on the high-abuse list (e.g. .xyz, .top, .tk). Score penalised −0.10. |
| mx_provider | string | null | Identified mail provider from MX records: google, microsoft, proofpoint, mimecast, zoho, yahoo, other, or null if no MX. |
| has_spf | bool | null | Domain publishes a valid SPF record. Score boosted +0.05. null if lookup was skipped. |
| has_dmarc | bool | null | Domain publishes a DMARC policy. Score boosted +0.05. null if lookup was skipped. |
| score | float | Weighted confidence 0.0–1.0. See score reference below. |
| threshold_pass | bool | null | true if score >= threshold, false if not. null when no threshold was passed. |
| did_you_mean | string | null | Corrected address for common typos (gmial.com → gmail.com). |
| checked_at | float | Unix timestamp of when the check was performed. |
Score weights
| Condition | Weight |
|---|---|
| Valid format | +0.20 |
| MX record found | +0.30 |
| SMTP accepted (250) | +0.50 |
| SMTP inconclusive (MX found, no answer) | +0.25 |
| SMTP rejected (5xx) | −0.10 |
| Catch-all domain | −0.10 |
| Disposable domain | −0.20 |
| Role address | −0.10 |
| Suspicious TLD | −0.10 |
| SPF record present | +0.05 |
| DMARC policy present | +0.05 |
Score is clamped to [0.0, 1.0]. Typical thresholds: ≥0.4 lenient (MX only), ≥0.6 balanced (MX + inconclusive SMTP), ≥0.7 strict (confirmed SMTP).
Get usage and plan info
Returns your current plan, usage counters, and billing period. Available from your dashboard.
Response fields
| Field | Type | Description |
|---|---|---|
| string | Account email address. | |
| plan | string | Current plan: free · starter · pro · business. |
| status | string | Subscription status: active · past_due · canceled. |
| requests_this_period | int | Validations used in the current billing period. |
| total_requests | int | All-time total validations made on this account. |
| limit | int | null | Monthly quota. null means unlimited (Business plan). |
| resets_at | float | null | Unix timestamp when the usage counter resets. null for free tier. |
| days_remaining | int | null | Days until the current billing period ends. null for free tier. |
Get 30-day usage history
Returns a daily breakdown of validation requests for the last 30 calendar days. Each entry covers one UTC day. Days with no activity have a count of 0. Available from your dashboard.
Response
An array of exactly 30 objects, oldest first (day 0 = 30 days ago, day 29 = today).
| Field | Type | Description |
|---|---|---|
| date | string | UTC date in YYYY-MM-DD format. |
| count | int | Number of successful /validate calls on that day. 0 if no calls were made. |
[
{ "date": "2025-05-24", "count": 35 },
{ "date": "2025-05-25", "count": 42 },
{ "date": "2025-05-26", "count": 0 },
...
{ "date": "2025-06-22", "count": 91 }
]
Rendered example
The dashboard shows this data as a bar chart in the Usage History card. Today's bar is brighter; past days are dimmer. Days with no activity are skipped.
API keys
Free and Starter accounts have one active API key at a time (creating a new key revokes the previous one). Pro accounts can hold up to 5 active keys simultaneously; Business accounts have no limit. Keys are hashed before storage. The full key is only shown once (at signup or after a rotation). The dashboard always displays the first 12 characters followed by masked dots.
Rotating your key from the dashboard
The easiest way to rotate your key is from the dashboard. Open the API Key panel, and below the key text box, click Rotate key. Confirm the prompt and your new key appears. Copy it before leaving the page. The old key is revoked the moment you confirm.
List all keys on the account (active and revoked). Requires session cookie.
[
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"key_prefix": "lj_live_xKj4",
"created_at": "2025-01-15T09:00:00+00:00",
"last_used_at": "2025-06-01T14:32:10+00:00",
"is_active": true
}
]
Issue a new API key. For Free and Starter plans, all existing active keys are revoked immediately (key rotation). For Pro and Business plans, the new key is added alongside existing ones (up to 5 for Pro, unlimited for Business). The full key is returned once — store it before closing the page. This is the endpoint the dashboard Rotate key button calls under the hood.
curl -X POST "https://api.letterjudge.com/keys" \ --cookie "lj_jwt=<session-cookie>"
{
"key": "lj_live_xKj4mN8pQr3vL7wEsTuBcDaFgHiJoYn2zb",
"prefix": "lj_live_xKj4"
}
401 as soon as the new key is issued. Update your
environment variables before rotating in production.
Revoke a specific key by ID without issuing a replacement. Use the ID from GET /keys.
{ "revoked": true }
Password reset
To reset your password, go to the sign-in page and click Forgot password. Enter your account email and you will receive a reset link within a few minutes. The link expires after one hour and can only be used once.
After clicking the link, enter your new password (minimum 8 characters). Your existing API key is not affected by a password reset.
Subscription
Plans and billing are managed from your dashboard. The dashboard shows your current plan, quota used this period, days until reset, and next billing date.
Upgrading
Click Upgrade on the dashboard billing panel to move to a higher plan. The new quota applies immediately once payment is confirmed.
Managing your subscription
Click Manage billing on the dashboard to open the Stripe customer portal. From there you can:
| Action | Notes |
|---|---|
| Update payment method | Replace the card on file at any time. |
| View invoices | Download PDF receipts for any past payment. |
| Change plan | Upgrade or downgrade. Changes take effect at the next billing cycle. |
| Cancel | Cancels at the end of the current period. You keep full API access until then. No refunds are issued for unused time. |
Subscription states
| Status | What it means | API access |
|---|---|---|
| active | Billing is current. | Full quota |
| past_due | A payment failed. Stripe will retry automatically. Your quota is not affected during the retry window. | Full quota |
| canceled | Subscription ended. Account reverted to the free plan. | Free (100/mo) |
Invoices
Billing is handled entirely by Stripe. When a payment goes through, Stripe sends a receipt to the email address on your account automatically — no action needed on your part.
Finding your invoices
All past invoices, including PDF downloads, are available in the Billing Portal. Click Manage billing on your dashboard to open it. From there you can:
- View and download PDF receipts for every charge
- Update your payment method
- Cancel or change your plan
Didn't receive the receipt email?
Check your spam folder first. If it's not there, open the Billing Portal — the invoice will be listed there regardless of whether the email arrived. You can re-download the PDF at any time.
Error codes
All error responses return JSON with a detail field.
plan, limit, used, and upgrade_url.
{
"detail": {
"error": "quota_exceeded",
"plan": "free",
"limit": 100,
"used": 100,
"upgrade_url": "/billing/checkout"
}
}
Rate limits & quotas
There are two distinct limits: a per-IP rate limit (requests per minute) and a per-account monthly quota.
Monthly quotas
| Plan | Validations / month | Price |
|---|---|---|
| free | 100 | $0 |
| starter | 5,000 | $9 / month |
| pro | 50,000 | $29 / month |
| business | ∞ unlimited | $99 / month |
Usage tracking
Every successful GET /validate call counts against your monthly quota.
The quota resets at the start of each billing period. Check GET /usage
for your current count, limit, and days until reset.
Quota enforcement
When your monthly limit is reached the API returns 429 with a structured body.
The Business plan has no limit and the quota check is skipped entirely.
{
"detail": {
"error": "quota_exceeded",
"plan": "free",
"limit": 100,
"used": 100,
"upgrade_url": "/billing/checkout"
}
}
{
"email": "[email protected]",
"plan": "starter",
"status": "active",
"requests_this_period": 3400,
"total_requests": 12847,
"limit": 5000,
"resets_at": 1751328000,
"days_remaining": 12
}
Code examples
curl
# Full check with SMTP curl "https://api.letterjudge.com/[email protected]" \ -H "X-Api-Key: lj_live_xKj4mN8pQr3vL7wEsTuBcDa" # MX-only (fast, no SMTP) curl "https://api.letterjudge.com/[email protected]&smtp=false" \ -H "X-Api-Key: lj_live_xKj4mN8pQr3vL7wEsTuBcDa"
Python
import requests r = requests.get( "https://api.letterjudge.com/validate", headers={"X-Api-Key": "lj_live_xKj4mN8pQr3vL7wEsTuBcDa"}, params={"email": "[email protected]"}, timeout=20, ) r.raise_for_status() data = r.json() if data["score"] >= 0.6 and not data["disposable"]: print("address looks good")
Node.js
const res = await fetch( `https://api.letterjudge.com/[email protected]`, { headers: { "X-Api-Key": "lj_live_xKj4mN8pQr3vL7wEsTuBcDa" } } ); const data = await res.json(); const isValid = data.score >= 0.6 && !data.disposable && data.mx_found;
PHP (Laravel)
$response = Http::withHeaders([ 'X-Api-Key' => 'lj_live_xKj4mN8pQr3vL7wEsTuBcDa', ])->get('https://api.letterjudge.com/validate', [ 'email' => '[email protected]', ]); $data = $response->json(); return $data['score'] >= 0.4 && !$data['disposable'] && $data['mx_found'];