LeadOcean verifies the addresses it holds and publishes the result as email_status. It does not take an upload of your CSV and run SMTP checks on it. This guide shows the three ways to use that status on candidates: size a pool, check one person you already have, and fetch a verified work email by LinkedIn URL.
Candidate lists bounce for a specific reason. People move jobs, and the address in your ATS is often a former employer's. That is the failure to catch.
TL;DR
- Time: about 25 minutes. You read statuses, size a candidate pool for free, spot-check five candidates and write a send rule.
- Records: about 10 of your 1,000 free records. Counts and enum lookups cost nothing. Each enrich call or work-email lookup is one record.
- Limit to know: LeadOcean does not verify personal (webmail) addresses. Treat
verifiedon a personal address as a lead, not a result.
Prerequisites
- A LeadOcean account from app.leadocean.io. The free plan has 1,000 records, one-off, with both search and enrich scopes.
- A key in your shell as
$LEADOCEAN_API_KEY. curlandjq.- Five candidates you plan to email: an address for each, and a LinkedIn URL if you have one.
export LEADOCEAN_API_KEY="paste-your-key-here"Every request goes to https://api.leadocean.io with the header x-api-key. Never paste a key into a script you share.
Step 1: Learn the 13 status values
email_status is the deliverability of a person's best address. Read the list before you filter on it, because the values are exact strings.
curl -s "https://api.leadocean.io/v1/enums/email_status" \
-H "x-api-key: $LEADOCEAN_API_KEY" | jq '.values'Group them into three decisions:
- Send:
verifiedandcatch_all_valid. On a business addressverifiedmeans LeadOcean's own verifier sent to it and it accepted.catch_all_validis a mailbox LeadOcean confirmed on a catch-all domain. - Hold:
catch_all,untested,unknown,risky,role,derived. The address might work. Nothing here proves it. - Drop:
invalid,disposable,spam_trap,abuse,none.
Also read email_type. A work address is at the person's current employer. A work_other address is at a different company, usually a former one, so it is the likeliest to bounce on a candidate.
Step 2: Size a candidate pool for free
Count how many people match a role and a status before you pull any rows. A search with count=true and limit=1 spends no records.
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 '{
"title": ["Data Engineer"],
"country": ["US"],
"emailType": ["work"],
"emailStatus": ["verified"],
"limit": 1
}' | jq '.meta | {total, totalIsExact, countOnly}'Note that count=true goes in the query string, not the JSON body. The body rejects unknown fields.
The same count from an MCP client such as Claude is one free tool call:
Use leadocean_count_leads with title ["Data Engineer"], country ["US"],
emailType ["work"], emailStatus ["verified"], facets false.
Tell me the total and say which filters you applied.A count of verified from search is an upper bound. Search carries no per-address verification record, so a person can show verified in search and untested when enriched. Step 3 is how you find out.
Pool size on 2026-10-21 for that call: 7,011 people (exact), spanning 2,806 employers. It spent 0 records. That is a count of verified work emails in search, not a mailable count and not a guarantee of deliverability.
Step 3: Check a candidate you already hold
Enrich by email to see what LeadOcean holds for that person. It costs one record. A person LeadOcean holds nothing for is a 404 and 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 '{"email": "jane.doe@acme.com", "reveal_email": true}' \
| jq '.data.contact_data | {email_status, email_type, emails: .contact_emails}'Send exactly one of linkedin_url, email, phone or person_id. If you have a LinkedIn URL, prefer it: it finds the person even when the address you hold is stale.
Compare contact_emails[].email with the address in your ATS. If they differ, your address is probably old. Use the one LeadOcean returns, and read its status.
Step 4: Fetch a verified work email by LinkedIn URL
When Step 3 shows untested or a former employer, ask for the current work email directly. This door returns one address or null.
curl -s -G "https://api.leadocean.io/v2/people/email/work" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
--data-urlencode "linkedin_url=https://www.linkedin.com/in/jane-doe-example" \
| jq '.data'It only returns addresses at the current employer's domain whose status is verified, catch_all_valid or catch_all. null means none qualify, not that the person has no email.
A call costs one record even when it finds nothing. Run it only on the candidates Step 3 did not clear.
Step 5: Write the send rule
Turn the status into a decision in code, so nobody on the team guesses. This reads one enrich response and prints send, hold or drop.
decide() {
jq -r '.data.contact_data
| if .email_status == "verified" and .email_type == "work" then "send"
elif (.email_status | IN("invalid","disposable","spam_trap","abuse","none")) then "drop"
else "hold" end'
}
curl -s -X POST "https://api.leadocean.io/v1/people/enrich" \
-H "x-api-key: $LEADOCEAN_API_KEY" -H "Content-Type: application/json" \
-d '{"linkedin_url": "https://www.linkedin.com/in/jane-doe-example", "reveal_email": true}' \
| decideHold means you send nothing yet. Run Step 4, or reach the person on LinkedIn instead. LeadOcean does not refund or credit back bounced emails, so read the status before you send.
On Solo ($97 a month) and above, export a whole pool with the same filters and the status already applied. Bulk CSV export is not on Free.
curl -s -X POST "https://api.leadocean.io/v1/exports" \
-H "x-api-key: $LEADOCEAN_API_KEY" -H "Content-Type: application/json" \
-d '{"filters": {"title": ["Data Engineer"], "country": ["US"],
"emailType": ["work"], "emailStatus": ["verified"]}, "limit": 100}'An export reserves up to limit records and bills each row written, so start with 100.
What you get
The status values, from the free leadocean_list_enum_values tool, run on 2026-10-21:
name: email_status total: 13
verified Deliverable
catch_all_valid Catch-all, mailbox confirmed
catch_all Catch-all domain
risky Risky
unknown Unknown
untested Not yet checked
invalid Undeliverable
role Role address
disposable Disposable address
spam_trap Spam trap
abuse Abuse / complainer
derived Derived, not verified
none No emailThe enrich response from Step 3, as a response shape from the API reference with placeholder values. This is not a real person.
{
"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",
"verified_batch_date": "2026-09-20"
}
]
}
},
"meta": { "source": "leadocean", "credits": 1 }
}verified_batch_date is the date of the verification run. A date means LeadOcean tested the address itself. null means the check was not ours, which is not the same as unverified.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | The x-api-key header is missing or wrong. | Send x-api-key: $LEADOCEAN_API_KEY. |
| 429 | Above your plan's rate limit per account (5 a second on Free, 50 on Solo, 200 on Agency). | Wait for Retry-After, slow the loop and retry. |
| 402 | You are out of records. On Free the 1,000 are one-off. | Lower limit, or move to Solo at $97 a month (Agency at $497 for teams). |
Zero rows or total of 0 | A filter value is not an exact enum string, such as Germany for DE. | Read valid values from GET /v1/enums/{name}. |
| 400 on export create | limit is over 50,000 (export_limit_rows) or a filter cannot be applied. | Split into smaller exports. Above 500,000 rows a day the API answers 429. |
| 404 on enrich | LeadOcean holds nothing for that identifier. | No charge. Try the LinkedIn URL instead of the email. |
FAQ
Can LeadOcean verify a personal Gmail address?
No. LeadOcean does not verify consumer domains. On a personal address, verified means a supplier asserted it. The personal-email endpoint, GET /v2/people/email/personal, publishes no status for that reason.
Why does search say verified and enrich say untested?
Search has no per-address verification record, so its verified count is an upper bound. Enrichment applies the narrow rule per address. The enriched answer is the correct one.
What should I send to a catch-all candidate address?
Hold it. A catch-all domain accepts every address, so no verifier can confirm one mailbox there. Fetch the work email in Step 4, or use another channel. See how to verify email addresses for the mechanics.
How does this compare with a bulk verifier?
A bulk verifier checks any address you upload. LeadOcean checks the addresses it holds, and adds the current employer and the person behind them. For a pure upload-and-clean job, see the best email verification tools and bulk verification for cold lists. Pricing is on the pricing page.
Spot-check your next candidate batch before it bounces
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →