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

How-to guide

How to Test Phone Enrichment Tools for Direct Dials (a 100-Person Bench)

Build a control list, run every vendor on it, score by answered calls. Our side of the bench costs 100 records.

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

Do not trust anyone's hit rate, including ours. A published "we tested 9 tools" table measures the author's list, not yours. Build a 100-person control list from your own market, run every vendor on it, and score by calls that reached the person.

This guide gives you that bench. It covers the control list, one script for LeadOcean, a cost table for eight other tools, and the scoring query. We publish no winner here because we did not run your list.

TL;DR

  • Build 100 people from your target market, run each vendor on the same 100, and score answered calls to the right person. Not "number found".
  • Setup takes about 2 hours. Dialing takes 1 to 2 days. The LeadOcean side spends 100 records: one phone lookup per person.
  • 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. Free keys get search and enrich.
  • Accounts on the vendors you want to test. Several have a free tier with a few phone credits; Cognism has no public price.
  • curl, jq and a spreadsheet.
  • A dialer or a phone, and a few hours of a rep's time. The test is only real if someone calls.

Step 1: Build a control list from your own market

Pick 100 people with a mix of seniority, company size and country. Use a LinkedIn URL for each. A LinkedIn URL is the one identifier every tool accepts, so it keeps the comparison fair.

Do not pick people who already sit in your CRM with a number. That tests recall of numbers you hold, not discovery. Pull fresh names. You can size the pool for free first.

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": ["Director"],
    "country": ["US"],
    "hasPhone": true,
    "limit": 1
  }' | jq '.meta | {total, countOnly}'

count=true goes in the query string, not the body. The count costs no records. To build the list, search with limit set to 100 and keep each row's LinkedIn URL. That page costs 100 records, so on Free it is a tenth of your allowance.

If you want a list that skips the search cost, export your own customers' contacts and strip the numbers. Either way, freeze the file. Every vendor gets the identical 100 rows.

Step 2: Run LeadOcean on the list

The v2 phone endpoint takes one identifier and returns every number held, best first, each with a type label such as mobile, direct or office. The label is the source's own, so treat an unfamiliar value as unknown.

bash
while read -r url; do
  curl -s -G "https://api.leadocean.io/v2/people/phone" \
    --data-urlencode "linkedin_url=$url" \
    -H "x-api-key: $LEADOCEAN_API_KEY"
  echo
  sleep 0.25
done < linkedin_urls.txt > leadocean_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, so 100 calls spend 100 records. The endpoint returns data and no meta. Read GET /v1/usage for what you spent.

Flatten the output to one best number per person:

bash
jq -r 'select(.data.found == true)
  | [.data.person_id, .data.best.phone, .data.best.type] | @csv' \
  leadocean_phones.jsonl > leadocean_best.csv

Step 3: Run the other eight with their own credit rules

Each vendor bills phones differently. Read the unit before you run, so one tool does not eat your budget. Prices below come from each vendor's own docs or pricing page, checked in September 2026.

ToolWhat a phone costsSource
Apollo8 credits per phone numberApollo API pricing
Kaspr100 phone credits a month on Starter ($65 monthly), 5 on FreeKaspr pricing
Lusha5 credits per phone, 7 through one API request with emailLusha API docs
ContactOut1 phone credit when a phone is found (API docs); Free gives 5 phones a dayContactOut pricing
RocketReach6 credits per phoneRocketReach credits
FullEnrich10 credits per mobile; not found or landline costs 0FullEnrich credits
LeadMagic5 credits per mobileLeadMagic pricing explained
CognismNo public price. Credits are per revealed contact, quoted by salesCognism pricing

Two cost traps matter for the test. FullEnrich bills nothing for a miss, while LeadOcean's v2 phone call bills a record even on a miss. Apollo Free's 75 credits a month cover 9 phone numbers at 8 credits each (Apollo pricing).

Run all 100 through each tool's free tier or a one-month plan. If a tool will not cover 100 lookups on its free tier, test the first 25 and say so in your sheet. Do not extrapolate.

Put each vendor's best number into one column of the same sheet, keyed by LinkedIn URL.

Step 4: Dial and score on outcomes

A returned number is not a hit. Score what happened on the call. Log one outcome per number: reached the person, reached a gatekeeper or switchboard, wrong person, disconnected, voicemail with the right name, or no answer after 2 tries.

Reached the person is a hit. Voicemail with the right name is a half hit. Everything else is a miss.

Dial at the same hours across vendors, or you test the clock and not the data. Use a rep who does not know which vendor supplied the number. Shuffle the rows and hide the source column until scoring is done.

Count the do-not-call rules for your market before you dial. The v2 phone endpoint does not publish a do-not-call flag. The older GET /v1/people/phone publishes dnc per number, but it is not populated, and null means unknown, never safe to call.

Step 5: Compute cost per answered call

Hit rate alone misleads, because tools price a miss differently. Use cost per hit.

sql
SELECT vendor,
       COUNT(*) FILTER (WHERE outcome = 'reached_person')            AS hits,
       COUNT(*) FILTER (WHERE number IS NOT NULL)                    AS numbers_returned,
       ROUND(SUM(cost_usd) / NULLIF(COUNT(*) FILTER
         (WHERE outcome = 'reached_person'), 0), 2)                  AS usd_per_hit
FROM bench
GROUP BY vendor
ORDER BY usd_per_hit;

For LeadOcean, cost per record on a paid plan is not a per-record price. Paid plans have a fair-use pace, not credits, so compare on your plan price over the volume you expect to dial. See pricing.

Also track the number type. A mobile reaches an executive. A desk line reaches an assistant at a desk-bound role. A vendor that returns only switchboards will score high on "found" and low on "reached".

What you get

The response shape from the API reference for a phone lookup, 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" }
  }
}

The free count ran in the leadocean_count_leads tool on 2026-10-21 with jobLevel Director, country US and hasPhone true. It sizes the pool you might draw your control list from:

json
{
  "total": 8976341,
  "totalIsExact": true,
  "distinctCompanies": 1444945,
  "distinctCompaniesIsExact": false,
  "credits": 0
}

That is a count of US directors with a number on file. It says nothing about how many of those numbers still ring. Your 100-person dial test answers that.

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 array in searchA filter value does not match the enum. Enum values are exact strings.Check values with GET /v1/enums or leadocean_list_enum_values.
400 on the phone callYou sent two identifiers, or none.Send one of linkedin_url, contact_id or person_id.
404 on a lookupWe hold no record for that person.Nothing to fix. A 404 costs nothing. Log a miss and move on.

FAQ

Why not just publish a ranking?

A ranking measures one list. Phone coverage shifts by country, seniority and industry, so a vendor that wins on US sales directors can lose on German finance leads. Your list is the only one that predicts your results.

How many people do I need for a fair test?

100 is the floor for a rough read. At 100 people, a gap of a few points between two tools is noise. Use 300 or more if two vendors score close and the choice is expensive.

Does LeadOcean charge for a phone lookup that finds nothing?

On the v2 phone endpoint, yes: 1 record per call, including a miss. The v1 enrich route is different, since a person we hold nothing for is a 404 and costs nothing. Plan your 100-record budget on the v2 rule.

Can I run the LeadOcean side from Claude instead of a script?

Yes. The hosted MCP server at https://api.leadocean.io/mcp has a leadocean_find_phone tool that looks up one person for 1 record. The free tools leadocean_count_leads and leadocean_list_filters size the pool. For the single-lookup version of this guide, see how to find a work phone or direct dial.

What is a direct dial, and does the type label matter?

A direct dial rings one person. The type on each LeadOcean number (mobile, direct, office) shows what you got. See what direct dials are. For a broader tool bench on CRM enrichment, see 11 HubSpot enrichment tools.

Run the LeadOcean side of your direct-dial bench for free

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

Get your free API key →