Filter on email_status before you export. The CSV you upload then holds only addresses you will send to. The three tools import differently, but the pre-send check is the same.
Related: how to verify email addresses, the best email finder for Smartlead, the best email finder for Lemlist and pricing.
TL;DR
- Every LeadOcean person record carries an
email_status. Filter toverifiedand export, so bad addresses never reach your sending domains. - Takes about 20 minutes. The test run here spends 100 records. Counting and sizing the list spends none.
- The free plan has 1,000 records for life, no card, so you can run this guide as written.
Prerequisites
- A LeadOcean API key from app.leadocean.io, exported as
$LEADOCEAN_API_KEY. A key on the free plan has the search and enrich scopes. curland Python 3.- An account in the sequencer you use: Instantly, Smartlead or Lemlist. Any of the three takes a CSV.
Bulk CSV export is not on the free plan. On Free, run the Step 2 filters through POST /v1/people/search with limit 100, then enrich each person_id with reveal_email: true to get the address. Search rows carry the status but not the address, so that route spends a record per row and per enrichment. Or move to Solo at $97 a month.
Step 1: Size the list for free
Count first. A count call costs 0 records and tells you how many verified people match before you spend anything. Send count=true as a query parameter, not in the JSON body, with limit 1.
curl -X POST "https://api.leadocean.io/v1/people/search?count=true&limit=1" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"jobLevel":["VP"],"country":["US"],"emailStatus":["verified"]}'data comes back empty and meta.countOnly is true. meta.total is capped at 100,000 on this call, so for a broad query use the MCP tool leadocean_count_leads instead. It gives an exact total and a breakdown. Enum values are exact strings: VP, not vp. List them with leadocean_list_enum_values.
Step 2: Export only the statuses you will send to
Choose your cutoff once. verified is the strict list. Add catch_all_valid if you accept domains that accept all mail but passed a further check. Leave catch_all, risky, unknown and untested out of the first send.
curl -X POST https://api.leadocean.io/v1/exports \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "sequencer upload",
"filters": {
"jobLevel": ["VP"],
"country": ["US"],
"emailStatus": ["verified"]
},
"limit": 100,
"columns": ["person_id","profile_full_name","current_company_domain","email_N_address","email_N_status"]
}'The answer is a 202 with a job id. An export spends one record per row. A request takes up to 50,000 rows and a day takes up to 500,000.
Which status to send to
Each status is a different bet. The values below are the ones LeadOcean returns for email_status.
| Status | Send in the first campaign? | Why |
|---|---|---|
verified | Yes | The strongest signal LeadOcean gives. |
catch_all_valid | Your call | The domain accepts all mail, but the address passed a further check. |
catch_all | No, test separately | The domain accepts everything, so nobody can be certain. |
risky, unknown, untested | No | Too little evidence. Check them elsewhere first. |
role, disposable, spam_trap, abuse, invalid | Never | Role inboxes, throwaway domains and known traps hurt your sending domain. |
The default for a LeadOcean count is verified, catch_all_valid and catch_all. Set emailStatus yourself so your count matches your cutoff. A count with an emailStatus filter is a count of people at those statuses. It is not a promise of delivery.
Step 3: Poll the job and download the CSV
curl -H "x-api-key: $LEADOCEAN_API_KEY" https://api.leadocean.io/v1/exports/EXPORT_ID
curl -H "x-api-key: $LEADOCEAN_API_KEY" -o leads.csv \
https://api.leadocean.io/v1/exports/EXPORT_ID/downloadPoll the first call until data.status is done. The status call is free. The CSV headers come back as email_1_address and email_1_status, even though you asked for email_N_*.
Step 4: Add a last gate before the upload
The filter already did the work. This script is a belt-and-braces check on the file: it drops blank addresses and anything that is not verified, and writes one CSV for the sequencer.
import csv
ok = {"verified"} # add "catch_all_valid" if you accept it
rows, dropped = [], 0
with open("leads.csv", newline="") as f:
for r in csv.DictReader(f):
if r["email_1_address"] and r["email_1_status"] in ok:
rows.append({
"email": r["email_1_address"],
"full_name": r["profile_full_name"],
"company_domain": r["current_company_domain"],
"person_id": r["person_id"],
})
else:
dropped += 1
with open("sequencer_upload.csv", "w", newline="") as f:
w = csv.DictWriter(f, fieldnames=["email","full_name","company_domain","person_id"])
w.writeheader()
w.writerows(rows)
print(f"kept {len(rows)}, dropped {dropped}")Keep person_id. It lets you suppress or re-check one person later without searching again.
Step 5: Upload to your sequencer
Upload sequencer_upload.csv through the CSV lead import of the tool you use. Map the email column to the tool's email field. Map the rest to custom fields, or skip them. Import screens differ by tool and plan, so follow each tool's own import docs rather than a fixed menu path.
LeadOcean does not send email and does not warm inboxes. It supplies the list. The sequencer owns sending.
Step 6: Re-check the catch-alls (optional)
Catch-all domains accept any address, so no checker can be certain about them. If you want to send to them anyway, export the catch_all rows separately and run them through a dedicated verifier first. LeadOcean + VerifyHQ shows that route with a script. Send the catch-alls from a separate sub-campaign so a bounce spike is easy to trace.
Run this check before every upload
The recipe repeats well. Keep the same five habits each time you build a list.
- Count first, with your exact
emailStatuscutoff. Write the cutoff down so a teammate can repeat it. - Export with
person_idin the columns. It is your join key for suppression lists and re-checks. - Read
email_1_statuson the file you receive, not just the filter you sent. The script in Step 4 does this in one pass. - Start small. Send 100 to 200 rows first and read the bounce rate in your sequencer before the full list goes out.
- Re-export lists older than a month. The dataset is refreshed monthly, so an old CSV drifts.
Keep catch-alls and verified rows in separate campaigns. When bounces rise, you will know which batch caused it.
What you get
This is a free count run through the MCP tool leadocean_count_leads on 2026-10-21. It costs 0 records.
{
"filters": { "jobLevel": ["VP"], "country": ["US"], "emailStatus": ["verified"] },
"total": 257840,
"totalIsExact": true,
"distinctCompanies": 83722,
"distinctCompaniesIsExact": false,
"credits": 0
}That is the count of US VPs whose email status is verified. It is not a count of people who will answer. The export in Step 2 returns a CSV like this. It is the response shape from the API reference, with placeholder values.
person_id,profile_full_name,current_company_domain,email_1_address,email_1_status
1234567890,Jane Doe,acme.com,jane.doe@acme.com,verifiedTroubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing or wrong x-api-key header | Re-paste the key from the app. The header name is x-api-key, not Authorization. |
| 402 | The free plan's 1,000 records are spent | Move to a paid plan, or lower limit on the next run. |
| 429 | Requests above your plan's rate limit (5 a second on Free, 200 on Agency) | Slow the loop and honor the Retry-After header. |
| Empty results | A wrong enum value, such as vp for job level | Fetch the exact strings with leadocean_list_enum_values and pass VP. |
| Export refused | Over 50,000 rows in one request, or 500,000 in a day | Split the job with narrower filters and run it across days. |
| Count looks too low | emailStatus set to verified only | Add the statuses you accept, such as catch_all, and send those rows as a separate test batch. |
The limits are listed on the LeadOcean limits docs, checked September 2026.
FAQ
Do Instantly, Smartlead and Lemlist verify emails for me?
This guide does not cover what each tool checks on import. Their features and plan limits change, so read each vendor's own docs. The filter here works whichever tool you use, because the list is clean before it arrives.
Does verified mean the email will not bounce?
No. verified is the best status LeadOcean gives, not a guarantee. A bounced email is not refunded or credited back, so read email_status before every send and watch your own bounce rate.
How fresh are the statuses?
The dataset is refreshed monthly, and each record carries its own fetched_at date. For a list older than a month, re-export instead of reusing the file.
Can I do this from Claude instead of curl?
Yes. The hosted MCP server at https://api.leadocean.io/mcp signs in with OAuth and has 13 tools. leadocean_count_leads sizes the list for free, and leadocean_export_leads runs the export.
What do I pay for this?
The free plan covers a 1,000-record test. Solo is $97 a month for one person and Agency is $497 a month for teams. Paid plans have a fair-use pace, not credits. See pricing.
Count your verified list first, then send to it
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →