A domain scan answers one question: which email addresses exist at this company? Most free tools return a few generic inboxes such as info@ and sales@. LeadOcean does not guess those. It returns the named people it holds at a domain, and it labels role addresses with email_status set to role.
TL;DR
- The scan is four calls: a free count, a roster, a role filter and one reveal. It takes about 5 minutes.
- The count is free. Each person returned counts one record. With
limit5 the scan spends up to 11 of the 1,000 Free records. - Role addresses (info@, support@) are labelled, not guessed. Named work emails come back with a status you can filter on.
Prerequisites
- A LeadOcean key from app.leadocean.io. Free needs no card.
- The key in your shell:
export LEADOCEAN_API_KEY=your_key_here curlandjq. Any HTTP client works.- A domain. This guide uses
acme.comas a placeholder. Swap in yours.
Free allows 5 requests a second. Run the steps one at a time and you will never hit it.
Step 1: Count the domain for free
Size the domain before you spend anything. Send count=true as a query parameter, not in the JSON body, and set limit=1. The body rejects unknown fields.
curl -s -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 '{"domain": ["acme.com"]}' | jq '.meta'The data array comes back empty and meta.countOnly is true. meta.total is how many people we hold at that domain. A count spends no records. If total is 0, stop here: there is nothing to reveal and nothing was billed.
Step 2: List the people at the domain
The roster endpoint pages through everyone we hold at a domain. Each person returned counts one record, so keep limit small while you look.
curl -s "https://api.leadocean.io/v1/companies/acme.com/people?limit=5" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
| jq '.meta, (.data | length)'These are thin records. They carry no contact points, which is why the next steps exist. Page with the meta.nextCursor value, passed back unchanged. A roster stops at 10,000 rows, and meta.depthCapped is true on the last page. Almost no company is that large.
Step 3: Filter for role addresses
Role addresses are the shared inboxes: info@, support@, sales@. LeadOcean marks them with emailStatus set to role. Combine that with domain to see which people at the domain have a role address as their best email.
curl -s -X POST "https://api.leadocean.io/v1/people/search" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"domain": ["acme.com"],
"emailStatus": ["role"],
"limit": 5
}' | jq '.data[] | {person_id: .meta.source_ids.person_id, status: .contact_data.email_status, type: .contact_data.email_type}'Filter values are exact strings, and role is one of 13 email_status values. List them free with GET /v1/enums/email_status.
Role mail is a weaker target than a named person. It often reaches a shared inbox nobody owns. The glossary entry on wildcard email domains covers why a domain that accepts any address makes this worse.
To keep only mailable people instead, swap the filter for the statuses that matter:
{
"domain": ["acme.com"],
"emailStatus": ["verified", "catch_all_valid"],
"emailType": ["work"],
"limit": 5
}Passing emailStatus replaces the mailable default, so you now get exactly the statuses you named.
Step 4: Reveal the address
Search and the roster return no contact points. Enrich one person by person_id with reveal_email set to true. Enrich takes exactly one of linkedin_url, email, phone or person_id. There is no name plus domain lookup, which is why steps 2 and 3 come first.
curl -s -X POST "https://api.leadocean.io/v1/people/enrich" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"person_id": "PERSON_ID_FROM_STEP_3", "reveal_email": true}' \
| jq '.data.contact_data.contact_emails'A person we hold nothing for is a 404 and costs nothing. A hit costs one record, even if search already returned that person. Revealing the email adds no extra charge on top.
What you get
The free role count, run on 21 October 2026 through the LeadOcean MCP tool leadocean_count_leads with emailStatus set to role and no other filter:
{
"total": 332561,
"totalIsExact": true,
"distinctCompanies": 226653,
"credits": 0
}That is people whose best email is a role address, not a mailable count. The top countries were US (90,923), NL (15,941) and IT (13,346). Role addresses are a thin slice of the 366M people with email, so do not plan a campaign on them.
The reveal call returns this shape, from the API reference (openapi.json, September 2026). The values are placeholders, not a real record.
{
"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": { "credits": 1 }
}verified_batch_date is the verification run, not a timestamp for the address. A null means LeadOcean's verifier did not run the check itself; it does not mean unverified.
How it compares
Icypeas lists domain search at 1 credit if emails are found, with 10 calls a second on that endpoint (Icypeas docs, September 2026). Both limits are on its own pages: credit costs and rate limits.
The difference is what comes back. A generic domain scan returns whichever inboxes it finds. LeadOcean returns people first, with a status on each address, and spends nothing until you ask for the address. If you want only info@ guesses, a free scanner does that job. If you want a named buyer with a verified address, use the steps above.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing or wrong x-api-key header | Check echo $LEADOCEAN_API_KEY and that the header is x-api-key, not Authorization. |
| 429 | Over the plan's rate limit: 5 a second on Free, 200 on Agency | Wait for the Retry-After seconds. Run steps in sequence, not in parallel. |
| 402 | The 1,000 records on Free are spent | The call costs nothing. Move to a paid plan on pricing or stop. |
Empty data on a real domain | A wrong enum string, such as Role for role, or total is 0 | Check GET /v1/enums/email_status. Values are exact and case-sensitive. |
400 on count | count=true sent in the JSON body | Move it to the query string and keep limit=1. |
meta.depthCapped true | One search paged past 10,000 rows | Narrow the filters and run smaller searches. Paging harder returns no cursor. |
FAQ
Does LeadOcean return info@ and contact@ for any domain?
No. It returns the people it holds, and labels a role address with email_status role when that is a person's best email. It does not generate generic inboxes by pattern.
How many records does one domain scan spend?
Counting is free. A roster or search call spends one record per person returned, and a reveal spends one per hit. With limit set to 5, the whole scan spends at most 11 records.
Can I run this on the Free plan?
Yes. Free includes search and enrich on both scopes, inside the 1,000 one-off records. Bulk CSV export is not on Free.
How do I scan many domains at once?
Pass several values in the domain array on the search call. For a list over a few hundred rows, use POST /v1/exports (up to 50,000 rows a request) on a paid plan.
Can I do this from Claude instead of curl?
Yes. The free MCP tool leadocean_count_leads takes the same domain and emailStatus filters. See the free email domain prompt for a worked prompt, and email finders for Instantly for sending the result on.
Scan your first domain, free
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →