TL;DR
- Verified email is a filter, not a separate product. Set
emailStatuson a search, then use one of six calls to count, find, enrich or export the addresses. - It takes about 20 minutes and spends about 21 LeadOcean records: 20 for a sample search and 1 for a single enrichment. The count costs 0.
- Pick the call by the job. Count to size, search to list, enrich to reveal, the two v2 lookups to fill gaps, export to move a file.
Prerequisites
- A LeadOcean account from app.leadocean.io. The free plan has no card and 1,000 records, one-off. Free runs at 5 requests a second.
- An API key exported as
$LEADOCEAN_API_KEY, pluscurlandjq. - A target audience written as filters. The example is VP-level sales leaders in Canada.
export LEADOCEAN_API_KEY="paste-your-key-here"Step 1: Count the verified audience (free)
Size the list before you spend anything. Send count=true as a query parameter with limit at 1. The body takes flat JSON, and every filter is 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 '{
"jobLevel": ["VP"],
"jobFunction": ["Sales & Business Development"],
"country": ["CA"],
"emailStatus": ["verified"],
"emailType": ["work"],
"limit": 1
}' | jq '.meta'This costs 0 records. meta.total is capped at 100,000, and meta.distinctCompanies tells you how many employers the people span. In an MCP client, leadocean_count_leads runs the same count.
Step 2: Search with an email status filter
emailStatus is the verification gate. verified is the strictest value. catch_all_valid means we confirmed the mailbox on a catch-all domain. catch_all means the domain accepts everything.
curl -s https://api.leadocean.io/v1/people/search \
-H "x-api-key: $LEADOCEAN_API_KEY" -H "content-type: application/json" \
-d '{
"jobLevel": ["VP"],
"jobFunction": ["Sales & Business Development"],
"country": ["CA"],
"emailStatus": ["verified", "catch_all_valid"],
"emailType": ["work"],
"limit": 20
}' > sample.json
jq -r '.data[] | [.meta.source_ids.person_id, .contact_data.email_status] | @csv' sample.jsonThis spends 20 records, one per person returned. Search rows carry email flags, not addresses. Page further with the cursor from meta.nextCursor. One search pages to 10,000 rows, then meta.depthCapped turns true.
Step 3: Enrich one person and reveal the email
Enrichment takes exactly one of linkedin_url, email, phone or person_id. Use a person_id from the sample. reveal_email returns the addresses and costs no extra record.
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 sample.json>", "reveal_email": true}' | jq .A person we hold nothing for is a 404 and costs nothing. Reveal only for rows you will contact.
Step 4: Fill gaps with the two v2 email lookups
Two single-purpose endpoints fill gaps. The work endpoint returns one address. The personal endpoint returns a list, with known-bad addresses withheld. Both take linkedin_url, contact_id or person_id.
curl -s "https://api.leadocean.io/v2/people/email/work?person_id=<person_id>" \
-H "x-api-key: $LEADOCEAN_API_KEY" | jq .
curl -s "https://api.leadocean.io/v2/people/email/personal?person_id=<person_id>" \
-H "x-api-key: $LEADOCEAN_API_KEY" | jq .The work endpoint returns an address at the person's current employer's domain, or data: null. Null means none that qualify, not no email. Each call costs one record, including a call that finds nothing.
Step 5: Export the verified list
Bulk CSV export is on Solo ($97 a month) and above, not on Free. An export takes up to 50,000 rows a request and 500,000 a day. Free users page the search with cursor instead.
curl -s https://api.leadocean.io/v1/exports \
-H "x-api-key: $LEADOCEAN_API_KEY" -H "content-type: application/json" \
-d '{
"name": "CA sales VPs, verified work email",
"filters": {
"jobLevel": ["VP"],
"jobFunction": ["Sales & Business Development"],
"country": ["CA"],
"emailStatus": ["verified"],
"emailType": ["work"]
},
"limit": 500,
"preset": "full"
}' | jq '.data'The export queues. Poll GET /v1/exports/{id}, then fetch /v1/exports/{id}/download. The CSV headers come back as email_1_address, email_1_status and so on. The app has the same export on its Exports page, with a price preview before you start.
Step 6: Check the status before you send
Read email_status on every row before it goes into a sequencer. We give no refund for bounced emails. LeadOcean has no sequencer, so the file goes into the tool you already use.
jq -r '.data[] | .contact_data.email_status' sample.json | sort | uniq -cDrop risky, unknown, role, disposable and spam_trap rows from the send list.
What you get
The count from Step 1, run with the free leadocean_count_leads tool on 2026-10-21. Filters: jobLevel VP, jobFunction Sales & Business Development, country CA, emailStatus verified, emailType work.
{
"total": 1602,
"totalIsExact": true,
"distinctCompanies": 1209,
"distinctCompaniesIsExact": true,
"credits": 0,
"unsupportedFilters": []
}That is the count of people with a verified work email, not everyone matching the other filters. A search row looks like this (response shape from the API reference, placeholder values):
{
"success": true,
"data": [
{
"meta": { "source_ids": { "person_id": "1234567890" } },
"profile_data": { "profile_full_name": "Jane Doe" },
"contact_data": { "has_email": true, "email_status": "verified", "email_type": "work" }
}
],
"meta": { "nextCursor": "..." }
}Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing or wrong x-api-key header | Check $LEADOCEAN_API_KEY is exported and sent as x-api-key. |
| 429 | Above the plan's rate limit (5 a second on Free, 200 on Agency) | Back off for the Retry-After seconds. |
| 402 | Out of records | Check GET /v1/usage. Move to a paid plan or narrow the pull. |
| Empty results | A filter value is not an exact string | Look it up with leadocean_list_enum_values. jobLevel is VP, not vp. |
data: null from a v2 lookup | No qualifying address held | Try enrich with reveal_email, or accept there is none. |
| 400 on export | More than 50,000 rows, or a filter the export cannot apply | Split into several exports and narrow the filters. |
FAQ
Is a verified email guaranteed to deliver?
No. verified means the mailbox was checked, not that it will never bounce. We give no refund for bounced emails, so read email_status first.
What is the difference between verified and catch_all_valid?
verified is a checked address. catch_all_valid is a mailbox we confirmed on a domain that accepts all mail. catch_all means the domain accepts all mail, so the single mailbox could not be confirmed.
Which call should I start with?
Start with the free count. Then run a 20-row search and read the statuses. Enrich or export only after the numbers look right.
How many records does this spend?
About 21 on the example: 20 for the search and 1 for the enrichment. Counts, 404s and empty pages cost nothing. See pricing.
Where can I read more on enrichment tools?
Get verified emails from one API call
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →