Accessing employee data through an API takes three calls. List the people at a company by domain, search people by title, then enrich one person for contact details. This guide runs all three against LeadOcean with curl and spends 11 records.
Examples use placeholders (Acme, acme.com, Jane Doe). Swap in your own domain and filters.
TL;DR
- Two read paths.
GET /v1/companies/{domain}/peoplelists employees at one company.POST /v1/people/searchfinds people across companies by filter. - Roster and search rows are thin and carry no contact points. Enrich one person to get emails and phones.
- Takes about 15 minutes and spends 11 records of the 1,000 free ones. Sizing a search with
count=truecosts nothing.
Prerequisites
- A LeadOcean account. The free plan gives 1,000 records one-off, no card: register here.
- An API key from the app, exported as
$LEADOCEAN_API_KEY. curlandjq.
export LEADOCEAN_API_KEY="paste-your-key-here"Every request goes to https://api.leadocean.io with the header x-api-key. A free key has both scopes, search and enrich.
Step 1: Size the list before you pull it
Send count=true and limit=1 as query parameters and the call returns a total without spending a record. The filters go in a flat JSON body, and every filter is an array of strings.
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 '{
"jobLevel": ["VP"],
"jobFunction": ["Engineering"],
"country": ["US"]
}' | jq '.meta'meta.total is capped at 100,000. If meta.totalIsExact is false, the total is a floor.
Enum values are exact strings. VP works, vice president returns nothing. List the allowed values with GET /v1/enums/job_level.
Step 2: List the employees at one company
GET /v1/companies/{domain}/people returns the people we hold at a company, paged. It is the direct answer to "who works at acme.com".
curl -s "https://api.leadocean.io/v1/companies/acme.com/people?limit=5" \
-H "x-api-key: $LEADOCEAN_API_KEY" | jq '{count: .meta.count, next: .meta.nextCursor}'This spends 5 records, one per person returned. Pass the string in meta.nextCursor back as cursor for the next page, unchanged. The roster stops at 10,000 rows, and a company that large is rare.
Step 3: Search across companies by title
Use search when you know the role, not the company. Filters combine with AND, and several values inside one array combine with OR.
curl -s -X POST "https://api.leadocean.io/v1/people/search" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": ["Head of Platform", "VP Engineering"],
"country": ["US"],
"employeeRange": ["201-500"],
"limit": 5
}' > page1.json
jq '.meta | {count, nextCursor}' page1.jsonWrap a title in square brackets, such as ["[CTO]"], for an exact match. limit runs from 1 to 100 and defaults to 25. To page, add "cursor" with the value from meta.nextCursor to the next body.
One search pages through 10,000 rows by default. When meta.depthCapped is true, narrow the filters and run several smaller searches.
Step 4: Enrich one person for contact details
Each search row carries an id at meta.source_ids.person_id. Send it back to enrich that exact person. Enrich takes exactly one of linkedin_url, email, phone or person_id.
PID=$(jq -r '.data[0].meta.source_ids.person_id' page1.json)
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\": \"$PID\", \"reveal_email\": true}"This costs one record. reveal_email is false by default and costs nothing extra: it returns every email and phone we hold for that person. If we hold nothing, you get a 404 and no charge.
There is no lookup by name plus company domain. Search by domain first, then enrich by person_id.
Step 5: Page through every result
Loop on meta.nextCursor until it is null. This script walks a roster and stops at a record budget you set, so a typo cannot drain the free plan.
CURSOR=""; SPENT=0; BUDGET=50
while [ "$SPENT" -lt "$BUDGET" ]; do
URL="https://api.leadocean.io/v1/companies/acme.com/people?limit=25"
[ -n "$CURSOR" ] && URL="$URL&cursor=$CURSOR"
RES=$(curl -s "$URL" -H "x-api-key: $LEADOCEAN_API_KEY")
echo "$RES" | jq -c '.data[]' >> acme-people.jsonl
SPENT=$((SPENT + $(echo "$RES" | jq '.data | length')))
CURSOR=$(echo "$RES" | jq -r '.meta.nextCursor // empty')
[ -z "$CURSOR" ] && break
done
echo "rows: $SPENT"Do not store a cursor between runs. A cursor is a position in one walk. Restart the query instead.
Step 6: Move to bulk when you pass a few thousand rows
Past a few thousand rows, stop paging and start an export. Bulk CSV export is not on the free plan. It is included on Solo and Agency. POST /v1/exports takes up to 50,000 rows a request and 500,000 a day, and returns a CSV. Poll GET /v1/exports/{id} and download from GET /v1/exports/{id}/download when it finishes.
The app has an Exports page with a filter builder, a column picker and the record price before you start. It needs no code. The API route suits a scheduled job.
Step 7: Skip the code and ask an agent
The same calls exist as tools on the hosted MCP server at https://api.leadocean.io/mcp. You sign in with OAuth, so there is no key to paste. Thirteen tools are exposed, and seven are free, including leadocean_count_leads, leadocean_count_companies, leadocean_list_filters and leadocean_list_enum_values.
A prompt like this one runs a count first, shows you the total, and only then spends records:
Count VPs of Engineering in the US at companies with 201-500 employees.
If the total is under 2,000, pull the first 5 people and tell me how many
records that spent. Do not enrich anyone yet.Free tools never spend records, however many times the agent calls them. Search, get_lead, find_phone and export tools spend one record per person or row.
Which endpoint to use
Pick by what you already know. The right call is usually obvious from the input.
| You have | Call | Costs |
|---|---|---|
| A company domain, want all its people | GET /v1/companies/{domain}/people | 1 record per person |
| A role and a market, no company | POST /v1/people/search | 1 record per person |
| One person's LinkedIn URL, email or phone | POST /v1/people/enrich | 1 record |
| An audience size | search with count=true and limit=1 | Free |
What this costs at scale
The 11 records here come out of the 1,000 free ones. Every person or company returned counts one record, and a 404 from enrich costs nothing. A v2 contact lookup costs one record even when it finds nothing.
Paid plans have no credits. Solo is $97 a month for one person. Agency is $497 a month for teams, with 5 API keys and 200 requests a second. Each paid plan has a fair-use pace: reaching it slows the key until the window resets, and nothing stops. There is no per-record price and no overage invoice. See pricing.
What you get
The Step 1 count is free. The free MCP tool leadocean_count_leads ran these filters on 2026-10-21, plus an employee-size filter. It counts mailable people by default, meaning a verified, catch_all_valid or catch_all email. The result, trimmed:
{
"query": "jobLevel VP, jobFunction Engineering, country US, employeeRange 201-500",
"total": 2106,
"totalIsExact": true,
"totalAll": 4930,
"distinctCompanies": 1446,
"defaults": "Counted only addresses worth sending to (emailStatus: verified or catch_all_valid or catch_all)",
"source": "leadocean"
}That is 2,106 mailable VPs of Engineering in the US at companies of 201 to 500 people, on 2026-10-21, spread across 1,446 employers. Without the email condition the same filters match 4,930 people (totalAll).
A record-level response has the shape below (response shape from the API reference, placeholder values). Contact points appear only after enrich with reveal_email:
{
"success": true,
"data": {
"profile_data": { "name": "Jane Doe", "title": "VP Engineering", "company": "Acme" },
"contact_data": {
"has_email": true,
"has_phone": true,
"email_status": "verified",
"contact_emails": [
{ "email": "jane.doe@acme.com", "type": "work", "status": "verified" }
],
"contact_phones": [
{ "phone": "+14155550133", "type": "mobile", "priority": 1 }
]
}
},
"meta": { "source": "leadocean" }
}Read email_status before you send anything. Each record also carries its own fetched_at date, and the dataset refreshes monthly, so check the date on anything you plan to act on. Bounced emails are not refunded.
Common mistakes
Most failed first runs come from four slips. Each one is cheap to avoid once you know it.
- Sending
count=truein the body. It is a query parameter. The JSON body rejects unknown fields. - Guessing enum values.
jobLeveltakesC-Team,VP,Director,Manager,StafforOther. Seniority bands are lowercase, such asc_suiteandvp. - Expecting emails in search rows. Search publishes
has_emailandhas_phoneflags only. Enrich the people you want. - Pulling first and sizing later. Run the free count, read the total, then set
limit.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing or wrong x-api-key header | Re-export $LEADOCEAN_API_KEY and check the header name. Retrying will not help |
| 402 | Out of records. On Free the 1,000 are a one-off allowance | Move to Solo ($97) or Agency ($497) on pricing, or lower limit |
| 429 | Above the plan's rate limit: 5 requests a second on Free, 50 on Solo, 200 on Agency | Honour the Retry-After header and back off |
Empty data | A filter value is not an exact enum string | List the allowed values with GET /v1/enums/job_level and copy one |
| 400 "Invalid cursor" | The cursor was edited, stored or built by hand | Restart the query and pass meta.nextCursor back unchanged |
400 export_limit_rows or 429 export_limit_daily | Over 50,000 rows a request, or over 500,000 rows a day | Split the export into several requests, or wait for the reset |
FAQ
How much does it cost to access employee data through an API?
On LeadOcean every person returned counts one record. The free plan gives 1,000 records one-off with no card. Paid plans start at Solo, $97 a month, with a fair-use pace instead of credits and no per-record price. See pricing.
Can I get employee emails from the company roster endpoint?
No. Roster and search rows are thin and carry no contact points. Enrich a person with reveal_email to get emails and phones.
Can I look up an employee by name and company domain?
Not in one call. Search by domain or companyName first, then enrich by person_id.
Can an AI agent do this without writing code?
Yes. The hosted MCP server at https://api.leadocean.io/mcp exposes the same search and enrich calls as tools. Sign in with OAuth, no key to paste.
Where do I go next?
Read what a B2B data API is, then compare options in best B2B data APIs and B2B data providers with an API.
Pull your first employee list with a free API key
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →