TL;DR
- A registrant list gives you the address they typed in. That is often a Gmail or a student account, so you need the work email at their current employer.
- It takes about 15 minutes for 10 people. The API route spends about 10 LeadOcean records: one per enrichment that finds the person. A miss costs nothing.
- For a long list, skip the loop and run one export. Read
email_statusin the CSV before you send.
Prerequisites
- A LeadOcean account from app.leadocean.io. The free plan has no card and gives 1,000 records one-off.
curl,jqandpython3.- An API key in your shell as
$LEADOCEAN_API_KEY. Free keys get both scopes, search and enrich. - Your registrant export from your webinar tool (name, email, company if the form asked). Use only people who registered for your own event.
export LEADOCEAN_API_KEY="paste-your-key-here"Step 1: Split the list into usable and unusable rows
Sort the CSV first. A row on a company domain may already be the work address, and enrichment tells you its status. A row with a free-mail address needs enrichment to find the employer, because the domain tells you nothing.
The script below splits a registrant file into two. The rows are placeholders.
cat > registrants.csv <<'EOF2'
first,last,email,company
Jane,Doe,jane.doe@gmail.com,Acme
John,Roe,john@example.org,Example
EOF2
python3 - <<'EOF2'
import csv
free = {"gmail.com","yahoo.com","outlook.com","hotmail.com","icloud.com","proton.me"}
work, personal = [], []
for r in csv.DictReader(open("registrants.csv")):
domain = r["email"].split("@")[-1].lower()
(personal if domain in free else work).append(r)
print(len(work), "on a company domain,", len(personal), "on a free-mail address")
EOF2Step 2: Guess the pattern by hand (one or two people)
For a registrant on a company domain you can guess the address pattern. This prints the usual candidates for a placeholder name.
first="jane"; last="doe"; domain="acme.com"
for p in "$first.$last" "$first" "${first:0:1}$last" "$first$last"; do
echo "$p@$domain"
doneEvery line is a guess. A domain that accepts all mail answers the same for a real and a fake address, so a guess can look fine and still bounce. Use this for one or two people, then move to the API.
Step 3: Enrich the address they gave you
POST /v1/people/enrich takes exactly one of linkedin_url, email, phone or person_id. Send the email the registrant typed, even if it is personal. The call returns the profile we hold for that address, with the current employer. With reveal_email true it also returns every address we hold.
curl -s https://api.leadocean.io/v1/people/enrich \
-H "x-api-key: $LEADOCEAN_API_KEY" -H "content-type: application/json" \
-d '{"email": "jane.doe@gmail.com", "reveal_email": true}' \
> jane.json
jq '.data | {name: .profile_data.profile_full_name,
linkedin: .profile_data.profile_url,
current: .contact_data.contact_current_experiences[0].company_domain}' jane.jsonA person we hold nothing for returns a 404 and costs nothing. A hit costs one record, the same with or without the reveal. Save the response, because the next step reads it.
Step 4: Pick the work email
The saved response already holds the addresses. Keep the ones of type work with a status of verified, catch_all_valid or catch_all. This step reads a file, so it costs nothing.
jq '[.data.contact_data.contact_emails[]?
| select(.type == "work")
| select(.status == "verified" or .status == "catch_all_valid" or .status == "catch_all")
| {email, status, verified_batch_date}]' jane.jsonAn empty array means no work address qualifies, not that the person has no email. For only the one best checked address, GET /v2/people/email/work takes the linkedin_url from Step 3, but it costs one more record, found or not. Loop Step 3 over your rows with a short sleep so you stay under your plan's rate limit.
Step 5: Size the lookalike audience for free
Webinars bring in people who did not register. Ask how many people we hold with your registrants' profile before you spend a record. Send count=true as a query parameter with limit at 1.
curl -s "https://api.leadocean.io/v1/people/search?count=true" \
-H "x-api-key: $LEADOCEAN_API_KEY" -H "content-type: application/json" \
-d '{
"jobFunction": ["Advertising & Marketing"],
"jobLevel": ["Director"],
"country": ["US"],
"limit": 1
}' | jq '.meta | {total, totalIsExact, credits}'Check enum values with GET /v1/enums before you guess. A wrong string returns nothing. The search body is flat JSON, filters are arrays, and limit runs 1 to 100.
Step 6: Export the matches in bulk
For a few hundred people, export instead of looping. Bulk CSV export is not on the Free plan: it comes with Solo ($97 a month) and Agency ($497 a month). One request takes up to 50,000 rows and a day takes up to 500,000. Each row costs one record.
curl -s https://api.leadocean.io/v1/exports \
-H "x-api-key: $LEADOCEAN_API_KEY" -H "content-type: application/json" \
-d '{
"name": "Webinar lookalikes",
"filters": {
"jobFunction": ["Advertising & Marketing"],
"jobLevel": ["Director"],
"country": ["US"],
"emailStatus": ["verified", "catch_all_valid", "catch_all"]
},
"limit": 100,
"preset": "full"
}' | jq '.data | {id, status}'Poll GET /v1/exports/{id}, then fetch GET /v1/exports/{id}/download when the status is done. The app does the same on its Exports page, with a filter builder, a column picker and the record price before you start.
Step 7: Check before you send
Columns are numbered: email_1_address, email_1_status. Keep rows where the status is verified or catch_all_valid.
python3 - <<'EOF2'
import csv
rows = list(csv.DictReader(open("export.csv")))
ok = [r for r in rows if r.get("email_1_status") in ("verified", "catch_all_valid")]
print(len(rows), "rows,", len(ok), "with a sendable status")
EOF2LeadOcean gives no refund or credit-back for bounced emails, so read the status first. catch_all means the domain accepts any address, so it is riskier than catch_all_valid.
What you get
The Step 5 sizing call is a real run. We ran it on 2026-10-21 with leadocean_count_leads, which spends 0 records, for Directors in Advertising & Marketing in the US.
{
"total": 245637,
"totalIsExact": true,
"totalAll": 683895,
"distinctCompanies": 106102,
"credits": 0,
"unsupportedFilters": []
}That is 245,637 mailable people on 2026-10-21 (verified, catch_all_valid or catch_all), out of 683,895 with no email filter. It is a mailable count, not everyone we hold.
Step 4 prints the shape below for one registrant. This is the response shape from the API reference, with placeholder values.
[
{
"email": "jane.doe@acme.com",
"status": "verified",
"verified_batch_date": "2026-09-20"
}
]Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing, wrong or revoked key | Re-copy the key from app.leadocean.io and export $LEADOCEAN_API_KEY again. |
| 429 | Above your plan's rate limit (5 a second on Free, 200 on Agency) | Slow the loop and honour the Retry-After header, or use an export. |
| 402 | The 1,000 free records are spent | Check GET /v1/account, or move to Solo ($97 a month) or Agency ($497 a month) on pricing. Free records do not reset. |
| Search returns no rows | A filter value outside the catalogue, such as a job level of "Director of Marketing" | Check exact strings with GET /v1/enums or leadocean_list_enum_values. |
| 404 on enrich | We hold nothing for that email | It costs nothing. Try the registrant's name and company through a search by domain. |
400 export_limit_rows | The export limit is above 50,000 rows | Split the audience by country or title and run several exports. |
FAQ
Can I email webinar registrants?
They gave you their address for the event. Check the registration terms and the rules where your recipient lives before you send follow-ups. Write about the webinar topic, not a cold pitch.
Why did the registrant use a personal email?
People avoid sales follow-up and sign up with a Gmail. Enrichment by that address finds the profile we hold, so you can look up the work address.
How many records does it cost?
One per enrichment hit. A 404 on enrich is free, and so are counts. Ten registrants spend about 10 at most. The optional work-email call in Step 4 adds one each.
What if the person changed jobs?
Enrichment returns the current employer we hold, and each record carries a fetched_at date. If the company does not match what the registrant told you, skip the row.
Can an AI agent run the lookups?
Yes. The hosted MCP server signs in with OAuth, so no key is pasted. Ask it to look up each registrant's address with leadocean_get_lead, which costs one record and nothing on a miss.
Where do I find more recipes?
Start from Twitter handles or an email signature. The find-work-email skill runs the lookup from Claude. Plans are on pricing.
Turn your webinar registrants into checked work emails, free
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →