A better outbound list comes from eight calls in a fixed order. Size the audience for free, search, pull the roster, enrich, add phones, then export. This guide runs all eight against the LeadOcean API with real queries.
TL;DR
- Two of the eight calls are free: a people count and a company count. Run them before you spend a record.
- The whole recipe takes about 40 minutes and spends about 130 records, well inside the 1,000 on the free plan.
- Every call below is a plain HTTP request to
https://api.leadocean.io, so a script or an MCP client can run it.
Prerequisites
- A LeadOcean account on the free plan (no card). The app is at app.leadocean.io.
- An API key in your shell:
export LEADOCEAN_API_KEY=.... Free keys carry both scopes, search and enrich. curlandjq. The MCP route needs only the server URLhttps://api.leadocean.io/mcpand an OAuth sign-in.- An ICP written as filters. The example here: Directors in sales, United States, companies of 51 to 200 people.
Step 1: Size the people list (free)
Send count=true as a query parameter with limit=1. It returns the total and spends nothing.
curl -s -G "https://api.leadocean.io/v1/people/search" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
--data-urlencode "count=true" \
--data-urlencode "limit=1" \
--data-urlencode "jobLevel=Director" \
--data-urlencode "jobFunction=Sales & Business Development" \
--data-urlencode "country=US" \
--data-urlencode "employeeRange=51-200" | jq '.meta'Read meta.total. It is capped at 100,000, so narrow the filters if you hit that. Enum values are exact strings: check them with GET /v1/enums or the leadocean_list_enum_values tool before you guess.
Step 2: Size the company list (free)
Count companies with the same ICP at the account level. GET /v1/companies/count is free and breaks the total down by industry, headcount band and HQ country.
curl -s -G "https://api.leadocean.io/v1/companies/count" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
--data-urlencode "employeeRange=51-200" \
--data-urlencode "hqCountry=US" \
--data-urlencode "hasMailableContacts=true" | jqIn an MCP client the same call is the leadocean_count_companies tool. Ask: "Count US companies with 51 to 200 employees that have a mailable contact."
Step 3: Pull the first page of people
Search returns one record per person. Ask for 25 first, not 100, to check the filters on a small page.
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": ["Director"],
"jobFunction": ["Sales & Business Development"],
"country": ["US"],
"employeeRange": ["51-200"],
"limit": 25
}' | jq '.meta'The body is flat JSON and filters are arrays. Page with meta.nextCursor, passed back unchanged. One search pages to 10,000 rows, then meta.depthCapped turns true and no cursor is issued.
Step 4: Pull the roster of a target account
When a company is already on your list, get its people directly. GET /v1/companies/{domain}/people returns thin records, one record per person.
curl -s "https://api.leadocean.io/v1/companies/acme.com/people" \
-H "x-api-key: $LEADOCEAN_API_KEY" | jq '.meta'Use it to find a second or third contact at an account where the first one bounced. Contact points are not in this response. Enrich a person for those.
Step 5: Enrich one person
Enrich takes exactly one of linkedin_url, email, phone or person_id. There is no name plus domain lookup, so search first, then enrich by person_id.
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_SEARCH", "reveal_email": true}'reveal_email returns the address at no extra record. A person we hold nothing for is a 404 and costs nothing.
Step 6: Read the career history
Person enrich returns resume_data since October 2026: past roles, education, certifications and awards, each when held. Use the past roles to write one true line in the opener.
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_SEARCH"}' \
| jq '.data.resume_data.experiences'Past employers carry no company_id yet, so match them by name, not by ID. The current role is in contact_current_experiences[].
Step 7: Add a phone number
Phones come from GET /v2/people/phone. It takes linkedin_url, contact_id or person_id and costs one record per call, even when it finds nothing. So filter first with hasPhone=true in the search.
curl -s -G "https://api.leadocean.io/v2/people/phone" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
--data-urlencode "person_id=PERSON_ID_FROM_SEARCH" | jq '.data'Numbers come best first. Each has a phone and a type, the source's own label (mobile, office and so on). Treat an unfamiliar label as unknown.
Step 8: Export the list
For more than a few hundred people, stop calling one by one and run a bulk export. POST /v1/exports takes up to 50,000 rows a request and 500,000 a day. Bulk CSV export is not on the free plan, so this step needs Solo ($97 a month) or higher.
curl -s -X POST "https://api.leadocean.io/v1/exports" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "content-type: application/json" \
-d '{
"name": "sales-directors-us-51-200",
"filters": {
"jobLevel": ["Director"],
"jobFunction": ["Sales & Business Development"],
"country": ["US"],
"employeeRange": ["51-200"],
"emailStatus": ["verified"]
},
"limit": 5000,
"preset": "full"
}'The export is async. Poll GET /v1/exports/{id}, then download from /v1/exports/{id}/download. Every row costs one record whatever the columns. The app does the same on its Exports page: filter builder, column picker, price preview, CSV download.
What you get
The free count for the ICP above, from the leadocean_count_leads tool on 2026-10-21. It counts mailable people by default (verified, catch_all_valid or catch_all):
{
"total": 36453,
"totalIsExact": true,
"totalAll": 119788,
"distinctCompanies": 20473
}total counts mailable people. totalAll is the same filters with no email condition. The gap between them is the people you would pay a record for and not be able to email.
A person-enrich record has this shape (response shape from the API reference, placeholder values). profile_data holds identity, headline and location; the reference leaves its fields untyped, so print one real response before you map them.
{
"success": true,
"data": {
"profile_data": { },
"contact_data": {
"has_email": true,
"email_status": "verified",
"email_type": "work",
"contact_emails": [{ "email": "jane.doe@acme.com", "type": "work", "status": "verified" }],
"contact_current_experiences": [ { } ]
},
"resume_data": {
"experiences": [{ "job_title": "Sales Manager", "company_name": "Acme", "start_date": "2019-01", "end_date": "2022-06" }],
"educations": []
},
"meta": { }
}
}The phone endpoint returns data with no meta: person_id, found, phones (best first, each { "phone": "+14155550133", "type": "mobile" }) and best.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing or wrong x-api-key header | Check $LEADOCEAN_API_KEY is exported and sent in the header x-api-key. |
| 429 | Above the plan's rate limit (5 requests a second on Free, 200 on Agency) | Honor Retry-After, or slow the loop. |
| 402 | Out of records (1,000 on Free) | Upgrade, or narrow the list. Counts and 404s never spend records. |
| Empty results | A bad enum value, such as director for jobLevel | Use the exact string Director. Check GET /v1/enums. |
400 export_limit_rows | More than 50,000 rows in one export | Split the filters into several exports. |
429 export_limit_daily | Past 500,000 export rows in a day | Wait for resetsAt in the response. |
FAQ
Which of the eight calls spend records?
Search, roster, enrich, phone and export. The two counts are free, and so is an enrichment that finds nothing. Phone calls cost one record even on a miss.
Why size the list before searching?
A count tells you if the ICP is 300 people or 300,000, before you spend a record. It also shows the gap between mailable and total people.
Can I run this from an AI client instead of curl?
Yes. The hosted MCP server at https://api.leadocean.io/mcp has 13 tools, including both counts. Sign in with OAuth, no key pasted.
Is a bounced email refunded?
No. Read email_status before sending, and filter to verified for the cleanest list. See pricing for what each plan includes.
Where do I go next?
Start with how to build a targeted prospect list. For tech filters see the tech stack version, and for dialing see the cold calling list. More guides are on the blog.
Build your outbound list with eight calls
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →