Most deliverability damage is done before you write the first email: you send to addresses that were never checked. Fix the list and the rest gets easier.
This guide covers the data side only. Authentication, warm-up and copy are in email deliverability best practices for B2B and building for email deliverability.
TL;DR
- Filter by
emailStatusbefore you pull a single row. Send toverifiedfirst, treat catch-all as a second tier, never send toinvalid,spam_trap,abuseordisposable. - Sizing the list is free. Pulling 25 people costs 25 records. Total time: about 15 minutes.
- Read
statusandverified_batch_dateon every address, and re-check anything old before a big send.
Prerequisites
- A LeadOcean API key in
$LEADOCEAN_API_KEY(the Free plan works: 1,000 records for life, no card, 5 requests a second). curlandjq.- A sending domain with SPF, DKIM and DMARC already set. This guide does not cover them.
Step 1: Learn the email status values
Thirteen statuses exist, and each means a different send decision. Ask the API for the list instead of guessing, because enum values are exact strings.
curl -s "https://api.leadocean.io/v1/enums/email_status" \
-H "x-api-key: $LEADOCEAN_API_KEY"Use this table as the policy:
| Status | Send? |
|---|---|
| verified | Yes. Start here. |
| catch_all_valid | Yes. Mailbox confirmed on a catch-all domain. |
| catch_all | Second tier. Domain accepts everything, so bounces are hidden. |
| risky, unknown, untested | Not in a cold send. |
| invalid, disposable, spam_trap, abuse | Never. |
| role, derived, none | Skip for personal outreach. |
Step 2: Size the list for free
Counting spends no records. Run the same filters with two email conditions and compare.
curl -s -X POST "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"],
"limit": 1
}' | jq '.meta | {total, distinctCompanies}'meta.total is capped at 100,000. The MCP tool leadocean_count_leads does the same job from Claude or any MCP client, and meta.distinctCompanies tells you how many employers the list spans.
Step 3: Pull only the tier you will send
Search returns one record per person. Put the status filter in the body so unsendable rows never reach you.
curl -s -X POST "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"],
"limit": 25
}'Page with cursor. One search pages to 10,000 rows, then meta.depthCapped is true: narrow the filters instead.
For a file, POST /v1/exports takes the same filters and up to 50,000 rows a request (500,000 a day). Bulk CSV export is not on Free.
Step 4: Check the age of each verdict
Search rows carry only the email_status flag. The addresses, each with its own status and verified_batch_date, come from enrichment with reveal_email. Take the ID from meta.source_ids.person_id on a search row. Enrichment costs one record per person found.
curl -s -X POST "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_SEARCH","reveal_email":true}' \
| jq '.data.contact_data.contact_emails'The date is the verification run, shared by a whole batch, not a timestamp for one address. A null date means our own verifier did not run the check; the status came from a source held to the same standard. Search applies the status rule per person, enrichment per address, so a search verified can come back untested on enrich. Trust the enriched answer.
Re-verify anything you plan to send months after you pulled it. The dataset itself is refreshed monthly.
Step 5: Split the send by tier
Send verified and catch_all_valid first, at your normal pace. Put catch_all in a separate, smaller campaign and watch its bounce rate before scaling. A catch-all domain accepts every address at the door, so the bounce arrives later or never shows up as a bounce at all.
Keep each tier in its own campaign so one bad tier cannot drag down the domain reputation of the whole send.
What you get
Real output from the free leadocean_count_leads tool on 2026-10-21. Filters: jobLevel VP, jobFunction Sales & Business Development, country CA.
| Email filter | Matching people | Employers |
|---|---|---|
None (totalAll) | 18,800 | not reported |
| Default: verified, catch_all_valid or catch_all | 5,574 | 3,873 |
emailStatus: ["verified"] | 1,854 | 1,392 |
Only 1,854 of 18,800 (about 10%) are verified. The rest is the audience you would otherwise mail blind. These are counts of people matching the filters, not a promise of inbox placement.
A contact in the response, shape from the API reference, placeholder values:
{
"email": "jane.doe@acme.com",
"type": "work",
"status": "verified",
"verified_batch_date": "2026-09-20"
}Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing or wrong x-api-key. | Check $LEADOCEAN_API_KEY is exported and sent in the header. |
| 429 | Above the plan's rate limit (5 a second on Free, 200 on Agency). | Honor Retry-After, or slow the loop. |
| 402 | The 1,000 free records are spent. | Move to a paid plan; see pricing. |
Empty data | Bad enum value, such as Verified or VP Sales in jobLevel. | Values are exact strings. Look them up at /v1/enums/{name}. |
meta.depthCapped is true | One search pages to 10,000 rows. | Narrow the filters and run more searches. |
| Export refused | Over 50,000 rows a request or 500,000 a day. | Split the export by country or seniority. |
FAQ
Does a verified status guarantee no bounces?
No. It lowers the odds. Mailboxes get deleted, people change jobs and servers change rules. LeadOcean gives no refund or credit-back for bounced emails, so read status before sending.
Should I ever send to catch-all addresses?
Only as a separate, smaller tier. catch_all_valid means we confirmed the mailbox on a catch-all domain, so it is safer than plain catch_all.
How is a work email different from a personal one?
Use emailType to ask for a work address. Our verifier has not tested personal addresses, so verified on a personal address comes from a supplier. See the email deliverability benchmarks for context.
Does sizing a list cost records?
No. A count, a breakdown and an enrichment that finds nothing cost nothing. A search page of 25 is 25 records.
Send only to addresses worth sending to
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →