Documentation

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

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.

header
X-Api-Key: lj_live_[.....]

Key format

API keys are formatted as: the prefix lj_live_ followed by encoded characters.

PartExampleNotes
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.
Note on key visibility: The full key is only shown once, at signup and after a manual rotation. The dashboard always shows the first 12 characters followed by masked dots. Store it in an environment variable or secrets manager immediately.
GET /validate

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

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

HeaderTypeRequiredDescription
X-Api-Key string required Your API key (lj_live_…).
curl: full check
curl "https://api.letterjudge.com/[email protected]" \
  -H "X-Api-Key: lj_live_xKj4mN8pQr3vL7wEsTuBcDa"
curl: MX-only (fast)
curl "https://api.letterjudge.com/[email protected]&smtp=false" \
  -H "X-Api-Key: lj_live_xKj4mN8pQr3vL7wEsTuBcDa"
curl: with threshold
curl "https://api.letterjudge.com/[email protected]&threshold=0.5" \
  -H "X-Api-Key: lj_live_xKj4mN8pQr3vL7wEsTuBcDa"
Tip: For high-volume signup forms where latency matters, use 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.

FieldTypeDescription
emailstringNormalised address as submitted (lowercased, trimmed).
format_validboolPasses RFC 5321 regex check on email structure.
mx_foundboolDomain has at least one valid MX record.
mx_recordsstring[]MX hostnames sorted by priority. Empty if none found.
smtp_reachablebool | nulltrue = 250 accepted · false = rejected · null = inconclusive (greylisted, timeout, or smtp=false).
smtp_detailstring | nullHuman-readable SMTP outcome or error message.
catch_allbool | nullDomain accepts mail for any address. Score penalised −0.10.
disposableboolDomain found in the 17k+ throwaway domain blocklist.
free_providerboolGmail, Outlook, Yahoo, iCloud, Proton, and similar.
role_addressboolGeneric prefix: admin, info, noreply, support… Score penalised −0.10.
sub_addressedboolLocal part contains a + tag (e.g. [email protected]). Informational only — no score impact.
suspicious_tldboolTLD is on the high-abuse list (e.g. .xyz, .top, .tk). Score penalised −0.10.
mx_providerstring | nullIdentified mail provider from MX records: google, microsoft, proofpoint, mimecast, zoho, yahoo, other, or null if no MX.
has_spfbool | nullDomain publishes a valid SPF record. Score boosted +0.05. null if lookup was skipped.
has_dmarcbool | nullDomain publishes a DMARC policy. Score boosted +0.05. null if lookup was skipped.
scorefloatWeighted confidence 0.0–1.0. See score reference below.
threshold_passbool | nulltrue if score >= threshold, false if not. null when no threshold was passed.
did_you_meanstring | nullCorrected address for common typos (gmial.com → gmail.com).
checked_atfloatUnix timestamp of when the check was performed.

Score weights

ConditionWeight
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

Get usage and plan info

Returns your current plan, usage counters, and billing period. Available from your dashboard.

Response fields

FieldTypeDescription
emailstringAccount email address.
planstringCurrent plan: free · starter · pro · business.
statusstringSubscription status: active · past_due · canceled.
requests_this_periodintValidations used in the current billing period.
total_requestsintAll-time total validations made on this account.
limitint | nullMonthly quota. null means unlimited (Business plan).
resets_atfloat | nullUnix timestamp when the usage counter resets. null for free tier.
days_remainingint | nullDays until the current billing period ends. null for free tier.
GET /usage/history

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

FieldTypeDescription
datestringUTC date in YYYY-MM-DD format.
countintNumber of successful /validate calls on that day. 0 if no calls were made.
sample response (truncated)
[
  { "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.

Dashboard: Usage History card (sample data)
Today's bar is highlighted · x-axis label every 7 days
Account

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.

One-time visibility: the full key is only shown right after rotation. Once you navigate away, only the first 12 characters are visible. Store it in an environment variable or secrets manager before closing the page.
GET /keys

List all keys on the account (active and revoked). Requires session cookie.

response
[
  {
    "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
  }
]
POST /keys

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
curl -X POST "https://api.letterjudge.com/keys" \
  --cookie "lj_jwt=<session-cookie>"
response
{
  "key":    "lj_live_xKj4mN8pQr3vL7wEsTuBcDaFgHiJoYn2zb",
  "prefix": "lj_live_xKj4"
}
For Free / Starter plans: rotation is immediate — any in-flight requests using the old key will start receiving 401 as soon as the new key is issued. Update your environment variables before rotating in production.
DELETE /keys/{key_id}

Revoke a specific key by ID without issuing a replacement. Use the ID from GET /keys.

response
{ "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:

ActionNotes
Update payment methodReplace the card on file at any time.
View invoicesDownload PDF receipts for any past payment.
Change planUpgrade or downgrade. Changes take effect at the next billing cycle.
CancelCancels at the end of the current period. You keep full API access until then. No refunds are issued for unused time.

Subscription states

StatusWhat it meansAPI 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.

Free plan: No charges are ever made on the free plan, so there are no invoices to issue. Invoices only appear when you upgrade to a paid plan and a billing cycle completes.

Error codes

All error responses return JSON with a detail field.

401 Invalid or missing API key, or session expired.
422 Validation error: email missing or exceeds 254 characters.
429 Monthly quota exceeded. Response body includes plan, limit, used, and upgrade_url.
403 Account disabled or no active subscription.
429 body
{
  "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

PlanValidations / monthPrice
free100$0
starter5,000$9 / month
pro50,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.

429 quota exceeded
{
  "detail": {
    "error":       "quota_exceeded",
    "plan":        "free",
    "limit":       100,
    "used":        100,
    "upgrade_url": "/billing/checkout"
  }
}
sample GET /usage response
{
  "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

bash
# 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

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

javascript
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)

php
$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'];