TL;DR
- Verify in four steps: normalise to E.164, check the line type, confirm the number belongs to the person, then test that the line is live. Steps 1 and 2 cost nothing to run on a list you hold.
- It takes about 30 minutes to set up. The LeadOcean calls below spend 3 records of the 1,000 on the Free plan.
- LeadOcean returns numbers with a type and tells you who holds a number. It does not ping a line to see if it rings. For that you still need a carrier lookup from a telecom vendor.
Most guides on this topic list tools. This one is the order of operations a technical buyer would put in a script. Each step either throws a number out or earns it a dial.
Prerequisites
- A LeadOcean API key in
$LEADOCEAN_API_KEY(Free plan: 1,000 records for life, no card, 5 requests a second). - Python 3 with
phonenumbers(pip install phonenumbers) for the local checks. curlandjq.- A list of numbers you hold, or a person you want a number for.
Step 1: Normalise to E.164 and drop what cannot be a number
Format validation is free and removes the most junk. Parse every number with a default region, then keep only the ones that are possible and valid.
import phonenumbers
def clean(raw, region="US"):
try:
n = phonenumbers.parse(raw, region)
except phonenumbers.NumberParseException:
return None
if not phonenumbers.is_possible_number(n):
return None
if not phonenumbers.is_valid_number(n):
return None
return phonenumbers.format_number(n, phonenumbers.PhoneNumberFormat.E164)
for raw in ["(415) 555-0133", "415-555-013", "+44 20 7946 0000"]:
print(raw, "->", clean(raw))is_possible_number checks length. is_valid_number checks that the prefix exists in that country's numbering plan. Neither proves the number is assigned to anyone. Treat a pass as "could exist".
Step 2: Check the line type
Mobile, direct and office numbers behave differently. Texting a switchboard wastes a send. A mobile is the line a rep reaches without a gatekeeper. phonenumbers can guess from the prefix, but it cannot see ported numbers.
from phonenumbers import number_type, PhoneNumberType
n = phonenumbers.parse("+14155550133")
print(number_type(n) == PhoneNumberType.MOBILE) # prefix guess onlyWhen you source numbers from LeadOcean, the type comes with the record. Ask for numbers on one person and read the type field:
curl -s "https://api.leadocean.io/v2/people/phone?person_id=$PERSON_ID" \
-H "x-api-key: $LEADOCEAN_API_KEY" | jq '.data.phones'person_id comes from meta.source_ids.person_id on a people-search row. The call returns every number we hold, best first, and the position in the array is the priority. It costs one record per call, including a call that finds nothing.
Step 3: Size the list before you spend anything
To find people who have a number, filter on hasPhone instead of looking each one up. Sizing is free. Here is a count run through the LeadOcean MCP tool leadocean_count_leads on 2026-10-21:
{
"country": ["US"],
"jobLevel": ["VP"],
"jobFunction": ["Sales & Business Development"],
"hasPhone": true
}Result: 275,259 people, 135,386 distinct employers (employer count is an estimate), 0 records spent. This count carries hasPhone and the tool's default email condition, so it is not a pure phone count. Pass emailStatus or reachable yourself to change that.
The same sizing works over REST. Send count=true as a query parameter and limit 1 in the body:
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 '{"country":["US"],"jobLevel":["VP"],"jobFunction":["Sales & Business Development"],"hasPhone":true,"limit":1}'meta.total is capped at 100,000 on this call, so use the MCP count for a larger number.
Step 4: Confirm the number belongs to the person
A valid mobile number can still belong to the wrong person. If you hold a number and a name, enrich by the number and compare. Enrich takes exactly one of linkedin_url, email, phone or person_id.
curl -s -X POST https://api.leadocean.io/v1/people/enrich \
-H "x-api-key: $LEADOCEAN_API_KEY" -H "Content-Type: application/json" \
-d '{"phone":"+14155550133"}'If the record that comes back has a different name or employer than your CRM row, quarantine the row. A 404 means nobody matched and costs nothing. You can also ask the reverse question with /v2/people/reverse.
Step 5: Test that the line is live
This is the check LeadOcean does not do. Format, type and ownership say a number is plausible. They do not say the SIM is active today. A live-line test (an HLR or carrier lookup) queries the network and returns connected, disconnected or ported. Buy that from a telecom lookup vendor. Run it last, on the short list that survived steps 1 to 4, because it costs money per number.
Also check consent. The phone endpoint publishes no do-not-call flag, because the source does not populate one. A missing flag is not clearance. Run your own do-not-call screen before any outbound call or text.
How the four checks fit together
Order them by cost. Each check removes numbers before the next one runs, so the expensive step sees the fewest.
| Check | Where it runs | Cost | What it catches |
|---|---|---|---|
| 1. E.164 format | Your code | Free | Typos, wrong length, impossible prefixes |
| 2. Line type | Your code, or the type field from LeadOcean | Free locally, 1 record per LeadOcean lookup | Switchboards and landlines in a mobile-only campaign |
| 3. Person match | POST /v1/people/enrich with phone | 1 record when found, 0 on a 404 | Right number, wrong person |
| 4. Live line | A carrier lookup vendor | Per number | Disconnected and recycled SIMs |
A practical rule: run 1 and 2 on the whole list, 3 on anything a rep will actually dial, and 4 only before a large text or call blast. For a handful of high-value accounts, skip 4 and let the rep's first dial be the test.
One more habit helps. Store the check date next to each number. A line verified in March says little about October, and each LeadOcean record also carries its own fetched_at date, so you can see how fresh the source was. The dataset is refreshed monthly.
What you get
Response shape from the API reference, placeholder values, for GET /v2/people/phone:
{
"success": true,
"data": {
"found": true,
"phones": [
{ "phone": "+14155550133", "type": "mobile" },
{ "phone": "+14155550134", "type": "office" }
],
"best": { "phone": "+14155550133", "type": "mobile" }
}
}Each number is phone and type, best first. The type is the source's own label and is not a closed list. Treat an unrecognised value as unknown, not as an error. The response has no meta; read GET /v1/usage for what you spent.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing or wrong x-api-key header | Send the key in x-api-key, read from $LEADOCEAN_API_KEY. |
| 429 | Above the plan's rate limit (5 a second on Free, 200 on Agency) | Back off for the Retry-After header, or move to a plan with a higher limit. |
| 402 | The 1,000 Free records are spent | Upgrade on the pricing page. Solo is $97 a month. |
| Empty results | A bad enum value, such as jobLevel: "vp" instead of VP | Enum values are exact strings. Check them with leadocean_list_enum_values. |
found: false | We hold no number for that person | This is not a failed lookup. Try another identifier, or move on. |
| Export refused | Over 50,000 rows a request or 500,000 a day | Split the export. Bulk CSV export is not on Free. |
FAQ
What is the difference between phone number validation and verification?
Validation checks the number's shape: length, prefix and country plan. Verification checks the number against something real: its line type, its owner or the carrier network. Run validation first because it is free.
Does LeadOcean tell me if a number is disconnected?
No. It returns the numbers it holds with a type, and lets you look up the person behind a number. It does not query the carrier network. Use a carrier lookup for that.
How many records does verification cost?
Format checks in your own code cost nothing. Sizing with hasPhone is free. Each phone lookup or enrich by phone costs one record. An enrichment that finds nobody costs nothing.
Does LeadOcean screen numbers against do-not-call lists?
No. LeadOcean publishes no do-not-call flag on phone numbers, so treat every number as unscreened. Screen against the national and regional registers yourself.
Where do I find mobile numbers by country?
See best mobile phone numbers by region, mobile number finders and phone number finder tools. The blog lists the rest, and pricing covers what each plan allows.
Verify the numbers you dial. Start with 1,000 free records.
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →