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

How-to guide

How to Find Direct Dials (B2B Teams, API)

A direct dial rings one person, not a switchboard. Size the pool for free, search for people who hold a number, then look each one up. About 10 records for a 5-person test.

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

A direct dial is a phone number that rings one person, not a switchboard or a receptionist. B2B teams find them by filtering for people who hold a number, then looking up each number and reading its type. This guide runs that in three API calls.

The word "direct" is a vendor label, not a promise. In B2B data it usually covers a desk line with its own number (a DID) or the person's mobile. A switchboard is neither. So the job is not "get a number". It is "get a number and check what kind it is".

TL;DR

  • Count people with a number for free, search for the ones you want, then look up each number. Total time: about 10 minutes.
  • A 5-person test spends 10 records: 5 search rows and 5 phone lookups. Counting spends none.
  • Read the type on every number (mobile, direct, office) before you build a dial list.

Prerequisites

  • A LeadOcean API key in $LEADOCEAN_API_KEY. The Free plan gives you 1,000 records for life, no card, and 5 requests a second. Get one here.
  • curl and jq.
  • The company domains or titles you want to call. Placeholders below use acme.com.

Step 1: Size the pool for free

Send count=true as a query parameter and limit 1 in the body. The call spends no records and returns the total in meta.total.

bash
curl -s -X POST "https://api.leadocean.io/v1/people/search?count=true" \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "jobLevel": ["VP"],
    "jobFunction": ["Sales & Business Development"],
    "country": ["US"],
    "hasPhone": true,
    "limit": 1
  }' | jq '.meta | {total, countOnly}'

hasPhone: true keeps only people with a number on file. meta.total is capped at 100,000 on this route. The MCP tool leadocean_count_leads takes the same filters, also free.

Filter values are exact strings. jobLevel takes C-Team, VP, Director, Manager, Staff or Other. Check a value with leadocean_list_enum_values before you guess.

Step 2: Search for people who hold a number

Run the same filters without the count flag and narrow to one company. 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"],
    "jobLevel": ["VP", "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. Filters are arrays of strings, and hasPhone is a boolean. limit runs from 1 to 100. Page with meta.nextCursor passed back as cursor.

Search rows carry a has_phone flag and an id, not the number itself. Each row costs 1 record.

Step 3: Look up each person's numbers

The phone endpoint returns every number held for one 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: Keep the real direct dials

Each number has a type, such as mobile, direct or office. The label is the source's own and not a closed list. Keep what fits the role. Desk roles such as inside sales and finance are reachable on a direct or office line. Executives and field roles are easier to reach on a mobile.

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

Treat any type you do not recognise as unknown, not as an error. The v2 endpoint does not publish a do-not-call flag, so check your own do-not-call lists before you dial.

What you get

The free count from step 1 ran in leadocean_count_leads on 2026-10-21. Passing hasPhone means the tool reports no totalAll and no email-status note, so read this total as people with a phone on file, not a mailable count.

json
{
  "total": 275259,
  "totalIsExact": true,
  "distinctCompanies": 135386,
  "distinctCompaniesIsExact": false,
  "credits": 0
}

Filters: jobLevel VP, jobFunction Sales & Business Development, country US, hasPhone true. The lookup response, as the response shape from the API reference, with placeholder values.

json
{
  "success": true,
  "data": {
    "person_id": "123456789",
    "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. A count says how many people hold a number. It does not say how many are mobile, direct or still dialable.

Troubleshooting

ErrorCauseFix
401The x-api-key header is missing or 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 Retry-After, 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, or the domain has nobody with a number.Drop one filter and recount. Check 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.
Export refusedA bulk export is over 50,000 rows a request or 500,000 a day, or you are on Free.Split the filters into smaller exports. Bulk CSV export is not on Free.

FAQ

What is the difference between a direct dial and a mobile?

A direct dial usually rings a desk line or extension that belongs to a seat. A mobile follows the person. Both reach one person, but a vendor may label a mobile as "direct", so read the type on each number. See what is a direct dial for the full definition.

How many records does finding a direct dial cost?

One record per search row and one per phone lookup. A lookup that finds nothing still costs 1 record on the v2 endpoint. Counting and listing filters cost nothing. A 5-person test costs 10 records.

Can I find a direct dial 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, then call the phone endpoint. The work phone guide walks through it.

How do I do this for thousands of people?

Use the Exports page in the app: a filter builder, a column picker with phone columns, the record price before you start and a CSV download. Bulk CSV export is included from Solo at $97 a month. Agency is $497 a month with 50,000 rows a file. To compare tools on hit rate, see we tested 9 phone enrichment tools.

Find your first direct dials free

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

Get your free API key →