API.
Two features, one API. Single search for live one-off lookups, Bulk search for asynchronous batches up to 5,000 rows. JSON in, JSON out — except for bulk uploads (multipart) and downloads (CSV).
Generate keys in Settings → API. Each key is shown once — store it like a password. You can disable or rotate keys at any time.
Authentication
Every request must carry an Authorization header:
Authorization: Bearer mz_live_<your_key>
Keys are scoped to a single account and counted against that account's credit balance. There is no separate API quota — what you see in your dashboard is what you can spend. Keys can be disabled or deleted at any time; revoked keys return 401 unauthorized.
/api/find-email
Find a verified email from first name, last name, and a company domain or name. The call is synchronous — responses arrive in 1–3 seconds for ~95% of requests; the SMTP-confirmation step can extend that to 10–15 seconds for harder targets. One credit is charged only on a verified result; not-found requests are free.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
| firstName | string | yes | Up to 80 chars. |
| lastName | string | yes | Up to 80 chars. |
| domainOrCompany | string | yes | Either a domain (acme.com) or a company name (Acme Inc). Domains yield the most precise results. |
Example — curl
curl -X POST https://mozartemail.com/api/find-email \
-H "Authorization: Bearer mz_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"firstName": "Clara",
"lastName": "Weber",
"domainOrCompany": "deutschebank.com"
}'Response — found
{
"ok": true,
"status": "found",
"email": "clara.weber@deutschebank.com",
"domain": "deutschebank.com",
"confidence": 95,
"provider": "Microsoft 365",
"rawStatus": "FOUND",
"elapsed": 1.4
}Response — not found
{
"ok": false,
"status": "not_found",
"email": null,
"domain": "deutschebank.com",
"confidence": 0,
"provider": null,
"rawStatus": "NOT_FOUND",
"elapsed": 2.1
}Example — Python
import requests
r = requests.post(
"https://mozartemail.com/api/find-email",
headers={"Authorization": "Bearer mz_live_xxx"},
json={
"firstName": "Clara",
"lastName": "Weber",
"domainOrCompany": "deutschebank.com",
},
timeout=30,
)
data = r.json()
if data["ok"]:
print(data["email"], "·", data["confidence"])Example — Node
const r = await fetch("https://mozartemail.com/api/find-email", {
method: "POST",
headers: {
"Authorization": "Bearer mz_live_xxx",
"Content-Type": "application/json",
},
body: JSON.stringify({
firstName: "Clara",
lastName: "Weber",
domainOrCompany: "deutschebank.com",
}),
});
const data = await r.json();
if (data.ok) console.log(data.email, "·", data.confidence);Bulk search
Upload a CSV or .xlsx, poll for status, download a results CSV. Jobs run on our worker and survive network drops on your side — you can disconnect after uploading and come back later for the results.
Lifecycle
- Upload a CSV or .xlsx with first name, last name, and domain-or-company columns. We parse it, hold one credit per row, and return a jobId.
- Status moves queued → processing → done (or error / cancelled). Poll every 5–10 seconds.
- Download the enriched CSV when status is done.
Pricing
One credit per row, only charged when an email is found. We pre-hold the full row count when you upload, then refund unused credits when the job finishes. If your balance can't cover the batch, the upload is rejected with insufficient_credits — nothing is deducted.
Limits
| Max rows per job | 5,000 |
| Max file size | 3 MB |
| Concurrent jobs per account | 3 |
| Result retention | Indefinite (delete with DELETE /api/bulk/{id}) |
File format
UTF-8 CSV or .xlsx (first worksheet) with a header row. Column names are case-insensitive — we accept common variants:
| Required column | Accepted headers (case-insensitive) |
|---|---|
| First name | first_name, firstname, first name, prenom |
| Last name | last_name, lastname, last name, nom |
| Target | domain, company, company_name, website, societe |
Blank rows are skipped silently. Extra columns are ignored.
/api/bulk/upload
Upload a CSV or .xlsx file as multipart/form-data with a single field named file. The endpoint validates the file, holds the credits, and returns a job ID immediately. Processing happens asynchronously.
Example — curl
curl -X POST https://mozartemail.com/api/bulk/upload \ -H "Authorization: Bearer mz_live_xxx" \ -F "file=@./prospects.csv" # .xlsx works the same way curl -X POST https://mozartemail.com/api/bulk/upload \ -H "Authorization: Bearer mz_live_xxx" \ -F "file=@./prospects.xlsx"
Response — accepted
{
"ok": true,
"jobId": "b18e2c80-3f7d-4a1d-9d1e-c4ad3e2c5b71",
"total": 1248
}Common rejections
| HTTP | reason | Meaning |
|---|---|---|
| 400 | no_file | Multipart had no file field. |
| 400 | file_too_large | Larger than 3 MB. |
| 400 | empty_csv | Fewer than 2 rows (no data). |
| 400 | invalid_file | Couldn't parse the file (corrupt .xlsx, malformed CSV, etc.). |
| 400 | missing_columns | Header didn't expose first-name, last-name, and a target column. |
| 400 | no_valid_rows | All rows blank after filtering. |
| 400 | too_many_rows | Over the 5,000-row limit. Response includes got and limit. |
| 402 | insufficient_credits | Balance can't cover the full batch. Response includes balance and required. |
| 429 | too_many_active_jobs | Already 3 jobs in queued or processing. Wait for one to finish. |
/api/bulk/{id}
Returns the job header — status, progress counters, and credit accounting. Poll every 5–10 seconds while queued or processing.
Example — curl
curl https://mozartemail.com/api/bulk/b18e2c80-3f7d-4a1d-9d1e-c4ad3e2c5b71 \ -H "Authorization: Bearer mz_live_xxx"
Response
{
"ok": true,
"job": {
"id": "b18e2c80-3f7d-4a1d-9d1e-c4ad3e2c5b71",
"filename": "prospects.csv",
"status": "processing",
"total": 1248,
"processed": 412,
"found": 358,
"notFound": 54,
"creditsHeld": 1248,
"creditsCharged": 358,
"errorMessage": null,
"createdAt": "2026-05-11T17:24:08.221Z",
"startedAt": "2026-05-11T17:24:09.804Z",
"finishedAt": null
}
}Status values
| queued | Accepted, waiting for the worker. |
| processing | Worker is running rows through the verifier. |
| done | All rows processed. Results ready to download. |
| error | Job failed before finishing. errorMessage is non-null. Held credits are refunded automatically. |
| cancelled | Job was cancelled (delete or admin action). Unused credits refunded. |
/api/bulk/list
List the most-recent 50 jobs for the authed account, newest first.
Response
{
"ok": true,
"jobs": [
{
"id": "b18e2c80-...",
"filename": "prospects.csv",
"status": "done",
"total": 1248,
"processed": 1248,
"found": 1041,
"notFound": 207,
"creditsHeld": 0,
"creditsCharged": 1041,
"createdAt": "2026-05-11T17:24:08.221Z",
"finishedAt": "2026-05-11T17:31:52.118Z"
}
]
}/api/bulk/{id}/download
Returns the enriched results as a CSV download. Available for any job — partial results stream as soon as rows are processed. Response is text/csv; charset=utf-8.
Output columns
| first_name | Echo of the input. |
| last_name | Echo of the input. |
| domain_or_company | Echo of the input. |
| Found email, or blank. | |
| status | One of verified, catch-all, risky, invalid, not_found. |
| confidence | 0–100 (see scale below). |
| provider | Detected mail provider, e.g. Microsoft 365, Google Workspace. |
Example — curl
curl https://mozartemail.com/api/bulk/b18e2c80-.../download \ -H "Authorization: Bearer mz_live_xxx" \ -o results.csv
/api/bulk/{id}
Remove a job and its rows. Permanent — there is no undo. Use this on jobs you no longer need; finished results are already in any CSV you downloaded.
curl -X DELETE https://mozartemail.com/api/bulk/b18e2c80-... \ -H "Authorization: Bearer mz_live_xxx"
Responses: { "ok": true } on success, 404 not_found if the ID doesn't belong to your account.
Statuses & confidence
Both Single and Bulk searches return the same set of result statuses. Use status for branching logic and confidence for risk-based thresholds.
Result statuses
| verified | SMTP-confirmed deliverable. Safe to send. |
| catch-all | Domain accepts all mail — pattern likely correct but can't be SMTP-proven. |
| risky | Partial signals. Send at lower volume or warm carefully. |
| invalid | SMTP refused; do not send. |
| not_found | No plausible pattern. No credit charged. |
Confidence scale
| Score | Meaning |
|---|---|
| 99 | SMTP-confirmed deliverable. |
| 95 | Pattern + MX confirmed. |
| 85 | Pattern match, catch-all domain. |
| 75 | Probable, partial signal. |
| 60 | Risky — verify manually. |
| 0 | Not found. |
Errors
All non-2xx responses include { "ok": false, "reason": "<code>" }. Common codes across both features:
| HTTP | reason | When |
|---|---|---|
| 400 | invalid_json | Body wasn't valid JSON (find-email only). |
| 400 | missing_fields | Required fields absent. |
| 400 | invalid_form | Multipart body could not be parsed (bulk upload). |
| 401 | unauthorized / not_authenticated | Missing or revoked API key. |
| 402 | insufficient_credits | Top up to continue. Bulk includes required; single returns balance: 0. |
| 404 | not_found | Unknown job ID, or job belongs to another account. |
| 429 | rate_limited | Hit one of the /api/find-email rate gates (see Rate limits below). Retry-After header indicates back-off seconds. |
| 429 | too_many_active_jobs | Already 3 bulk jobs queued or processing. |
| 500 | server_exception | Internal error. Contact support if persistent. |
| 503 | upstream_rate_limited | Upstream verifier rate-limited us. Back off using the Retry-After header (5s by default). |
| 503 | icypeas_timeout | Upstream verifier didn't respond in 30s; safe to retry. |
Rate limits
| Single search | 40 requests / min per account, 3 requests / sec per account (burst), and 40 requests / min across all accounts (shared upstream quota). Any of the three returns 429 rate_limited with a Retry-After header — back off on the indicated seconds and retry. |
| Bulk uploads | 3 concurrent jobs per account. Queue more by waiting for one to finish. |
| Status / list / download | Unlimited. Poll every 5–10 seconds for status; longer intervals are kinder to our cache. |
Need something else? Back home · Manage keys · concierge@mozartemail.com