A REST API for validating, enriching and collecting email addresses. JSON in, JSON out, Bearer authentication. $0.00049 per address with no subscription and no per-seat licence — you are billed for calls that return data.
Get a free API keyhttps://charlymail.com/api
with Authorization: Bearer keys. Verification costs $0.00049 per address, billed from
a prepaid token balance, with 200 tokens free on signup.
Every endpoint takes a long-lived API key in the Authorization header. Keys are
created on the API keys page and shown once — store it immediately. Up to 10 active keys per
account; a compromised key is revoked on the same page.
# All requests carry the key in a header
curl -H "Authorization: Bearer ck_abc123..." \
https://charlymail.com/api/lists
Keys grant full account access, including spending your token balance. Do not commit them to repositories or expose them in client-side code.
Check one address synchronously. Returns validity, enrichment fields and a 0–100 score. Costs 1 token. This is the endpoint most integrations start with — sign-up form validation, CRM hooks, lead-capture gates.
Upload a CSV or TXT file for bulk processing. multipart/form-data, up to 50 MB,
20 uploads per hour. Parsing, MX checks, disposable and role filtering, and enrichment all run in
the background; poll the list endpoint for status.
List your databases with aggregated valid / risky / invalid counters, or fetch one with full metadata: name, status, tags, source and SMTP settings. Lists that are not yours return 404 — never 403, so the API does not confirm that an ID exists.
Read enriched rows with filtering and pagination, or export the whole list. Export applies your suppression list automatically and lets you pick columns and delimiter.
Start a collection job against public sources — GitHub, Reddit, npm, PyPI, Hacker News and others.
Accepts filters for country, gender, provider and volume. Returns a job ID; poll
GET /api/parser/:id for progress. See
how collection works.
Manage the suppression list — addresses that must never appear in an export. Applied automatically on every export, so an unsubscribe recorded once stays honoured across all lists.
300 requests per minute per IP globally, with tighter per-user limits on expensive endpoints (uploads are capped at 20 per hour). Every response carries the current state:
| Header | Meaning |
|---|---|
| X-RateLimit-Limit | Ceiling for the current window |
| X-RateLimit-Remaining | Requests left in this window |
| X-RateLimit-Reset | UNIX timestamp when the counter resets |
Exceeding a limit returns 429 Too Many Requests.
Errors return a non-2xx status and a JSON body. A 402 means the token balance ran out
and may carry buy_tokens: true so your client can route the user to billing.
{
"error": "Insufficient tokens",
"buy_tokens": true
}
| Status | Meaning |
|---|---|
| 400 | Invalid request — missing field or bad format |
| 401 | Missing, revoked or expired key |
| 402 | Not enough tokens on the balance |
| 404 | Resource not found, or not owned by your account |
| 429 | Rate limit exceeded |
| 5xx | Server error — safe to retry with backoff |
curl -X POST https://charlymail.com/api/lists/validate-single \
-H "Authorization: Bearer $CK" \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]"}'
const res = await fetch(
'https://charlymail.com/api/lists/validate-single',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.CHARLYMAIL_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ email: '[email protected]' }),
}
);
if (res.status === 402) throw new Error('Top up token balance');
const data = await res.json();
// { status: 'valid', score: 88, name: 'John', gender: 'male', ... }
import os, requests
r = requests.post(
"https://charlymail.com/api/lists/validate-single",
headers={"Authorization": f"Bearer {os.environ['CHARLYMAIL_KEY']}"},
json={"email": "[email protected]"},
timeout=30,
)
r.raise_for_status()
print(r.json())
| Call | Tokens | Cost |
|---|---|---|
| Validate + enrich one address | 1 | $0.00049 |
| Deep SMTP verification | 5 | $0.00245 |
| Collect one new address | 1 | $0.00049 |
| Reads — lists, contacts, export, job status | 0 | free |
Invalid addresses are refunded after processing, duplicates you already own are not charged, and cache hits from previously verified addresses are free. Full breakdown on the pricing page; billing is prepaid in crypto with no card and no KYC.
Full endpoint reference with every parameter lives in the API documentation.
Get a free API key