The fastest way to find an email from a phone number is a reverse lookup against a contact database. A manual web search works for the odd public number, but it does not scale and it returns no deliverability check.
TL;DR
- Manual search takes 5 to 10 minutes per number and often ends with nothing. One API call is a single request.
- One enrich call with
phoneandreveal_emailcosts 1 record. A number we hold nothing on is a 404 and costs nothing. - For a list, loop the same call from a short script. Stay under your plan's rate: 5 requests a second on Free, 50 on Solo, 200 on Agency.
Prerequisites
- A LeadOcean API key from the free plan: 1,000 records, one-off, no card. Export it as
$LEADOCEAN_API_KEY. curlandjqfor the examples.- Phone numbers in international format, such as
+14155550133. - A place to keep a text file of numbers, one per line, if you plan to run the bulk step.
Step 1: Try the manual search first
Search the number in quotes before you spend anything. It only works when the person published the number on a page a search engine can read.
"+1 415 555 0133"
"(415) 555-0133"
"415-555-0133" email
"415.555.0133" site:linkedin.comRun all four formats, because pages print numbers in different styles. Check company contact pages, speaker bios, press releases and old PDFs. Anything you find is a lead, not a fact: the number may belong to a switchboard or a former employer.
If a page shows a name and a company, guess the address pattern and verify it before you send. A guess that was never tested is the most common cause of a bounce.
Set a time limit. If ten minutes of searching gives you nothing, move on to the lookup. The manual route is a fallback for one-off numbers, not a method for a list.
Step 2: Normalize the number
Strip spaces, brackets and dots, and add the country code. The enrich endpoint accepts any human format, but a clean E.164 number removes doubt about which country you meant.
echo "(415) 555-0133" | tr -cd '0-9' | sed 's/^/+1/'
## prints +14155550133The one-liner assumes a US number. For other countries, replace +1 with the right prefix, and drop a leading trunk 0 where the national format uses one.
Step 3: Look the number up
Send the number to POST /v1/people/enrich with reveal_email set to true. The call returns the person and every email we hold for them. It costs 1 record whether or not you reveal contact points.
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", "reveal_email": true}'Send exactly one identifier. The endpoint takes one of linkedin_url, email, phone or person_id. If we hold nobody for that number, you get a 404 and spend nothing.
The response carries the person's profile and their emails in one answer. You do not need a second call to get the address, which is why the whole lookup is 1 record.
A strict alternative is GET /v2/people/reverse?phone=+14155550133. It returns the identity only: full_name, job_title, company_name, company_domain, linkedin_url and person_id. It costs 1 record, even when it finds nobody.
Step 4: Pick the work email
Filter the contact_emails array to the work type, then read the status. Use verified or catch_all_valid for outreach. Treat catch_all as a risk you accept on purpose.
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", "reveal_email": true}' \
| jq '[.data.contact_data.contact_emails[]
| select(.type == "work"
and (.status == "verified" or .status == "catch_all_valid"))]
| first'Skip work_other. That type is an address at a former employer, so mail to it may bounce or reach the wrong inbox. We do not refund or credit back bounced emails, so read status before you send.
Want the single best current address instead of a filter? Take person_id from the reverse call and send it to GET /v2/people/email/work. It returns one checked address at the current employer, or data: null when none qualifies. That path costs 2 records in total.
Step 5: Run it on a list
Loop the call over a text file of numbers. This script writes a CSV of phone, email and status, and stops on the errors that mean you should not keep going.
#!/usr/bin/env bash
## numbers.txt: one E.164 number per line
echo "phone,email,status" > out.csv
while read -r num; do
out=$(curl -s -w '\n%{http_code}' -X POST \
https://api.leadocean.io/v1/people/enrich \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "content-type: application/json" \
-d "$(jq -n --arg p "$num" '{phone:$p, reveal_email:true}')")
code=$(echo "$out" | tail -n1)
body=$(echo "$out" | sed '$d')
case "$code" in
200) echo "$body" | jq -r --arg p "$num" '
([.data.contact_data.contact_emails[]?
| select(.type=="work"
and (.status=="verified" or .status=="catch_all_valid"))]
| first) as $e
| [$p, ($e.email // ""), ($e.status // "")] | @csv' >> out.csv ;;
404) echo "\"$num\",," >> out.csv ;;
429) sleep 1 ;;
401|402) echo "stopped on $code at $num" >&2; exit 1 ;;
esac
sleep 0.2 # 5 requests a second, the Free plan rate
done < numbers.txtThe sleep 0.2 keeps the loop at the Free rate of 5 requests a second. Shorten it on Solo (50 a second) or Agency (200 a second). The real constraint is spend: each matched number is 1 record. Count your numbers first and read your remaining records from GET /v1/usage, which is free.
The script does not retry a 429. Rerun the numbers that came back empty after a short wait.
When you have a list of companies instead of numbers, skip the lookup and export directly. POST /v1/exports takes up to 50,000 rows a request and 500,000 a day. Bulk CSV export is not on Free: it starts on Solo at $97 a month.
What you get
A matched number returns the person and their contact points. This is the response shape from the API reference, with placeholder values. It is not a real record.
{
"success": true,
"data": {
"profile_data": { "...": "name, title, company and LinkedIn URL" },
"contact_data": {
"has_email": true,
"has_phone": true,
"email_status": "verified",
"email_type": "work",
"contact_emails": [
{
"email": "jane.doe@acme.com",
"type": "work",
"status": "verified",
"verified_batch_date": "2026-09-20"
}
],
"contact_phones": [
{ "phone": "+14155550133", "type": "mobile", "priority": 1 }
]
}
},
"meta": {}
}Three fields matter most. email_type tells you whether the address is at the current employer. status tells you how safe it is to send. verified_batch_date names the verification run that checked it, and null means the check came from a source we hold to the same standard.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing, wrong or revoked key | Check that x-api-key carries $LEADOCEAN_API_KEY and the key is still active in the app. |
| 402 | Free plan: the 1,000 records are spent | The allowance is one-off and does not reset. Move to Solo ($97 a month) or Agency ($497 a month) on pricing. |
| 404 | We hold nobody for that number | Costs nothing. Try the other formats, or enrich by linkedin_url or email if you have one. |
| 429 | Over your plan's rate per account (5 a second on Free, 50 on Solo, 200 on Agency), or a fair-use window reached on a paid plan | Honour Retry-After. Slow the loop, or split the job across keys on Agency (5 keys). |
| 400 | Validation failed, often two identifiers in one request | Send exactly one of linkedin_url, email, phone or person_id. |
Empty contact_emails | You left reveal_email out, so it defaulted to false | Resend with "reveal_email": true. The call costs 1 record either way. |
FAQ
Can I find an email from a phone number for free?
Yes, up to a limit. The free plan gives you 1,000 records, one-off, with no card. Numbers we hold nothing on return a 404 and spend no records.
Does a phone-to-email lookup work for mobile numbers?
It works for any number that sits on a record we hold. We hold a phone for 166M people (measured September 2026). A personal mobile with no matching record returns a 404.
Should I use enrich or reverse?
Use enrich for most jobs. It costs 1 record and returns the full person with every email. Use reverse when you only need to know who holds the number, and expect 1 record even on a miss.
Do I pay when the lookup finds nothing?
Not on enrich: a 404 is free. The v2 contact endpoints, including reverse, cost 1 record per call even when they find nothing.
What about the opposite direction?
To go from a person to a number, use the find phone number endpoint or the Claude skill. For a worked example, read how to find a VP of Sales phone number. More guides live in the blog, and the pricing page lists all four plans.
Turn the next phone number into a verified work email
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →