The reliable way to find a business email is to query a database that stores the address with a deliverability status. Guessing a pattern and hoping is the fallback, not the method.
Most guides on this topic list browser extensions and tell you to try first.last@company.com. This one is for people who want a repeatable request. You will size a list, find the people, and reveal one address each, with a status you can filter on.
Pick this route when you have a market ("US C-level leaders at 11-50 person companies") or a company domain, not a single name.
TL;DR
- The first run takes about 15 minutes with
curl. - It spends about 2 records per contact: 1 for the search row, 1 for the reveal. Counts, sizing and a 404 are free.
- Send only to
verified,catch_all_validorcatch_all. Checkemail_statusbefore every send.
Prerequisites
- A LeadOcean API key from app.leadocean.io. Free is 1,000 records, one-off, no card.
- The key exported as
$LEADOCEAN_API_KEY. curlandjq. Examples useacme.comand Jane Doe as placeholders.
export LEADOCEAN_API_KEY="paste-your-key-here"Step 1: Size the audience for free
Count before you spend. Send count=true and limit=1 as query parameters. Arrays go comma-separated.
curl -s -G https://api.leadocean.io/v1/people/search \
-H "x-api-key: $LEADOCEAN_API_KEY" \
--data-urlencode "jobLevel=C-Team" \
--data-urlencode "country=US" \
--data-urlencode "employeeRange=11-50" \
--data-urlencode "count=true" \
--data-urlencode "limit=1" | jq '.meta | {total, totalIsExact, countOnly}'REST meta.total stops at 100,000. A bigger market returns a floor with totalIsExact false. The MCP tool leadocean_count_leads is also free.
Step 2: Search the people
Search returns a thin row per person, not an address. Send a flat JSON body. Filters are arrays of strings, limit runs 1 to 100, and you page with cursor.
curl -s https://api.leadocean.io/v1/people/search \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jobLevel": ["C-Team"],
"country": ["US"],
"employeeRange": ["11-50"],
"emailStatus": ["verified", "catch_all_valid"],
"limit": 25
}' | jq '.data[0], .meta.nextCursor'Each person returned costs 1 record. Pass meta.nextCursor back as cursor for the next page. One search pages to 10,000 rows, then meta.depthCapped is true. Narrow the filters instead of walking further.
Start from a company instead? Use GET /v1/companies/{domain}/people, for example /v1/companies/acme.com/people.
Step 3: Reveal the address with enrich
Enrich takes exactly one of linkedin_url, email, phone or person_id. There is no name plus domain lookup, so take the person_id from the search row at meta.source_ids.person_id.
curl -s https://api.leadocean.io/v1/people/enrich \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"person_id": "PERSON_ID_FROM_STEP_2", "reveal_email": true}' \
| tee person.json | jq '.data.contact_data | {email_status, email_type, contact_emails}'It costs 1 record. Someone we hold nothing for is a 404 and costs nothing. Revealing adds every email and phone we hold at no extra charge.
Step 4: Read the status before you send
The address is only half the answer. email_status tells you whether to send. A verified address is safe to mail. A catch_all domain accepts everything, so the risk is higher. Filter the file you saved in Step 3; a second enrich call would spend another record.
jq -r '.data.contact_data
| select(.email_status | IN("verified","catch_all_valid","catch_all"))
| .contact_emails[0].email' person.jsonSkip risky, invalid, role, disposable and spam_trap. We do not refund bounced emails, so the filter is your protection.
Step 5: Do it from an AI agent
The hosted MCP server at https://api.leadocean.io/mcp signs in with OAuth, so you paste no key. It has 13 tools. Counting is free, so ask for the count first.
Count US C-Team people at companies with 11-50 employees using
leadocean_count_leads. Report the total and distinctCompanies, and say
which email filter the count used. Do not run a search yet.Step 6: Export a whole list
For hundreds of rows, export instead of paging. POST /v1/exports takes the same filters as people search, up to 50,000 rows a request and 500,000 a day. Bulk CSV export is not on Free.
curl -s https://api.leadocean.io/v1/exports \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "US C-Team 11-50",
"filters": {"jobLevel": ["C-Team"], "country": ["US"],
"employeeRange": ["11-50"],
"emailStatus": ["verified", "catch_all_valid"]},
"limit": 5000,
"preset": "full"
}'Poll GET /v1/exports/{id}, then GET /v1/exports/{id}/download. Every row costs 1 record. The Exports page in the app does the same with a filter builder and shows the price first.
What you get
Step 1 is free. We ran leadocean_count_leads on 2026-10-21 with job level C-Team, country US and headcount 11-50, on the default mailable filter (verified, catch_all_valid or catch_all).
| Query | Count | Exact |
|---|---|---|
| Mailable people (default email filter) | 732,580 | Yes |
| Same filters, no email condition | 2,808,968 | Yes |
The mailable people span about 339,596 employers (an estimate, not exact). Roughly a quarter of the people who match have an address worth sending to. Narrow by industry or title before you export.
A revealed person comes back in this shape. This is the response shape from the API reference, with placeholder values.
{
"success": true,
"data": {
"profile_data": { "...": "name, title, company" },
"contact_data": {
"has_email": true,
"email_status": "verified",
"email_type": "work",
"contact_emails": [
{ "email": "jane.doe@acme.com", "type": "work", "status": "verified" }
]
}
}
}Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | The x-api-key header is missing, or the key is wrong or revoked. | Copy a fresh key from the app and reload $LEADOCEAN_API_KEY. |
| 402 | Your records are spent. On Free the 1,000 are one-off. | Check GET /v1/account, or move to a paid plan. |
| 429 | Requests above your plan's rate limit: 5 a second on Free, 200 on Agency. | Wait the Retry-After seconds, then retry. |
| Empty results | A filter value is not an exact enum string, such as Chief for C-Team. | Call leadocean_list_enum_values and pass the exact string. |
| 404 on enrich | We hold nothing for that person. | Nothing was charged. Try another person_id. |
| 400 or 429 on an export | 400: over 50,000 rows in one export. 429: past 500,000 rows a day. | Split the job across requests and days, or narrow the filters. |
FAQ
Can I find a business email from a name and a company?
Not in one call. Enrich has no name plus domain lookup. Search by domain or GET /v1/companies/{domain}/people, then enrich by person_id.
Why does search not show the email?
Search returns a thin row with flags such as has_email. The address comes from enrich with reveal_email set, which costs 1 record.
How many business emails can I get on the free plan?
You have 1,000 records, one-off. A contact costs about 2: one from search and one from the reveal. That is roughly 500 contacts.
Are the addresses current?
The dataset is refreshed monthly, and each record carries its own fetched_at date. Read email_status before you send.
For a single company site, read how to find email addresses on a company website. For one senior role, see ways to find CEO email addresses, and for owners, how to find small business owner email. More recipes live in the blog hub. Plans are on pricing.
Size your list free, then reveal verified business emails
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →