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

How-to guide

How to Access Employee Data Through an API (with curl)

Seven steps take you from a company domain to a verified work email. Real queries, the response shape, and the errors you will hit.

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

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}/people lists employees at one company. POST /v1/people/search finds 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=true costs 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.
  • curl and jq.
bash
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.

bash
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".

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

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 '{
    "title": ["Head of Platform", "VP Engineering"],
    "country": ["US"],
    "employeeRange": ["201-500"],
    "limit": 5
  }' > page1.json

jq '.meta | {count, nextCursor}' page1.json

Wrap 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.

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

bash
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:

text
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 haveCallCosts
A company domain, want all its peopleGET /v1/companies/{domain}/people1 record per person
A role and a market, no companyPOST /v1/people/search1 record per person
One person's LinkedIn URL, email or phonePOST /v1/people/enrich1 record
An audience sizesearch with count=true and limit=1Free

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:

json
{
  "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:

json
{
  "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=true in the body. It is a query parameter. The JSON body rejects unknown fields.
  • Guessing enum values. jobLevel takes C-Team, VP, Director, Manager, Staff or Other. Seniority bands are lowercase, such as c_suite and vp.
  • Expecting emails in search rows. Search publishes has_email and has_phone flags only. Enrich the people you want.
  • Pulling first and sizing later. Run the free count, read the total, then set limit.

Troubleshooting

ErrorCauseFix
401Missing or wrong x-api-key headerRe-export $LEADOCEAN_API_KEY and check the header name. Retrying will not help
402Out of records. On Free the 1,000 are a one-off allowanceMove to Solo ($97) or Agency ($497) on pricing, or lower limit
429Above the plan's rate limit: 5 requests a second on Free, 50 on Solo, 200 on AgencyHonour the Retry-After header and back off
Empty dataA filter value is not an exact enum stringList the allowed values with GET /v1/enums/job_level and copy one
400 "Invalid cursor"The cursor was edited, stored or built by handRestart the query and pass meta.nextCursor back unchanged
400 export_limit_rows or 429 export_limit_dailyOver 50,000 rows a request, or over 500,000 rows a daySplit 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?

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 →