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

How-to guide

How to Find Someone's Work Phone or Direct-Dial Number (API)

Start from a company domain and a title, keep only people who hold a number, then look up each one. 1 record per number looked up.

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

You find a work phone in three calls. Search a company domain for people who hold a number, look up each person by id, then sort the numbers by type. The type tells you whether you got a desk line, a mobile or a switchboard. This guide runs all three against the LeadOcean API.

A company's website lists the switchboard. A switchboard reaches a gatekeeper. A direct dial or a mobile reaches the person. Clay's guide to the same task makes the same point and chains several providers to cover it (Clay, October 2026). Here you use one source and read the type on each number.

TL;DR

  • Search by domain and title with hasPhone set to true, then call GET /v2/people/phone on each person_id.
  • It takes about 10 minutes. A 5-person test spends 10 records: 5 for the search rows and 5 for the phone lookups.
  • The Free plan covers it: 1,000 records for life, no card, 5 requests a second.

Prerequisites

  • A LeadOcean API key from app.leadocean.io, exported as $LEADOCEAN_API_KEY. The key needs the search and enrich scopes. Free keys get both.
  • curl and jq.
  • A target: one company domain and a title. This guide uses acme.com and "Director" as placeholders. Swap in your own.

Step 1: Count who has a number, for free

Count before you spend. A count costs no records, so you can tune the filters first.

Send count=true as a query parameter with limit=1. The JSON body rejects unknown fields, so the flag stays in the URL.

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 '{
    "domain": ["acme.com"],
    "title": ["Director"],
    "hasPhone": true
  }' | jq '.meta | {total, countOnly}'

hasPhone: true keeps only people with a number on file. meta.total is capped at 100,000 on this route. In an MCP client, the leadocean_count_leads tool returns an exact total with breakdowns and also costs nothing.

Step 2: Search the company for people with a number

Run the same filters without the count flag. Every row you get has a number behind it, so no lookup in step 3 is wasted.

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 '{
    "domain": ["acme.com"],
    "title": ["Director"],
    "hasPhone": true,
    "limit": 5
  }' > people.json

jq -r '.data[].meta.source_ids.person_id' people.json > ids.txt
wc -l ids.txt

The body is flat JSON and every filter is an array of strings, except hasPhone, which is a boolean. limit runs from 1 to 100. Page with meta.nextCursor passed back as cursor.

Search rows carry no numbers. They carry has_phone and an id at meta.source_ids.person_id. Each row returned costs 1 record.

If you know the person already, skip this step. Send a linkedin_url to the phone endpoint instead.

Step 3: Look up each person's numbers

The phone endpoint returns every number held for the person, best first. Send one identifier: person_id, contact_id or linkedin_url.

bash
while read -r id; do
  curl -s -G "https://api.leadocean.io/v2/people/phone" \
    --data-urlencode "person_id=$id" \
    -H "x-api-key: $LEADOCEAN_API_KEY"
  echo
  sleep 0.25
done < ids.txt > phones.jsonl

The sleep keeps you under the Free limit of 5 requests a second. Each call costs 1 record, including a call that finds nothing. The endpoint returns data and no meta, so read GET /v1/usage to see what you spent.

Step 4: Sort desk lines from mobiles from switchboards

The type on each number is the source's own label. It is not a closed list. In a sample of 54 live numbers on 2026-09-21, the API reference counted mobile 41, unknown 10, office 2 and switchboard 1. Most "work" numbers you find will be mobiles.

bash
jq -c 'select(.data.found == true)
  | .data as $p
  | {person_id: $p.person_id,
     direct:  ([$p.phones[] | select(.type == "direct" or .type == "office")][0].phone),
     mobile:  ([$p.phones[] | select(.type == "mobile")][0].phone),
     skipped: [$p.phones[] | select(.type == "switchboard") | .phone]}' \
  phones.jsonl > sorted.jsonl

Call the direct or office number first for desk-bound roles. Call the mobile first for executives and field roles. Drop switchboards unless you have nothing else. Treat any type you do not recognise as unknown, not as an error.

One compliance note. The v2 endpoint does not publish a do-not-call flag. The older GET /v1/people/phone returns dnc on each number, but it is not populated yet, and null means unknown, never safe to call. Check your own do-not-call lists before you dial.

What you get

The response shape from the API reference, with placeholder values:

json
{
  "success": true,
  "data": {
    "person_id": "1234567",
    "found": true,
    "phones": [
      { "phone": "+14155550133", "type": "mobile" },
      { "phone": "+14155550134", "type": "office" }
    ],
    "best": { "phone": "+14155550133", "type": "mobile" }
  }
}

A person we hold nothing for returns found: false. For scale, we ran a free count in the leadocean_count_leads tool on 2026-10-21. It is not the step 1 query. It has no company or title filter, so it sizes the whole pool.

json
{
  "total": 8976341,
  "totalIsExact": true,
  "distinctCompanies": 1444945,
  "filters": "jobLevel Director, country US, hasPhone true"
}

That is a count of US directors with a number on file. It says nothing about how many numbers are mobile, direct or still dialable.

Troubleshooting

ErrorCauseFix
401The x-api-key header is missing or the key is wrong.Check that $LEADOCEAN_API_KEY is set in the shell that runs curl.
429You are above your plan's rate limit: 5 requests a second on Free, 200 on Agency.Wait for the Retry-After header. Add a sleep, or move to a plan with a higher limit.
402Your records are spent. On Free, the 1,000 are a one-off allowance.Check GET /v1/account, then see pricing.
Empty data arrayThe domain has no people with a number, or a filter value does not match the enum.Drop title and recount. Check enum values with GET /v1/enums.
400 on the phone callYou sent two identifiers, or none.Send one of linkedin_url, contact_id or person_id.
404 on the phone callWe hold no record for that person.Nothing to fix. A 404 costs nothing. Move to the next id.

FAQ

How is a direct dial different from a work phone?

A work phone can be the switchboard, a desk line or a mobile the person uses for work. A direct dial is a line that rings one person. The type on each number shows which you have.

How many records does a lookup cost?

One record per call to the phone endpoint, the same as an enrichment. A v2 call that finds no number still costs 1 record. The search before it costs 1 record per row.

Can I find a number from a name and company?

Not in one call. There is no name plus domain lookup. Search by domain and title, take the person_id from the row, then call the phone endpoint. See find a phone number for the endpoint reference.

Can I get numbers in bulk without a script?

Yes. The Exports page in the app has a filter builder, a column picker with phone columns, a record price before you start and a CSV download. Bulk CSV export is not on Free. It is included on Solo at $97 a month. Agency is $497 a month with 50,000 rows a file.

Can Claude do this for me?

Yes, through the hosted MCP server at https://api.leadocean.io/mcp. The leadocean_find_phone tool looks up one person and costs 1 record. For mobile numbers specifically, see how to find a cell phone number for B2B outreach. To go the other way, see find a LinkedIn profile from a phone number.

Find your first 5 direct dials for free

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

Get your free API key →