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 Cell Phone Number for B2B Outreach (API)

Filter to people who hold a number first, then look up only those. Two calls, 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 B2B cell number in two calls: search with hasPhone set to true, then look up each person by id. The search tells you who holds a number before you spend anything on a lookup. This guide runs both against the LeadOcean API with real queries.

TL;DR

  • Size the list for free with a count, search only people who have a number, then call GET /v2/people/phone on each.
  • It takes about 15 minutes. A 10-person test spends 20 records: 10 for the search rows, 10 for the 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 in mind: a job level, a function and a country. This guide uses VPs of Sales in the US.

Step 1: Size the list for free

Count first. A count spends no records, so you can tune filters until the list is the right size.

Send count=true as a query parameter, with limit=1. The JSON body rejects unknown fields, so keep the flag 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 '{
    "jobLevel": ["VP"],
    "jobFunction": ["Sales & Business Development"],
    "country": ["US"],
    "hasPhone": true
  }' | jq '.meta | {total, countOnly}'

meta.total is capped at 100,000 on this route. If you use Claude or another MCP client, the leadocean_count_leads tool gives the exact figure with breakdowns. We ran it on 2026-10-21 with the same four filters and got 275,259 people.

Filter values are exact strings. VP and Sales & Business Development must match the enum. Check them with GET /v1/enums or the leadocean_list_enum_values tool before you guess.

Step 2: Search only people who hold a number

Run the same filters without the count flag. hasPhone: true keeps rows where a number exists, so every lookup in step 3 has something to find.

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 '{
    "jobLevel": ["VP"],
    "jobFunction": ["Sales & Business Development"],
    "country": ["US"],
    "hasPhone": true,
    "limit": 10
  }' > 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 phone numbers. They carry has_phone, and an id at meta.source_ids.person_id. That id is what you send in the next step. Each row returned costs 1 record, so 10 rows cost 10.

Step 3: Look up each number

Call the phone endpoint with the id. It returns every number held for that person, best first.

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

Send exactly one identifier: linkedin_url, contact_id or person_id. person_id and contact_id are the same field, so send one of them. The sleep keeps you under the 5 requests a second on Free. Each call costs 1 record, including a call that finds nothing.

If you already hold a LinkedIn URL, skip steps 1 and 2 and send linkedin_url instead. The same pattern works in the other direction: find a LinkedIn profile from a phone number.

Step 4: Keep the mobile numbers

The array is ordered best first, and each number has a type. The type is the source's own label, so it is not a closed list. Treat any value you do not recognise as unknown.

bash
jq -c 'select(.data.found == true)
  | {person_id: .data.person_id, mobile: ([.data.phones[] | select(.type == "mobile")][0].phone)}
  | select(.mobile != null)' phones.jsonl > mobiles.jsonl

wc -l mobiles.jsonl

This keeps one mobile number per person and drops people with only an office or direct line. Change the select to keep direct too if your reps call desks.

One warning on compliance. 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, not safe to call. Run your own DNC and consent checks before anyone dials.

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" }
  }
}

There is no meta object on this endpoint. The price per call is not reported in the body, so read GET /v1/usage for it. A person we hold nothing for returns found: false, and a record we cannot match at all is a 404.

The free count from step 1, run on 2026-10-21:

json
{
  "total": 275259,
  "totalIsExact": true,
  "distinctCompanies": 135386,
  "filters": "jobLevel VP, jobFunction Sales & Business Development, country US, hasPhone true"
}

That is a count of US VPs of Sales with a number on file. It says nothing about how many numbers are mobile or 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 arrayA filter value does not match the enum, for example Vp instead of VP.List the valid strings with GET /v1/enums and copy them exactly.
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 many records does a phone lookup cost?

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

Can I find a number from a name and company?

Not directly. There is no name plus domain lookup. Search by company domain or by title and country, take the person_id from the row, then call the phone endpoint with it.

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, and Agency at $497 a month allows 50k 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. The find phone number skill wraps it.

Find your first 10 cell numbers for free

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

Get your free API key →