TL;DR
- A domain alone gives you a mail server, not a person. You need a name at that domain first, then an address that has been checked.
- It takes about 10 minutes for one person. The API route spends about 4 LeadOcean records per person: 3 for a search at
limit3, plus 1 for the work-email lookup. - For a list, skip the loop and run one export. Each row costs one record, and you read
email_statusbefore you send.
Prerequisites
- A LeadOcean account from app.leadocean.io. The free plan has no card and gives 1,000 records, one-off. Step 6 needs bulk CSV export, which is not on Free: it starts on Solo ($97 a month).
curl,jqanddig.- An API key in your shell as
$LEADOCEAN_API_KEY. Free keys get both scopes, search and enrich.
export LEADOCEAN_API_KEY="paste-your-key-here"Step 1: Check where the domain's mail goes (the manual way)
Mail for a domain is routed by its MX records, so look them up first. This tells you whether the domain takes mail at all, and which provider hosts it (RFC 5321, September 2026).
dig +short MX acme.comNo MX record usually means no working inboxes at that domain. Stop there. If it returns records, the domain can receive mail, but you still have no name and no address.
The manual route from here is slow. Find a person on the company site or a profile, guess a pattern, then verify the guess.
Step 2: Guess the pattern and know what a guess is worth
A common pattern is first name and last name joined by a dot. This script prints the usual candidates for one placeholder name.
first="jane"; last="doe"; domain="acme.com"
for p in "$first.$last" "$first" "${first:0:1}$last" "$first$last" "${first:0:1}.$last"; do
echo "$p@$domain"
doneEvery line is a guess. A domain that accepts all mail returns the same answer for a real and a fake address, so a guess can look fine and still bounce. LeadOcean calls those domains catch_all, and marks a confirmed mailbox on one as catch_all_valid.
Use this route for one or two people. For anything more, use the API.
Step 3: Size the audience at the domain for free
Ask how many people we hold at the domain before you pull a record. A search with count=true as a query parameter and limit at 1 is a free sizing call. Send the domain as an array.
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": ["acme.com"],
"jobLevel": ["C-Team", "VP"],
"emailStatus": ["verified", "catch_all_valid", "catch_all"],
"limit": 1
}' | jq '.meta | {total, totalIsExact, credits}'The count tells you if the domain is worth a search. A zero means we hold nobody at that level there, so stop and save the records.
Step 4: Search by domain and title
There is no domain-only email lookup in the API, because an address belongs to a person. Search the domain with a title keyword, and keep limit small. Each person returned costs one record.
curl -s "https://api.leadocean.io/v1/people/search" \
-H "x-api-key: $LEADOCEAN_API_KEY" -H "content-type: application/json" \
-d '{
"domain": ["acme.com"],
"title": ["Head of Sales", "VP Sales"],
"limit": 3
}' | jq '{count: .meta.count, credits: .meta.credits,
people: [.data[] | {name: .profile_data.full_name, person_id: .meta.source_ids.person_id}]}'A search row is thin: name, headline, current role, plus flags such as has_email and email_status. It carries no address. Each row has a person_id at meta.source_ids.person_id.
To walk a whole company instead, use GET /v1/companies/acme.com/people. It pages with a cursor and stops at 10,000 rows.
Step 5: Get the work email for one person
Send the person_id to the work-email endpoint. It returns one address, or null. It returns only an address at the person's current employer that is verified, catch_all_valid or catch_all.
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'This costs one record per call, including a call that finds nothing. A null means no address qualifies, not that the person has no email. If you want every email and phone we hold in one go, call POST /v1/people/enrich with reveal_email set to true instead. It also costs one record.
Step 6: Do a whole list of domains with an export
For more than a handful of people, export instead of looping. Bulk CSV export needs a paid plan: Solo at $97 a month, or Agency at $497 with 50,000 rows a file. An export writes one CSV with numbered columns such as email_1_address and email_1_status. A request takes up to 50,000 rows and a day takes up to 500,000.
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 three domains",
"filters": {
"domain": ["acme.com", "example.com", "example.org"],
"jobLevel": ["C-Team", "VP"],
"emailStatus": ["verified", "catch_all_valid", "catch_all"]
},
"limit": 100,
"preset": "full"
}' | jq '.data | {id, status, requested, estimatedTotal}'The export reserves up to your limit when it starts, and each row written costs one record. The full preset reveals emails, so the key needs the enrich scope. Poll GET /v1/exports/{id}, then fetch GET /v1/exports/{id}/download when the status is done.
The app does the same on its Exports page, with a filter builder, a column picker and the record price before you start. For a longer list, read how to find emails from a list of domains.
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 acme.com, our placeholder domain.
{
"total": 9,
"totalIsExact": true,
"totalAll": 198,
"distinctCompanies": 2,
"credits": 0,
"unsupportedFilters": []
}That is 9 people at the default mailable filter (verified, catch_all_valid or catch_all). The same filters with no email condition match 198 people (totalAll). Quote the mailable figure when you plan sends, and say which one you used.
The work-email lookup returns the shape below. This is the response shape from the API reference, with placeholder values.
{
"success": true,
"data": {
"email": "jane.doe@acme.com",
"status": "verified",
"verified_batch_date": "2026-09-20"
}
}A verified_batch_date means we ran the check. A null there means the check came from a source we hold to the same standard, not that the address is unverified. LeadOcean gives no refund or credit-back for bounced emails, so read status before you send.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing, wrong or revoked key | Re-copy the key from app.leadocean.io and export $LEADOCEAN_API_KEY again. |
| 429 | Above your plan's rate: 5 requests a second on Free, 50 on Solo, 200 on Agency, per account | Wait the Retry-After seconds, then slow the loop, or use an export for large batches. |
| 402 | The 1,000 free records are spent | Check what is left with GET /v1/account, or move to Solo ($97 a month) or Agency ($497 a month) on pricing. Free records do not reset. |
| Search returns no rows | A filter value outside the catalogue, such as a job level of "VP Sales" | Check exact strings with GET /v1/enums or leadocean_list_enum_values. Job level is VP, not a free-text title. |
data: null on the work email | No address at the current employer is verified, catch_all_valid or catch_all | Costs one record. Try POST /v1/people/enrich with reveal_email, or pick another person. |
400 export_limit_rows | The export limit is above 50,000 rows | Split the audience by domain group or title and run several exports. |
FAQ
Can I find an email from a domain alone?
Not as one address. A domain has many people. Search the domain with a title or job level, then look up the work email for the person you pick.
How many records does it cost?
One per person returned by a search and one per work-email call. Counts are free. A search at limit 3 plus one lookup spends about 4 records.
How do I know the email is real?
Each address carries a status. The work-email endpoint returns only verified, catch_all_valid or catch_all. Catch-all domains accept any address, so only catch_all_valid confirms the mailbox exists.
Can I start from a company name instead?
Yes, but a domain is cleaner. The search takes companyName as keywords, which can match more than one company. If you hold a person's name and company, read how to find an email from name and company.
Where else can I find domains to start from?
A startup directory works well. See how to find emails from a YC company list. More recipes are in the blog hub, and plans are on pricing.
Turn any company domain into a checked work email, free
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →