TL;DR
- You do not need a connection. A public
linkedin.com/in/...URL goes intoGET /v2/people/email/workandGET /v2/people/phone, and you read the answer from the response. - It takes about 10 minutes the first time, mostly getting a key. One profile spends 1 to 2 of your 1,000 free records, found or not.
- The result carries a status. Filter on it before you send, because bounced emails are not refunded.
Prerequisites
- A LeadOcean account and key from app.leadocean.io. The free plan has no card and 1,000 records, one-off.
- The key in your shell as
$LEADOCEAN_API_KEY. Free keys get the search and enrich scopes. curlandjq. For the list step, a text file with one profile URL per line.
export LEADOCEAN_API_KEY="paste-your-key-here"Step 1: Check what the profile shows by hand
Open the profile and click Contact info. It costs nothing and takes a minute.
You see only what the person chose to show. Many profiles show no email and no phone, and what is there may be stale. This step does not scale past a handful of people, which is why the rest of this guide uses an API.
Before you send a URL anywhere, strip the tracking parameters that share links add.
echo "https://www.linkedin.com/in/jane-doe-123abc/?utm_source=share&utm_medium=member_desktop" \
| sed -E 's/\?.*$//'https://www.linkedin.com/in/jane-doe-123abc/Step 2: Get the work email
Send the cleaned URL to the work email endpoint. It returns one address at the person's current employer, or data: null.
curl -s -G "https://api.leadocean.io/v2/people/email/work" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
--data-urlencode "linkedin_url=https://www.linkedin.com/in/jane-doe-123abc/" | jqThe endpoint returns only addresses with status verified, catch_all_valid or catch_all. So null means none that qualify, not that the person has no email.
The call costs 1 record whether it finds an address or not. Nothing is sent to the person and no connection request is made. You are querying a dataset, not LinkedIn.
Step 3: Get the phone number
Phone numbers come from a separate endpoint, so you pay for them only when you want them.
curl -s -G "https://api.leadocean.io/v2/people/phone" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
--data-urlencode "linkedin_url=https://www.linkedin.com/in/jane-doe-123abc/" | jqLike the email call, this costs 1 record per call, including a call that finds nothing. Run the email call first and the phone call only for the profiles you plan to dial.
If you want the full record in one request instead, use enrich. It takes exactly one of linkedin_url, email, phone or person_id. A person we hold nothing for is a 404 and costs nothing.
curl -s -X POST "https://api.leadocean.io/v1/people/enrich" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "content-type: application/json" \
-d '{
"linkedin_url": "https://www.linkedin.com/in/jane-doe-123abc/",
"reveal_email": true
}' | jq '.data.contact_data'reveal_email adds no extra record. The same response includes career history under resume_data (past roles, education, certifications), when we hold it.
Step 4: Run it over a list
Put one URL per line in profiles.txt. Loop the work email call and write one JSON line per profile.
while IFS= read -r url; do
clean=$(echo "$url" | sed -E 's/\?.*$//')
curl -s -G "https://api.leadocean.io/v2/people/email/work" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
--data-urlencode "linkedin_url=$clean" \
| jq -c --arg url "$clean" '{url: $url, email: .data.email, status: .data.status}'
sleep 0.25
done < profiles.txt > results.jsonlThe sleep 0.25 keeps you under the Free limit of 5 requests a second. On Agency, at 200 requests a second, drop it.
Records add up on a loop. 500 profiles spend 500 records, hits and misses alike. Run 20 first and read the hit rate before you run the rest.
Step 5: Filter on status before you send
An address is not a send list until you have read its status. Keep verified and catch_all_valid. Treat catch_all as a risk you size.
jq -c 'select(.email != null and (.status == "verified" or .status == "catch_all_valid"))' \
results.jsonl > sendable.jsonl
wc -l sendable.jsonlThe dataset is refreshed monthly, and each record carries its own fetched_at date. If a list is older than a month, look the profiles up again.
If you have no URLs yet, skip LinkedIn entirely. Search by domain with GET /v1/companies/{domain}/people, then enrich by person_id. For a filtered audience, POST /v1/exports writes up to 50,000 rows a request and 500,000 a day. Bulk CSV export is not on Free.
What you get
Response shape from the API reference, with placeholder values. The work email call returns success and data, with no meta object.
{
"success": true,
"data": {
"email": "jane.doe@acme.com",
"status": "verified",
"verified_batch_date": "2026-09-20"
}
}The enrich call returns contact points under data.contact_data. Phones come back as contact_phones[].phone.
{
"success": true,
"data": {
"contact_data": {
"has_email": true,
"has_phone": true,
"email_status": "verified",
"contact_emails": [
{ "email": "jane.doe@acme.com", "type": "work", "status": "verified" }
],
"contact_phones": [
{ "phone": "+14155550133", "type": "mobile" }
]
}
}
}Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing, wrong or revoked key | Re-copy the key from app.leadocean.io and re-export $LEADOCEAN_API_KEY. The header is x-api-key. |
| 429 | More than your plan's rate limit on one key (5 a second on Free, 200 on Agency) | Honor the Retry-After header, add a short sleep, or run fewer workers. |
| 402 | The free 1,000 records are spent | Check GET /v1/account, or move to Solo at $97 a month or Agency at $497 a month. |
| 404 on enrich | We hold no record for that profile | Nothing was charged. Try the person's email or another identifier. |
data: null on the work email call | No checked address at the current employer qualifies | The call still cost 1 record. Try enrich, or GET /v2/people/email/personal if a personal address fits your use. |
| Export refused | More than 50,000 rows a request or 500,000 a day | Split the export across requests or days. |
FAQ
Can I really get contact info without sending a connection request?
Yes. The API looks the profile URL up in LeadOcean's dataset. No connection request or message goes to the person.
Is it free?
Checking Contact info by hand is free. The API route starts on LeadOcean's free plan: 1,000 records, one-off, no card.
Does a lookup that finds nothing cost a record?
On the v2 email and phone endpoints, yes: 1 record per call. On POST /v1/people/enrich, a miss is a 404 and costs nothing.
Is the phone number always there?
No. Some profiles have an email and no phone. Check has_phone in the enrich response, or read found in the phone call's data.
Where do I go next?
Read how to get phone numbers from LinkedIn without an extension, how to get warm leads from LinkedIn posts and how to optimize your LinkedIn URL. The full email walkthrough is the LinkedIn profile to email guide.
Get a checked work email from any public LinkedIn URL
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →