Pre-Black Friday: 20% off, for as long as you stayEARLYBFon Solo and AgencyEnds Nov 28Start free

How-to guide

How to verify emails for free (with the LeadOcean API)

A free email verifier checks whether an address can receive mail. Here is the shortest path with real queries and the statuses you get back.

Get your free API key →Free to start. No credit card. 1,000 records to spend whenever you like.

TL;DR

  • LeadOcean is not a paste-a-list verifier. It checks addresses in its own database and returns an email_status on every person record.
  • It takes about 15 minutes and spends 1 to 3 records. Counts, filter lookups and enum lookups cost 0 records.
  • The Free plan gives you 1,000 records for life, no card, so the whole recipe is free.

A free email verifier tells you whether an address will bounce before you send. A single-address checker is fine for one address and slow for a list.

We take a different route. Every person we hold carries a verification verdict, so you can filter a whole audience to the safe statuses before you pull a single row. If you only need to test a stranger's address you hold nowhere else, a standalone verifier is the right tool. If you are building a list to send to, filtering by status beats verifying afterwards.

Prerequisites

  • A LeadOcean account on the Free plan: start free.
  • An API key from the app, exported as $LEADOCEAN_API_KEY.
  • curl and jq, or any HTTP client.
  • Optional: the MCP server at https://api.leadocean.io/mcp for the free count tools.
bash
export LEADOCEAN_API_KEY="paste-your-key-here"

Step 1: Learn the statuses

There are 13 email_status values, and the exact strings matter. Look them up before you filter.

bash
curl -s "https://api.leadocean.io/v1/enums/email_status" \
  -H "x-api-key: $LEADOCEAN_API_KEY" | jq '.data.values'

The safe set is verified and catch_all_valid. catch_all means the domain accepts everything, so the mailbox cannot be confirmed either way. Do not send to invalid, spam_trap, abuse or disposable. Use care with risky, role and derived.

One detail most guides skip. untested means nobody has checked the address yet. unknown means it was checked and the result was inconclusive. Treat untested as a maybe and unknown as a weaker signal than untested.

Step 2: Size a verified list for free

A count spends nothing. Send count=true as a query parameter and limit: 1 in the body. The JSON body rejects unknown fields, so keep count out of it.

bash
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 '{
    "jobLevel": ["VP"],
    "jobFunction": ["Sales & Business Development"],
    "country": ["CA"],
    "emailStatus": ["verified"],
    "limit": 1
  }' | jq '.meta | {total, countOnly}'

Filters are arrays, and enum values are exact strings. meta.total is capped at 100,000. Through MCP, the same question is the leadocean_count_leads tool.

Step 3: Pull only the safe addresses

Now fetch rows. Each person returned costs one record. Keep limit small while you test.

bash
curl -s -X POST "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", "catch_all_valid"],
    "limit": 3
  }' | jq '.data[] | {has_email: .contact_data.has_email, status: .contact_data.email_status}'

Page further with the cursor from the response. One search pages to 10,000 rows. Narrow the filters instead of walking the index.

Step 4: Confirm one address on enrichment

Search is an upper bound. A person can come back verified in a search row and untested when enriched, and the enriched answer is the correct one. Confirm before a high-stakes send.

bash
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.contact_emails'

Enrich takes exactly one of linkedin_url, email, phone or person_id. Send an address we hold nothing for and you get a 404 that costs nothing. A hit costs one record.

Step 5: Turn statuses into a send rule

Decide once what each status means, and code it. This filter keeps the safe set, parks the maybes and drops the rest.

bash
jq -r '.data[] | .contact_data.email_status as $s
  | if ($s == "verified" or $s == "catch_all_valid") then "send"
    elif ($s == "catch_all" or $s == "untested") then "test-batch"
    else "drop" end' response.json | sort | uniq -c

Send the first group. Mail the second in a small batch and watch the bounce rate before you scale. Drop the third.

Catch-all domains are the common trap. A server that accepts everything makes any single-address checker return "valid", then the message bounces later. We probe the domain itself, and for Microsoft 365 managed tenants and Google Workspace we confirm the mailbox through the provider's identity check. That is what catch_all_valid records.

Step 6: Do the same in an AI client

The hosted MCP server has 13 tools. Seven are free, and three of them cover this recipe: leadocean_count_leads, leadocean_list_filters and leadocean_list_enum_values. Sign-in is OAuth, so you paste no key.

text
Count VPs of Sales & Business Development in Canada whose email status is verified.
Use the free count tool only. Do not pull any rows.

The client calls leadocean_count_leads with emailStatus: ["verified"] and returns the total. By default that tool counts only verified, catch_all_valid and catch_all addresses, so say which filter you want and read the label on the number it returns.

What you get

Step 2, run through the free MCP count tool on 2026-10-21. The filters were jobLevel VP, jobFunction Sales & Business Development, country CA, emailStatus verified.

json
{
  "total": 1854,
  "totalIsExact": true,
  "distinctCompanies": 1392,
  "credits": 0,
  "unsupportedFilters": []
}

That is a count of people whose best email has the verified status, labelled by what we filtered. It is not a promise of zero bounces. A search count of verified is an upper bound, so confirm per address on enrichment.

Step 4 returns an email object per address. This is the response shape from the API reference, with placeholder values:

json
{
  "email": "jane.doe@acme.com",
  "type": "work",
  "status": "verified",
  "verified_batch_date": "2026-09-20"
}

verified_batch_date is the date of the verification run that produced the verdict. A null date means the check was not ours, not that the address is unverified. Read it as a signal, not a gap.

Troubleshooting

ErrorCauseFix
401Missing or wrong x-api-key headerRe-export $LEADOCEAN_API_KEY and send it as the x-api-key header.
429Above the plan's rate limit (5 requests a second on Free, 200 on Agency)Wait for the Retry-After value, then slow the loop.
402The 1,000 free records are spentUpgrade on pricing or keep sizing with free count calls.
Empty resultsA bad enum value, such as Verified instead of verifiedLook the exact string up with GET /v1/enums/email_status.
404 on enrichWe hold no record for that addressIt costs nothing. Try linkedin_url or search by domain first.
Export refusedBulk CSV export is not on FreeUse search paging on Free, or move to Solo for bulk export.

FAQ

Is there a free email verifier that handles a whole list?

Not for a list you upload. LeadOcean reads stored statuses instead. The Free plan covers 1,000 records for life, and sizing a list by status costs 0. For a list you already own, a standalone bulk verifier is the better fit.

Can I paste any address and get a verdict?

Not for an address we hold nothing for. Enrich returns a 404 and charges nothing. The route works for people in the database.

How is verified different from catch_all?

On a business address, verified means our own verifier sent to the mailbox and it accepted. On a personal address it means a supplier asserted it, because we do not verify consumer domains. catch_all means the domain accepts any address, so the mailbox cannot be confirmed. catch_all_valid is a catch-all mailbox whose existence we confirmed through the provider's identity check.

Does LeadOcean refund bounces?

No. There is no refund or credit-back for bounced emails. Read email_status before you send.

How often is the data refreshed?

The dataset is refreshed monthly. Each record also carries its own fetched_at date.

Keep going

Read email hygiene for how often to clean a list, email marketing databases for where lists come from, and cold email response rate for what a clean list is worth. Prices are on pricing.

Filter your next list to verified addresses before you send

Free to start. No credit card. 1,000 records to spend whenever you like.

Get your free API key →