---
title: Enrich a lead list
description: Find and verify a work email for every person on a list, one row at a time, with a quote before you spend and a provider name on every result.
---

This walks through `people.email.find` and `people.email.verify` on one lead, then how to repeat
it for a list. It is the same loop as the `find-email` skill and the recipes plugin skill.

## What it costs

`people.email.find` and `people.email.verify` each price per call, and the useful number for a
batch is `costPerSuccessUsd`: the price divided by the provider's success rate, so a cheap provider
that mostly misses can cost more per hit than a pricier one that mostly finds. Read it from
[`search`](/mcp-tools/search) before you run anything:

```json MCP
search {"filters": {"capability": "people.email.find"}, "prefer": "cheapest"}
```

On 2026-09-28 the cheapest `people.email.find` provider listed `costPerSuccessUsd` around
$0.0036, and the cheapest `people.email.verify` provider around $0.0019 per call. Prices change, so
read the live number before quoting a customer or your own budget. Multiply by the row count for
both jobs, add them, and check [`balance`](/mcp-tools/balance) before you start.

1. **Find the email**

    Send `first_name` and `last_name` (or `name`) plus `domain`. A `linkedin_url` or `company` also
    works. Give the run its own [`idempotencyKey`](/concepts/idempotency), and add [`fallback`](/concepts/fallback) so a miss on one provider tries
    the next inside the same hold.

    <CodeGroup>

    ```json MCP
    run {"endpointId": "job:people.email.find", "input": {"first_name": "Jane", "last_name": "Doe", "domain": "example.com"}, "idempotencyKey": "list1-row1-find", "fallback": {"maxAttempts": 3, "maxCostUsd": 0.1}}
    ```

    ```bash REST
    curl -s "https://api.looot.ai/v1/runs?wait=20" \
      -H "Authorization: Bearer $LOOOT_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "endpointId": "job:people.email.find",
        "input": {"first_name": "Jane", "last_name": "Doe", "domain": "example.com"},
        "idempotencyKey": "list1-row1-find",
        "fallback": {"maxAttempts": 3, "maxCostUsd": 0.1}
      }'
    ```

    ```bash CLI
    looot run job:people.email.find \
      --input '{"first_name": "Jane", "last_name": "Doe", "domain": "example.com"}' \
      --wait
    ```

    </CodeGroup>

    :::note
    looot 1.1.0 does not accept `--fallback` or `--prefer` on `looot run`. The CLI call above tries one
    provider. Use MCP or REST when you want fallback across a list.
    :::

    Read `normalized.fields.email` when it is present, or `result` otherwise. An [`outcome`](/concepts/outcomes) of `weak`
    with `normalized.fields.check` (or `verdict`) equal to `guessed` is a pattern guess, not a
    confirmed address: verify it before you keep it, same as a found one.

2. **Verify the email**

    Run `people.email.verify` on every address you plan to keep, with a fresh `idempotencyKey`:

    <CodeGroup>

    ```json MCP
    run {"endpointId": "job:people.email.verify", "input": {"email": "jane.doe@example.com"}, "idempotencyKey": "list1-row1-verify", "fallback": true}
    ```

    ```bash REST
    curl -s "https://api.looot.ai/v1/runs?wait=20" \
      -H "Authorization: Bearer $LOOOT_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "endpointId": "job:people.email.verify",
        "input": {"email": "jane.doe@example.com"},
        "idempotencyKey": "list1-row1-verify",
        "fallback": true
      }'
    ```

    ```bash CLI
    looot run job:people.email.verify \
      --input '{"email": "jane.doe@example.com"}' \
      --wait
    ```

    </CodeGroup>

3. **Read the verdict and keep only valid**

    `normalized.fields.status` (email verify) is one of `valid`, `invalid`, `catch_all`, `risky` or
    `unknown`. Keep `valid`. Flag `catch_all` and `risky` for the person reviewing the list; don't send to
    them outright, and drop `invalid` and `unknown`.

4. **Repeat, a few rows at a time**

    Run a handful of leads at once, not the whole list in parallel. On [`too_many_inflight_runs`](/errors/rest-errors#too_many_inflight_runs),
    wait a couple of seconds and retry with the same `idempotencyKey`. When the list is done, report
    how many were found, how many verified `valid`, and the total spent (sum of `actualCost`).

## What the answer looks like

A completed run carries:

| Field | What it is |
| --- | --- |
| `status` | `completed`, `failed`, `queued` or `running` |
| `outcome` | `hit`, `weak`, `miss`, `error`, `rejected`, `skipped` or `pending` |
| [`servedProviderId`](/concepts/served-provider) | which provider actually answered (can differ from the one you asked for, after a fallback) |
| `actualCost` | what this run charged, in USD |
| `normalized.fields` | on `people.email.find`: `email`, `check`; on `people.email.verify`: `email`, `status`, `deliverable` |
| `route.summary` | fallback runs only, one line per attempt and the total charged |

## On a miss or error

- No address found: `outcome` is `miss`. With `fallback`, the run already tried other providers
  inside the hold before giving up.
- Only a guessed pattern: `outcome` is `weak`, `check`/`verdict` is `guessed`. Verify it before you
  use it; a wrong guess fails verification and costs nothing extra beyond the verify call.
- Verify comes back `catch_all` or `risky`: the mailbox may or may not exist. Show it to a person
  before anything is sent.
- `too_many_inflight_runs`: wait a couple of seconds and retry the same `idempotencyKey`.
- [`insufficient_balance`](/errors/rest-errors#insufficient_balance): the run is blocked, not charged. Show `topUp.checkoutUrl` from the error,
  or call [`top_up`](/mcp-tools/top-up), then retry with a **new** `idempotencyKey`.

<Related />
