# Enrichment

> Find professional emails, find phone numbers and verify addresses with the Emelia enrichment API. Every call starts a job that you poll until it is done.

Source: https://docs.emelia.io/docs/enrichment

Emelia has three enrichment tools, each with its guide and its routes:

- [Email Finder API](https://docs.emelia.io/docs/enrichment/email-finder): The professional email of a person, from a name and a company. 1 credit, refunded when nothing is found.

- [Phone Number Finder API](https://docs.emelia.io/docs/enrichment/phone-finder): The phone number of a person, from a LinkedIn profile URL. 50 credits, refunded when nothing is found.

- [Email Verification API](https://docs.emelia.io/docs/enrichment/email-verifier): Whether an address can receive mail. 0.25 credit, never refunded.

## How a job works

The three tools work the same way:

1. You `POST` the input. Emelia answers at once with `{ "success": true, "jobId": "..." }`.
2. You `GET` the job every 3 to 5 seconds until its `status` is no longer `running`.
3. You read the result in `data`: `status` is `done` (or `error`), with the email, the number or the verdict.

| Tool           | Start                      | Result                            | History                   | Typical duration               |
| -------------- | -------------------------- | --------------------------------- | ------------------------- | ------------------------------ |
| Email Finder   | `POST /tools/find/email`   | `GET /tools/find/email/{jobId}`   | `GET /tools/find/email`   | Seconds, up to about 4 minutes |
| Phone Finder   | `POST /tools/find/phone`   | `GET /tools/find/phone/{jobId}`   | `GET /tools/find/phone`   | No fixed upper bound           |
| Email Verifier | `POST /tools/verify/email` | `GET /tools/verify/email/{jobId}` | `GET /tools/verify/email` | Under 60 seconds, then `error` |

This helper starts a job and polls it, for any of the three tools:

**curl**

```bash
# Start the job
JOB_ID=$(curl -s -X POST https://api.emelia.io/tools/verify/email \
  -H "Authorization: $EMELIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email": "claire.fontaine@nexora.io"}' | jq -r .jobId)

# Poll it every 3 seconds until it is no longer running
while true; do
  RESULT=$(curl -s "https://api.emelia.io/tools/verify/email/$JOB_ID" -H "Authorization: $EMELIA_API_KEY")
  [ "$(echo "$RESULT" | jq -r .data.status)" != "running" ] && break
  sleep 3
done
echo "$RESULT"
```

**JavaScript**

```js
const API = 'https://api.emelia.io';
const headers = { Authorization: process.env.EMELIA_API_KEY, 'Content-Type': 'application/json' };
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

/** Starts a job, then polls it every 3 seconds, for up to 5 minutes. */
export async function runJob(path, input) {
  const start = await fetch(`${API}${path}`, { method: 'POST', headers, body: JSON.stringify(input) });
  const { jobId, error } = await start.json();
  if (!jobId) throw new Error(error ?? `HTTP ${start.status}`);

  for (let i = 0; i < 100; i++) {
    await sleep(3000);
    const res = await fetch(`${API}${path}/${jobId}`, { headers });
    const { data } = await res.json();
    if (data && data.status !== 'running') return { jobId, ...data };
  }
  return { jobId, status: 'running' }; // keep the jobId and come back later
}

console.log(await runJob('/tools/verify/email', { email: 'claire.fontaine@nexora.io' }));
```

**Python**

```python
import os
import time
import requests

API = "https://api.emelia.io"
session = requests.Session()
session.headers["Authorization"] = os.environ["EMELIA_API_KEY"]

def run_job(path, payload, every=3, timeout=300):
    """Starts a job, then polls it until it is no longer running."""
    start = session.post(f"{API}{path}", json=payload).json()
    job_id = start.get("jobId")
    if not job_id:
        raise RuntimeError(start.get("error", "job not started"))
    deadline = time.time() + timeout
    while time.time() < deadline:
        time.sleep(every)
        data = session.get(f"{API}{path}/{job_id}").json().get("data") or {}
        if data.get("status") != "running":
            return {"jobId": job_id, **data}
    return {"jobId": job_id, "status": "running"}  # keep the jobId, come back later

print(run_job("/tools/verify/email", {"email": "claire.fontaine@nexora.io"}))
```

A job still running after your timeout is not lost: keep its `jobId` and read it later. Never start the same search again instead: it would be billed twice.

## Costs and limits

| Tool           | Cost        | Refunded when                                                       |
| -------------- | ----------- | ------------------------------------------------------------------- |
| Email Finder   | 1 credit    | No email is found, or the search could not start. Not on a timeout. |
| Phone Finder   | 50 credits  | No number is found, or the search could not start.                  |
| Email Verifier | 0.25 credit | Never.                                                              |

Credits are taken when the job starts. See [Credits](https://docs.emelia.io/docs/credits.md) for the history of your debits. The `/tools/...` routes are not counted in the per minute [rate limit](https://docs.emelia.io/docs/rate-limits.md), and their polling is free.

## Enrich many contacts

There is no batch route for the tools: you start one job per contact. To go fast:

1. **Filter first.** Remove the contacts you will not reach before spending credits.
2. **Start every job, then poll them.** Waiting for each job before starting the next one turns minutes into hours.
3. **Keep the `jobId` of every contact,** and write the results back to your list with [Update a contact](https://docs.emelia.io/docs/lists/update-contact.md).
4. **Stop at the first credit error.** The next contacts would fail the same way.

To find the emails of a whole LinkedIn extraction, use [Find the emails of a scraper](https://docs.emelia.io/docs/linkedin-scraper.md): 1 credit per email found.

## From an AI assistant

The [MCP server](https://docs.emelia.io/docs/ai/mcp.md) wraps the three tools: `find_email`, `find_phone` and `verify_email` start the job and wait for the result for up to 90 seconds, and `get_enrichment_result` reads a job that was still running. Note that `find_email` needs `country`, even if the tool does not mark it as required.
