Data accuracy matters most because a bad address does damage you cannot undo. A bounce hurts your sender reputation, and no later fix gives it back. Everything else about a list (coverage, freshness, filters) can be improved after the fact.
This guide shows the cheap way to enforce accuracy: filter on email_status, count the result for free, then verify the rows you pull.
Coverage is a volume problem. Accuracy is a reputation problem. You can always buy more rows, but you cannot buy back a domain that mailbox providers have learned to distrust.
Three things go wrong when you skip the filter:
- Hard bounces from
invalidanddisposableaddresses push your bounce rate up, and inbox providers score you on it. spam_trapandabuseaddresses are worse than a bounce. They mark you as a sender who does not clean lists.roleaddresses such as a shared inbox rarely reach a person, so they burn volume for no reply.
Competitors cover this at length (Leadmagic's take, checked September 2026). The part they skip is the query. The rest of this page is the query.
TL;DR
- Accuracy is enforced by one filter (
emailStatus) and one field (email_status), not by a clean-up pass after the send. - It takes about 15 minutes and about 25 LeadOcean records. Counts are free.
- Nothing is refunded for a bounce, so read
email_statusbefore you send.
Prerequisites
- A LeadOcean API key in
$LEADOCEAN_API_KEY. The Free plan (1,000 records for life, no card, 5 requests a second) is enough. curlandjq.- Optional: the hosted MCP server at
https://api.leadocean.io/mcpif you would rather run the counts from Claude.
Step 1: Know what the statuses mean
Every email carries a status, and the default for "mailable" is not the same as "safe". Our count tool counts verified, catch_all_valid and catch_all unless you say otherwise.
The full list of 13 values: verified, catch_all_valid, catch_all, risky, unknown, untested, invalid, role, disposable, spam_trap, abuse, derived, none.
Pull the live list instead of trusting a blog post:
curl -s https://api.leadocean.io/v1/enums/email_status \
-H "x-api-key: $LEADOCEAN_API_KEY"My rule: send to verified first. Treat catch_all as a second tier at a lower daily volume, because a catch-all domain accepts every address and tells you nothing about the mailbox.
Step 2: Count how much accuracy costs you, for free
The accurate slice is smaller than the mailable slice. Measure the gap before you commit to a list. A count spends no records.
curl -s "https://api.leadocean.io/v1/people/search?count=true&limit=1" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-X POST -d '{
"jobLevel": ["VP"],
"jobFunction": ["Sales & Business Development"],
"country": ["CA"],
"emailStatus": ["verified"]
}' | jq '.meta | {total, totalIsExact, distinctCompanies}'With the MCP server connected, ask Claude for the same thing. It calls leadocean_count_leads:
Count VPs in Sales & Business Development in Canada. Give me the total
with no email filter, with the default mailable filter, and with
emailStatus verified only.Step 3: Pull only the accurate rows
Once the count looks right, search with the same body. The search body is flat JSON, filters are arrays, and limit runs 1 to 100. 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 '{
"jobLevel": ["VP"],
"jobFunction": ["Sales & Business Development"],
"country": ["CA"],
"emailStatus": ["verified"],
"limit": 20
}' | jq '.meta.nextCursor'Page with cursor. One search pages to 10,000 rows. If meta.depthCapped is true, narrow the filters instead of walking further.
Step 4: Verify the rows before you send
A list goes stale between the pull and the send. Enrich a sample by person_id and read the status again. Enrichment costs one record, and a person we hold nothing for is a 404 that costs nothing.
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": "1234567890", "reveal_email": true}' \
| jq '.data.contact_data | {email_status, email_type, contact_emails}'Gate your sender on the result. A minimal rule keeps only verified work addresses:
jq -r 'select(.email_status=="verified" and .email_type=="work") | .contact_emails[0].email'A search count of verified is an upper bound. Enrichment applies the stricter per-address rule, so a row can come back untested when enriched, and the enriched answer wins.
The dataset is refreshed monthly and each record carries its own fetched_at date. Skip rows whose date is older than your tolerance.
Step 5: Tier the list and export it
Do not treat all mailable rows the same. Build two tiers from the same filter and send them at different paces.
| Tier | emailStatus | How to send |
|---|---|---|
| 1 | ["verified"] | Full daily volume |
| 2 | ["catch_all_valid", "catch_all"] | Small batches, watch bounces |
| Leave out | risky, unknown, untested, invalid, role, disposable, spam_trap, abuse, derived, none | Not in this list. Never send to invalid, spam_trap, abuse or disposable |
Export tier 1 with one request. Exports take up to 50,000 rows a request and 500,000 a day, and bulk CSV export is not on Free (Solo and above). Check the record price first on the Exports page in the app, which shows it before you start.
curl -s -X POST https://api.leadocean.io/v1/exports \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "VP Sales CA, verified",
"limit": 1854,
"filters": {
"jobLevel": ["VP"],
"jobFunction": ["Sales & Business Development"],
"country": ["CA"],
"emailStatus": ["verified"]
}
}'The filters go inside filters, and limit is required. Poll GET /v1/exports/{id}, then download from GET /v1/exports/{id}/download.
Run the tier 2 export separately and load it into a different campaign. When bounce rates on tier 2 climb, you can stop it without touching tier 1.
What you get
Live result from leadocean_count_leads, run 21 October 2026 for VPs in Sales & Business Development, country CA. Counts only, no records spent.
| Filter | Total |
|---|---|
No email condition (totalAll) | 18,800 |
Default mailable (verified, catch_all_valid, catch_all) | 5,574 |
emailStatus: ["verified"] only | 1,854 |
Only 1,854 of 18,800 people are verified. That is the honest size of the list you can send to with confidence. The 5,574 figure is mailable, not verified.
Response shape from the API reference (placeholder values):
{
"success": true,
"data": {
"contact_data": {
"has_email": true,
"email_status": "verified",
"email_type": "work",
"contact_emails": [{ "email": "jane.doe@acme.com", "type": "work", "status": "verified" }]
}
},
"meta": { "credits": 1, "source": "own" }
}Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing or wrong x-api-key header | Check $LEADOCEAN_API_KEY is exported and the header name is exact |
| 429 | Above the plan's rate limit (5 a second on Free, 200 on Agency) | Honour Retry-After and slow the loop |
| 402 | The 1,000 free records are spent | Move to Solo ($97 a month) or Agency ($497 a month) |
| Empty results | A bad enum value, such as "VP Sales" for jobLevel | Resolve it with GET /v1/enums or leadocean_list_enum_values; levels are C-Team, VP, Director, Manager, Staff, Other |
| 404 on enrich | Nobody matched the person_id | Free; pick another row |
| Export refused (400 or 429) | Over 50,000 rows an export or 500,000 a day | Split the filter into smaller exports |
FAQ
Is a verified email the same as a deliverable email?
No. On a business address, verified means our own verifier sent to it and it accepted. It can still go dead later, which is why Step 4 re-reads the status before you send.
Should I send to catch-all addresses?
Only as a second tier. A catch-all domain accepts any address, so the check cannot tell you the mailbox exists. Send in small batches and watch the bounce rate.
Do bounced emails get refunded?
No. LeadOcean does not refund or credit back bounced emails. Read email_status first, which is the point of this guide.
How often is the data refreshed?
The dataset is refreshed monthly, and each record carries its own fetched_at date. See why legacy data providers fail for what stale data costs and why AI enrichment matters for revenue teams.
Does a waterfall improve accuracy?
It improves coverage. It does not make a bad address good. See what waterfall enrichment is and always re-check status at the last hop. Plans are on pricing. More guides are in the blog.
Send to people who can receive it
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →