# Email Finder

> API reference for the Warmerly Email Finder: find and verify a person's or company's email from a domain, website, or name, via async jobs you poll.

Source: https://warmerly.com/docs/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

```text
POST /email-finder/lookup
```

Submit 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](https://warmerly.com/docs/email-finder#quota)).

```bash
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):

```json
{
  "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`:

```json
{
  "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

```text
POST /email-finder/bulk
```

Submit 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](https://warmerly.com/docs/email-finder#batch-status).

```bash
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" }
    ]
  }'
```

```json
{
  "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

```text
GET /email-finder/jobs/:id
```

Fetch the current status of a lookup job, whether it came from a single or bulk request.

```bash
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"
```

```json
{
  "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

```text
DELETE /email-finder/jobs/:id
```

Deletes the job and its discovered email records.

```bash
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"
```

```json
{ "deleted": "3f9a2e2a-1c4b-4a2e-9b1e-2f6c8a7d5e10" }
```

## Batch status

```text
GET /email-finder/batches/:id
```

Returns 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`.

```bash
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"
```

```json
{
  "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](https://warmerly.com/docs/email-finder#list-results) with `batchId` to fetch the actual emails once a batch is complete.

## List results

```text
GET /email-finder/results
```

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

```bash
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"
```

```json
{
  "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

```text
GET /email-finder/quota
```

Returns your current monthly Email Finder usage. This endpoint is account-level, it does not require `X-Project-Id`.

```bash
curl https://app.warmerly.com/api/v1/email-finder/quota \
  -H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
```

```json
{ "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`:

```json
{
  "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. |

## Each part of this reference has its own page (7)

- [Single lookup](https://warmerly.com/docs/email-finder/single-lookup)
- [Bulk lookup](https://warmerly.com/docs/email-finder/bulk-lookup)
- [Get a job](https://warmerly.com/docs/email-finder/get-a-job)
- [Delete a job](https://warmerly.com/docs/email-finder/delete-a-job)
- [Batch status](https://warmerly.com/docs/email-finder/batch-status)
- [List results](https://warmerly.com/docs/email-finder/list-results)
- [Quota](https://warmerly.com/docs/email-finder/quota)

## More in API reference

- [Verify](https://warmerly.com/docs/verify): API reference for Warmerly's email verification: check syntax, MX records, disposable/role addresses, and…
- [Leads](https://warmerly.com/docs/leads): API reference for the Warmerly lead database: search millions of crawled companies, export matching rows to…
- [Inbox](https://warmerly.com/docs/inbox): API reference for the Warmerly Inbox: list contacts, pull a unified email/LinkedIn conversation timeline,…
