You get a small pipeline. A LeadOcean export is split by email_1_status, then created as leads in two lemlist campaigns. Verified emails go to your main campaign. Catch-all emails go to a second campaign with a lower daily limit. LeadOcean's free plan has 1,000 records for a first run.
Related: the integrations hub, Smartlead, Salesloft, Reply.io and pricing.
How it works
Five steps. lemlist's API creates one lead per request, so the loop lives in a script (source: lemlist Create Lead in Campaign, September 2026).
- Get both keys. In the LeadOcean app, copy a key from API keys. In lemlist, open Settings, then Integrations, then Generate a new API key. lemlist shows it once.
- Size the list. Call search with
count=trueandlimit=1. It is free. - Export. POST your filters to
/v1/exportswithemailStatusset toverified,catch_all_validandcatch_all. You can build the same export on the Exports page of the app, which shows the record price before you start. - Create two campaigns in lemlist. One for verified, one for catch-all. Copy each campaign id from the URL.
- Run the script. It reads the CSV and posts each row to the campaign that matches its email status.
Why split: a catch-all domain accepts every address at the SMTP level, so a bounce can hide until later. Send those at lower volume and watch bounces. LeadOcean returns 13 email statuses, and risky, unknown, invalid, role, disposable and spam_trap stay out of this recipe.
Configuration
Step 3, the export. Several values in one filter join with OR.
curl -X POST https://api.leadocean.io/v1/exports \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"lemlist VP sales CA","filters":{"jobLevel":["VP"],"jobFunction":["Sales & Business Development"],"country":["CA"],"emailStatus":["verified","catch_all_valid","catch_all"],"emailType":["work"]},"limit":10000,"columns":["person_id","profile_first_name","profile_last_name","current_job_title","current_company_name","current_company_domain","profile_url","email_N_address","email_N_status"]}'
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 people.csv https://api.leadocean.io/v1/exports/EXPORT_ID/downloadStep 5, the push. lemlist uses HTTP Basic auth with an empty username and your key as the password. Its limit is 20 requests per 2 seconds per key (source: lemlist authentication and rate limits, September 2026). The script is ours. It sleeps 0.15 seconds between calls and honors Retry-After.
import csv, os, time, requests
AUTH = ("", os.environ["LEMLIST_API_KEY"])
CAMPAIGNS = {
"verified": os.environ["LEMLIST_CAMPAIGN_VERIFIED"],
"catch_all_valid": os.environ["LEMLIST_CAMPAIGN_CATCHALL"],
"catch_all": os.environ["LEMLIST_CAMPAIGN_CATCHALL"],
}
for r in csv.DictReader(open("people.csv")):
camp = CAMPAIGNS.get(r["email_1_status"])
if not camp:
continue
body = {
"email": r["email_1_address"],
"firstName": r["profile_first_name"],
"lastName": r["profile_last_name"],
"jobTitle": r["current_job_title"],
"companyName": r["current_company_name"],
"companyDomain": r["current_company_domain"],
"linkedinUrl": r["profile_url"],
"emailStatus": r["email_1_status"],
}
url = f"https://api.lemlist.com/api/campaigns/{camp}/leads/?deduplicate=true"
resp = requests.post(url, auth=AUTH, json=body)
if resp.status_code == 429:
time.sleep(int(resp.headers.get("Retry-After", 2)))
resp = requests.post(url, auth=AUTH, json=body)
if resp.status_code >= 400:
print(r["email_1_address"], resp.status_code, resp.text[:120])
time.sleep(0.15)deduplicate=true refuses a contact that already has a lead in another campaign (409 LEAD_ALREADY_IN_OTHER_CAMPAIGN). A repeat in the same campaign returns 400 LEAD_ALREADY_IN_CAMPAIGN. Neither writes anything. The script prints refusals so you can review them.
Field mapping
lemlist's lead body takes email, firstName, lastName, companyName, jobTitle, linkedinUrl, phone and companyDomain. Any other key is stored as a custom variable you can use as {{emailStatus}} in a template (source: lemlist Create Lead, September 2026).
| LeadOcean field | lemlist field | Note |
|---|---|---|
email_1_address | email | Best address first. |
email_1_status | custom emailStatus | Also picks the campaign. Not a built-in field. |
profile_first_name | firstName | Empty if the profile has no split name. |
profile_last_name | lastName | |
current_job_title | jobTitle | |
current_company_name | companyName | Reaches the shared company record in lemlist. |
current_company_domain | companyDomain | Good dedupe key. |
profile_url | linkedinUrl | |
phone_1_number | phone | Add to columns if you want it. Phones need Solo or above for bulk export. |
person_id | custom leadoceanId | Add to columns to re-match later. |
Column ids come from GET /v1/exports/columns, which needs no key. Career history is there too. Add past_1_job_title and past_1_company_name to columns, and send them as custom variables if a template mentions a past employer. lemlist stores each extra key you send as a custom variable.
Cost at 10,000 rows
LeadOcean covers 10,000 rows on Solo at $97 a month. lemlist's Email plan is $69 a month, $55 billed yearly, with 50,000 emails a month (source: lemlist pricing, October 2026). That is $166 a month in total, or $152 with lemlist billed yearly. The sum is ours.
| Item | Free LeadOcean | Paid LeadOcean |
|---|---|---|
| LeadOcean | 1,000 records, one-off, no card | Solo $97 a month. Agency $497 a month for teams and 50k rows a file |
| Rows you can export | Up to 1,000 through search and enrich. Bulk CSV export is not on Free | 10,000 in one export on either plan (one export holds up to 50,000 rows) |
| lemlist Email | $69 a month, $55 billed yearly | Same |
| lemlist Multichannel | $109 a month, $87 billed yearly | Same |
| lemlist lead-data add-on | Not needed | Not needed |
lemlist sells its own lead data as credits. Its add-on is $20 a month ($16 billed yearly) for 2,000 credits, which it says is 400 emails or 100 phones (lemlist pricing, October 2026). You skip that here because the email comes from LeadOcean. LeadOcean has no credits or overage invoice: paid plans run on a fair-use pace. lemlist's pricing page lists "CRM integrations & API" in the Email plan, so the Email plan is enough for this recipe.
A 10,000-row run is one export and about 10,000 lemlist calls. The lemlist side is paced by its rate limit, so run it in the background and keep the printed refusals as your review list.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| 401 from lemlist | Bearer header or a key without the leading colon | Use Basic auth with an empty username. In curl: -u ":$LEMLIST_API_KEY". |
| 429 from lemlist | More than 20 requests in 2 seconds on one key | Sleep between calls and read Retry-After. At 0.15 seconds a row, 10,000 rows take about 25 minutes. |
400 LEAD_ALREADY_IN_CAMPAIGN | The email is already a lead in that campaign | Skip it. It is a repeat, not an error. |
409 LEAD_ALREADY_IN_OTHER_CAMPAIGN | deduplicate=true and the contact sits in another campaign | Drop the parameter if you want the overlap, or remove the lead first. |
| 403 "permission to create contacts" | The key's user cannot create contacts or companies | Give that user the permission in lemlist, or use another key. |
Empty email_1_address column | The key lacks the enrich scope, or the row has no email | Use a key with both scopes. Free keys get both inside the 1,000 records. |
| Bounces from the catch-all campaign | Catch-all domains accept mail they later reject | Lower the daily limit on that campaign. LeadOcean does not refund bounces, so read email_status first. |
FAQ
Can I send both statuses to one campaign?
Yes, but you lose the volume control. Catch-all domains bounce later and more often, and one campaign has one daily limit. Two campaigns keep the verified list clean.
Does lemlist find emails for me?
lemlist sells email and phone lookup as credits. Its create-lead call has findEmail, verifyEmail and findPhone flags, all off by default. With LeadOcean you already hold the address and its status, so leave them off.
Can I use an MCP client instead of a script?
LeadOcean has a hosted MCP server with 13 tools, including count_leads and export_leads. lemlist documents its own MCP server for Claude, ChatGPT, Cursor and others (lemlist MCP setup, October 2026). We did not test them together, so the script stays the reliable path for 10,000 rows.
How do I size the list before I spend records?
Use the MCP count_leads tool or a search with count=true and limit=1. Both are free. A count with an emailStatus filter is a count by that status, not a promise of mailable leads.
Fill lemlist with leads you can send to
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →