Email Finder
Find and verify a person's or company's email address from a domain, website, or business name, optionally narrowed to a specific person. Lookups run asynchronously as jobs: most resolve in a few seconds, but cache misses can take longer while Warmerly scrapes and verifies candidates, so the API returns immediately with either a completed result or a job you poll.
All Email Finder endpoints require X-Api-Key. Endpoints that create or read job data are project-scoped and also require X-Project-Id (missing or malformed: 400 bad_request; a project you are not a member of: 403 forbidden; an unknown project: 404 not_found); GET /email-finder/quota is account-level and does not require X-Project-Id.
#Single lookup
POST /email-finder/lookupSubmit one lookup. Provide at least one of domain, website, or name, add person to narrow the search to a specific individual at that domain/company.
| Field | Type | Description |
|---|---|---|
domain | string | Company domain, e.g. "acme.com". |
website | string | Full website URL; the domain is extracted from it. |
name | string | Business name, used when no domain or website is given. If domain is also sent, domain is used. |
location | string | Optional location hint to disambiguate a business name. |
person.firstName | string | Optional first name to target a specific person. |
person.lastName | string | Optional last name to target a specific person. |
person is used with a business name lookup. If none of domain, website or name gives a usable value the request returns 400 bad_request (provide a valid domain, website, or business name). If you are out of lookup quota it returns 402 quota_exhausted (see Quota).
curl https://app.warmerly.com/api/v1/email-finder/lookup \
-X POST \
-H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "X-Project-Id: 8f14e45f-ceea-4c6a-8c8d-6b1a5b5c9c1a" \
-H "Content-Type: application/json" \
-d '{
"domain": "acme.com",
"person": { "firstName": "Jane", "lastName": "Doe" }
}'If the answer for that domain is already cached, the lookup completes synchronously and returns 200 with the result inline (result is null if the cache holds no email for the domain):
{
"status": "done",
"jobId": "3f9a2e2a-1c4b-4a2e-9b1e-2f6c8a7d5e10",
"job": {
"id": "3f9a2e2a-1c4b-4a2e-9b1e-2f6c8a7d5e10",
"status": "done",
"confidence": 92,
"error": null,
"createdAt": "2026-07-02T09:14:03.000Z",
"completedAt": "2026-07-02T09:14:03.000Z"
},
"result": {
"email": "jane.doe@acme.com",
"confidence": 92,
"verifyStatus": "verified",
"source": "pattern_guess",
"isCatchAll": false,
"mxHost": "aspmx.l.google.com",
"evidenceUrl": "https://acme.com/team",
"providerName": "Google Workspace"
}
}Otherwise it enqueues a job and returns 202 with a pollUrl:
{
"status": "pending",
"jobId": "3f9a2e2a-1c4b-4a2e-9b1e-2f6c8a7d5e10",
"pollUrl": "https://app.warmerly.com/api/v1/email-finder/jobs/3f9a2e2a-1c4b-4a2e-9b1e-2f6c8a7d5e10"
}Poll the job (see below) until status is "done" or "failed".
#Bulk lookup
POST /email-finder/bulkSubmit up to 1,000 lookups in one request. Each item accepts the same fields as a single lookup. All items are enqueued under a shared batchId you can use to track progress via Batches.
curl https://app.warmerly.com/api/v1/email-finder/bulk \
-X POST \
-H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "X-Project-Id: 8f14e45f-ceea-4c6a-8c8d-6b1a5b5c9c1a" \
-H "Content-Type: application/json" \
-d '{
"items": [
{ "domain": "acme.com", "person": { "firstName": "Jane", "lastName": "Doe" } },
{ "website": "https://widgetco.io" },
{ "name": "Roasted Coffee Co", "location": "Austin, TX" }
]
}'{
"batchId": "6e6f9d0a-4a3f-4c7e-9c86-1b6a4e5c2f01",
"results": [
{ "index": 0, "ok": true, "jobId": "3f9a2e2a-1c4b-4a2e-9b1e-2f6c8a7d5e10", "cached": true, "result": { "email": "jane.doe@acme.com", "confidence": 92, "verifyStatus": "verified", "source": "pattern_guess", "isCatchAll": false, "mxHost": "aspmx.l.google.com", "evidenceUrl": "https://acme.com/team", "providerName": "Google Workspace" } },
{ "index": 1, "ok": true, "jobId": "7c1e9b3d-8a2f-4e11-9d0c-5b3a1f4e6c22", "cached": false },
{ "index": 2, "ok": false, "error": "invalid input" }
]
}items must hold 1 to 1,000 entries, otherwise 400 bad_request (invalid_body). Each item is evaluated independently: an invalid item (ok: false) does not fail the rest of the batch. Items that hit the cache resolve immediately (cached: true, result present); the rest are enqueued (cached: false) and must be polled individually via their jobId, or tracked in aggregate via batch status.
#Get a job
GET /email-finder/jobs/:idFetch the current status of a lookup job, whether it came from a single or bulk request.
curl https://app.warmerly.com/api/v1/email-finder/jobs/3f9a2e2a-1c4b-4a2e-9b1e-2f6c8a7d5e10 \
-H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "X-Project-Id: 8f14e45f-ceea-4c6a-8c8d-6b1a5b5c9c1a"{
"job": {
"id": "3f9a2e2a-1c4b-4a2e-9b1e-2f6c8a7d5e10",
"status": "scraping",
"confidence": null,
"error": null,
"createdAt": "2026-07-02T09:14:03.000Z",
"completedAt": null
},
"result": null
}status moves through pending → resolving → scraping → verifying → done (or failed). result is populated once status is "done" (and is null if no email was found); on "failed", check job.error for the reason. A job that doesn't exist, or that belongs to a different project than the one in X-Project-Id, returns 404 not_found.
#Delete a job
DELETE /email-finder/jobs/:idDeletes the job and its discovered email records.
curl https://app.warmerly.com/api/v1/email-finder/jobs/3f9a2e2a-1c4b-4a2e-9b1e-2f6c8a7d5e10 \
-X DELETE \
-H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "X-Project-Id: 8f14e45f-ceea-4c6a-8c8d-6b1a5b5c9c1a"{ "deleted": "3f9a2e2a-1c4b-4a2e-9b1e-2f6c8a7d5e10" }#Batch status
GET /email-finder/batches/:idReturns aggregate progress for a bulk lookup, grouped by job status, for the project in X-Project-Id. A batch id that matches no jobs in that project returns total: 0 rather than a 404.
curl https://app.warmerly.com/api/v1/email-finder/batches/6e6f9d0a-4a3f-4c7e-9c86-1b6a4e5c2f01 \
-H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "X-Project-Id: 8f14e45f-ceea-4c6a-8c8d-6b1a5b5c9c1a"{
"batchId": "6e6f9d0a-4a3f-4c7e-9c86-1b6a4e5c2f01",
"total": 3,
"done": 2,
"pending": 1,
"counts": {
"done": 2,
"scraping": 1
}
}done sums jobs in either done or failed status. Use List results with batchId to fetch the actual emails once a batch is complete.
#List results
GET /email-finder/resultsLists jobs for the current project, each joined with its winning discovered email (if any), newest first. Cursor-paginated.
| Query param | Type | Description |
|---|---|---|
status | string | Filter by job status: pending, resolving, scraping, verifying, done, failed. |
batchId | uuid | Restrict to jobs from a specific bulk batch. |
minConfidence | integer (0-100) | Only include results with confidence at or above this value. |
cursor | ISO 8601 datetime | Pass the previous response's nextCursor to fetch the next page. |
limit | integer (1-100, default 25) | Page size. |
curl "https://app.warmerly.com/api/v1/email-finder/results?status=done&limit=25" \
-H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "X-Project-Id: 8f14e45f-ceea-4c6a-8c8d-6b1a5b5c9c1a"{
"results": [
{
"id": "3f9a2e2a-1c4b-4a2e-9b1e-2f6c8a7d5e10",
"status": "done",
"confidence": 92,
"inputDomain": "acme.com",
"inputName": null,
"batchId": null,
"createdAt": "2026-07-02T09:14:03.000Z",
"email": "jane.doe@acme.com",
"verifyStatus": "verified",
"source": "pattern_guess"
}
],
"nextCursor": "2026-07-02T09:14:03.000Z"
}Pass nextCursor as the cursor query param to fetch the next page; it is null on the last page.
#Quota
GET /email-finder/quotaReturns your current monthly Email Finder usage. This endpoint is account-level, it does not require X-Project-Id.
curl https://app.warmerly.com/api/v1/email-finder/quota \
-H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"{ "used": 37, "limit": 500, "remaining": 463 }The limit depends on your plan; with no active plan the allowance is 50 lookups a month. Both POST /email-finder/lookup and POST /email-finder/bulk check remaining quota before enqueuing. A bulk request counts against quota by item count, plus any of your earlier lookups that are still queued. If the request would exceed your limit, it's rejected with 402 quota_exhausted:
{
"error": {
"code": "quota_exhausted",
"message": "You've used 500 of 500 email lookups this month. Upgrade your plan to continue.",
"details": {
"label": "email lookups",
"used": 500,
"limit": 500,
"topUpUrl": null,
"upgradeUrl": "/settings/billing"
}
}
}The message text varies: when credits can cover the shortfall it offers them, and details.topUp and details.topUpUrl are set.
#Errors
| Status | Code | When |
|---|---|---|
400 | bad_request | Missing or malformed X-Project-Id (project-scoped endpoints), invalid body/query, or no valid domain/website/name provided. |
401 | unauthorized | Missing, invalid, or revoked API key. |
402 | quota_exhausted | Monthly lookup quota used up (lookup and bulk). |
403 | forbidden | You are not a member of the project in X-Project-Id. |
403 | agency_plan_required | API keys only work while the workspace the request acts on is on the Agency plan. |
404 | not_found | Project, or job, doesn't exist or doesn't belong to the given project. |
429 | too_many_requests | The API key's per-minute rate limit was exceeded. |