← CharlyMail

Email verification API

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 key
The short version: CharlyMail exposes a REST API at https://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.

Authentication

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.

Endpoints

POST/api/lists/validate-single

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.

POST/api/lists/upload

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.

GET/api/lists   GET/api/lists/:id

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.

GET/api/lists/:id/contacts   GET/api/lists/:id/export

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.

POST/api/parser

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.

GET/api/suppress/list   POST/api/suppress/add

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.

Rate limits

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:

HeaderMeaning
X-RateLimit-LimitCeiling for the current window
X-RateLimit-RemainingRequests left in this window
X-RateLimit-ResetUNIX timestamp when the counter resets

Exceeding a limit returns 429 Too Many Requests.

Errors

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
}
StatusMeaning
400Invalid request — missing field or bad format
401Missing, revoked or expired key
402Not enough tokens on the balance
404Resource not found, or not owned by your account
429Rate limit exceeded
5xxServer error — safe to retry with backoff

Code examples

curl

curl -X POST https://charlymail.com/api/lists/validate-single \
  -H "Authorization: Bearer $CK" \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]"}'

Node.js

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', ... }

Python

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

Pricing per call

CallTokensCost
Validate + enrich one address1$0.00049
Deep SMTP verification5$0.00245
Collect one new address1$0.00049
Reads — lists, contacts, export, job status0free

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