Pre-Black Friday: 20% off, for as long as you stayEARLYBFon Solo and AgencyEnds Nov 28Start free

How-to guide

How to Find an Email From a List of Domains (2026)

Start from a column of domains and end with a CSV of work emails, each with a status. Manual check first, then the bulk way.

Get your free API key →Free to start. No credit card. 1,000 records to spend whenever you like.

TL;DR

  • A domain is a company, not a person. To get emails from a list, filter people by domain, pick the titles you want, and read each address's status.
  • It takes about 15 minutes for 50 domains. The count is free. An export spends one LeadOcean record per row.
  • Do not loop one call per domain. One search or export takes the whole list as an array. Export needs Solo ($97 a month) or higher.

Prerequisites

  • A LeadOcean account from app.leadocean.io. The free plan has no card and 1,000 records, once. Bulk CSV export is not on Free, so the export step needs Solo ($97 a month) or higher.
  • curl, jq and dig.
  • A text file of domains, one per line, without https:// or www..
  • An API key in your shell as $LEADOCEAN_API_KEY.
bash
export LEADOCEAN_API_KEY="paste-your-key-here"

Step 1: Clean the list and check mail routing (the manual way)

Mail for a domain is routed by its MX records (RFC 5321, September 2026). A domain with no MX record has no working inboxes, so drop it first.

bash
sed -E 's#^https?://##; s#^www\.##; s#/.*$##' domains.txt | sort -u > clean.txt

while read d; do
  [ -n "$(dig +short MX "$d")" ] && echo "$d"
done < clean.txt > mail-domains.txt

wc -l mail-domains.txt

This tells you which domains can receive mail. It gives you no names and no addresses. Guessing patterns at scale bounces, because catch-all domains accept any address.

Step 2: Turn the list into a JSON array

The API takes filters as arrays. Convert the file once and reuse it. Check that the array has one entry per domain and no blank strings. Fix typos now, because a misspelled domain only shows up later as zero rows.

bash
jq -R . mail-domains.txt | jq -s . > domains.json
head -c 200 domains.json

Step 3: Size the list for free

Count before you spend. A search with count=true as a query parameter and limit at 1 spends nothing. Send the domains as an array.

bash
curl -s "https://api.leadocean.io/v1/people/search?count=true" \
  -H "x-api-key: $LEADOCEAN_API_KEY" -H "content-type: application/json" \
  -d "{\"domain\": $(cat domains.json), \"jobLevel\": [\"C-Team\", \"VP\"], \"limit\": 1}" \
  | jq '.meta | {total, totalIsExact, countOnly}'

meta.total is capped at 100,000. If the total is far above what you want to email, narrow with jobLevel, jobFunction or title before you go on.

Step 4: Sample a few rows and read the status

Pull a small page first to check the shape. Each person returned costs one record, so keep limit low.

bash
curl -s "https://api.leadocean.io/v1/people/search" \
  -H "x-api-key: $LEADOCEAN_API_KEY" -H "content-type: application/json" \
  -d "{\"domain\": $(cat domains.json), \"jobLevel\": [\"C-Team\", \"VP\"], \"limit\": 3}" \
  | jq '{meta: .meta, people: .data}'

A search row is thin. It carries flags such as has_email and email_status, not the address. Each row has a person_id at meta.source_ids.person_id. Page further with cursor. One search pages to 10,000 rows, and then meta.depthCapped is true.

Step 5: Look up one work email by person_id

For a single person, send the person_id to the work-email endpoint.

bash
PERSON_ID="paste-person_id-from-step-4"

curl -s "https://api.leadocean.io/v2/people/email/work?person_id=$PERSON_ID" \
  -H "x-api-key: $LEADOCEAN_API_KEY" | jq '.data'

It returns one address or data: null. It returns only verified, catch_all_valid or catch_all addresses at the person's current employer. It costs one record per call, including a call that finds nothing. For a list, do not loop this. Use Step 6.

Step 6: Export the whole list

An export takes the same filters and writes one CSV. A request takes up to 50,000 rows and a day takes up to 500,000.

bash
curl -s https://api.leadocean.io/v1/exports \
  -H "x-api-key: $LEADOCEAN_API_KEY" -H "content-type: application/json" \
  -d "{
    \"name\": \"VP and C-team at my domain list\",
    \"filters\": {
      \"domain\": $(cat domains.json),
      \"jobLevel\": [\"C-Team\", \"VP\"],
      \"emailStatus\": [\"verified\", \"catch_all_valid\", \"catch_all\"]
    },
    \"limit\": 500,
    \"preset\": \"full\"
  }" | jq '.data | {id, status}'

The full preset reveals emails, so the key needs the enrich scope. Each row written costs one record. Poll GET /v1/exports/{id}, then fetch GET /v1/exports/{id}/download when the status is done.

You can do the same in the app on the Exports page. It has a filter builder, a column picker and a price preview before you start, then a CSV download.

Step 7: Keep the right rows

An export returns every person who matches, so a large company can fill your row budget alone. Trim the CSV before you send. First list the email columns in your file.

bash
head -1 export.csv | tr ',' '\n' | grep -n "email_"

Keep rows whose email_1_status is verified or catch_all_valid. If a domain has none, loosen jobLevel to include Director and run a second small export for just those domains. Do this before you widen emailStatus, because a looser status raises the bounce rate.

Send a first small batch, watch the bounces, then scale. The data is refreshed monthly and each record carries its own fetched_at date, so an old export is worth re-running before a new campaign.

What you get

The Step 3 count is a real run. We ran it on 2026-10-21 with leadocean_count_leads, which spends 0 records, for C-Team and VP people at three placeholder domains (acme.com, example.com, example.org).

json
{
  "total": 9,
  "totalIsExact": true,
  "totalAll": 209,
  "distinctCompanies": 2,
  "credits": 0,
  "unsupportedFilters": []
}

That is 9 mailable people (verified, catch_all_valid or catch_all) on 2026-10-21. totalAll is the same filters with no email condition. Read the gap: the mailable count is not everyone we hold.

The export CSV has numbered columns: email_1_address, email_1_status, email_2_address and so on. The name is in profile_full_name and the employer domain in current_company_domain. GET /v1/exports/columns is free and lists every column id. The work-email lookup returns the shape below, from the API reference, with placeholder values.

json
{
  "success": true,
  "data": {
    "email": "jane.doe@acme.com",
    "status": "verified",
    "verified_batch_date": "2026-09-20"
  }
}

LeadOcean gives no refund or credit-back for bounced emails. Filter on email_status before you send.

Troubleshooting

ErrorCauseFix
401Missing, wrong or revoked keyRe-copy the key from app.leadocean.io and export $LEADOCEAN_API_KEY again.
429Above the plan's rate limit (5 a second on Free, 200 on Agency)Wait for Retry-After, then slow down. Use one export, not a per-domain loop.
402The 1,000 free records are spentCheck GET /v1/account. Move to Solo or Agency. Free records do not reset.
Zero rowsA bad enum value, such as VP Sales as a job levelJob level is VP. Check strings with GET /v1/enums or leadocean_list_enum_values.
Zero rows for a domainWe hold nobody there at that level, or the domain has a typoCount that domain alone for free. Drop the level filter and recount.
Export refusedThe limit is above 50,000 rows, or the day's 500,000 is usedSplit the list into several exports.

FAQ

Can I get one email per domain?

Not as one lookup. A domain has many people. Filter by title or jobLevel, and keep the contacts you want per company.

How many records will my list cost?

One per row exported. Fifty domains at about three contacts each is up to 150. Counts, filters and empty pages cost nothing.

How do I know an address is real?

Each address has a status. Only verified is a checked mailbox. catch_all domains accept any address, so catch_all_valid is the safer of the two.

Can I do this on the free plan?

Yes, up to Step 5. Counts are free, and a search page or a work-email lookup spends from the 1,000 free records. Bulk CSV export starts at Solo ($97 a month). On Free, page through the search with cursor and look up each person_id instead.

What if my list has company names, not domains?

Use companyName in the search, or find the domains first. For a worked example from a public list, see how to find emails from the YC company list.

What about one domain at a time?

Read how to find an email from a company domain. For people without a company list, try podcast guests. More recipes are in the blog hub, and plans are on pricing.

Turn your domain list into verified work emails, free

Free to start. No credit card. 1,000 records to spend whenever you like.

Get your free API key →