Skip to content
looot docs
Esc
↑↓navigate↵open⌘Jpreview
On this page

Enrich a lead list

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 before you run anything:

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 before you start.

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, and add fallback so a miss on one provider tries the next inside the same hold.

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}}
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}
  }'
looot run job:people.email.find \
  --input '{"first_name": "Jane", "last_name": "Doe", "domain": "example.com"}' \
  --wait

Read normalized.fields.email when it is present, or result otherwise. An outcome 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.

Verify the email

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

run {"endpointId": "job:people.email.verify", "input": {"email": "jane.doe@example.com"}, "idempotencyKey": "list1-row1-verify", "fallback": true}
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
  }'
looot run job:people.email.verify \
  --input '{"email": "jane.doe@example.com"}' \
  --wait

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.

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, 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 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: the run is blocked, not charged. Show topUp.checkoutUrl from the error, or call top_up, then retry with a new idempotencyKey.

Was this page helpful?