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

How-to guide

How to Get an Email Address From a LinkedIn Profile or URL (Step-by-Step)

One POST with a linkedin_url returns the work email and its deliverability status. Here is the request, the output and what it costs.

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

You get an email from a LinkedIn URL by sending the URL to an enrichment API and asking it to reveal contact points. With LeadOcean that is one POST to /v1/people/enrich with linkedin_url and reveal_email: true.

This guide is the short version. No browser extension, no copy and paste. Real requests, the response shape, and the errors you will hit.

TL;DR

  • One call to POST /v1/people/enrich turns a LinkedIn URL into a work email plus its email_status. It takes about 10 minutes the first time, seconds after that.
  • Cost: 1 record per profile we hold. A 404 (nobody matched) costs nothing. The Free plan gives you 1,000 records for life, no card.
  • Check email_status before you send. There is no refund for bounces.

Prerequisites

  • A LeadOcean API key from app.leadocean.io. Free works, and it carries both the search and enrich scopes.
  • The key in your shell as $LEADOCEAN_API_KEY.
  • curl and jq. Python or Node works the same way.
  • One or more LinkedIn profile URLs. Examples below use a placeholder: https://www.linkedin.com/in/jane-doe-example.
bash
export LEADOCEAN_API_KEY="paste-your-key-here"

Step 1: Check your account and balance

Confirm the key works and see how many records you have left. GET /v1/account is free.

bash
curl -s https://api.leadocean.io/v1/account \
  -H "x-api-key: $LEADOCEAN_API_KEY" | jq

A 401 here means the key is wrong or missing. Fix that before anything else.

Step 2: Enrich one LinkedIn URL

Send exactly one identifier. For this task it is linkedin_url. Set reveal_email to true, because the default for a LinkedIn lookup is false and you would get the profile without the address.

bash
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-example",
    "reveal_email": true
  }' | jq '.data.contact_data'

reveal_email adds every email and phone we hold for that person at no extra record. You pay one record for the person, not one per field.

Step 3: Read the status before you use the address

The contact_emails array comes back with a status on each address. Filter to what is worth sending to.

bash
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-example","reveal_email":true}' \
  | jq '.data.contact_data.contact_emails[]
        | select(.status=="verified" or .status=="catch_all_valid")
        | .email'

verified and catch_all_valid are safe defaults. catch_all is a gamble on a domain that accepts everything. risky, unknown, invalid, role, disposable and spam_trap should not go into a sequence.

Look at type too. work is the current employer. work_other is a former employer. personal is a personal address, and our verifier has not tested those.

Step 4: Loop over a list of URLs

For a list, run the same call per row and skip the misses. A 404 means we hold nothing for that profile, so it is free.

bash
while read -r url; 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 "{\"linkedin_url\":\"$url\",\"reveal_email\":true}")
  code=${out##*$'\n'}
  body=${out%$'\n'*}
  if [ "$code" = "200" ]; then
    echo "$url,$(echo "$body" | jq -r '.data.contact_data.contact_emails[0].email')"
  else
    echo "$url,,$code"
  fi
  sleep 0.25
done < urls.txt > emails.csv

The sleep 0.25 keeps you at about 4 requests a second, under the 5 a second Free limit. On Agency the limit is 200 a second. Above 1,000 rows, use POST /v1/exports instead, which takes up to 50,000 rows a request.

Step 5: Script it in Python

The same call from code. This version writes one row per URL and keeps the status next to the address, so the filter in Step 3 happens before anything reaches a sequencer.

python
import os, time, csv, requests

API = "https://api.leadocean.io/v1/people/enrich"
HEADERS = {"x-api-key": os.environ["LEADOCEAN_API_KEY"]}

def lookup(url):
    r = requests.post(API, headers=HEADERS,
                      json={"linkedin_url": url, "reveal_email": True})
    if r.status_code == 429:
        time.sleep(int(r.headers.get("Retry-After", 1)))
        return lookup(url)
    if r.status_code != 200:
        return None
    emails = r.json()["data"]["contact_data"].get("contact_emails", [])
    good = [e for e in emails if e["status"] in ("verified", "catch_all_valid")]
    return good[0] if good else None

with open("urls.txt") as f, open("emails.csv", "w", newline="") as out:
    w = csv.writer(out)
    for url in (line.strip() for line in f if line.strip()):
        hit = lookup(url)
        w.writerow([url, hit["email"] if hit else "", hit["status"] if hit else ""])
        time.sleep(0.25)

Two details matter. A 429 is not charged, so retrying after Retry-After is safe. And a profile that returns only a catch_all address is skipped on purpose: decide that one by hand.

Step 6: Do it in Claude with the MCP server

If you work in Claude, the hosted MCP server at https://api.leadocean.io/mcp signs in with OAuth, so no key is pasted. It exposes 13 tools. leadocean_get_lead looks up one person by LinkedIn URL, work email, phone or person_id, and costs 1 record. A lookup that finds nobody costs nothing.

text
Use leadocean_get_lead for https://www.linkedin.com/in/jane-doe-example.
Reveal the work email. Tell me its email_status and whether the address
is work or work_other. Do not guess if nothing comes back.

To size a list before you spend anything, call leadocean_count_leads. It is free.

What you get

Response shape from the API reference, with placeholder values (not a real person):

json
{
  "success": true,
  "data": {
    "profile_data": { "...": "name, headline, location" },
    "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 }
      ],
      "contact_current_experiences": [ { "...": "current role" } ]
    },
    "resume_data": { "...": "past roles, educations, certifications" }
  },
  "meta": { "source": "leadocean" }
}

Since 17 October 2026 the same call also returns resume_data: past roles, education, certifications and awards. Past employers carry no company_id yet.

For scale, here is a free count run on 21 October 2026 with leadocean_count_leads, filtered to jobLevel VP and country US. It counted only addresses worth sending to (verified, catch_all_valid or catch_all):

json
{ "total": 1242801, "totalIsExact": true, "totalAll": 3102417, "distinctCompanies": 226527, "credits": 0 }

That is the count, not a spend. Counts cost 0 records.

Troubleshooting

ErrorCauseFix
401Missing or wrong x-api-key headerRe-export $LEADOCEAN_API_KEY and send it as x-api-key.
404We hold nothing for that profileFree. Try the person's email, or search by company domain and enrich by person_id.
402Out of records (Free is 1,000 for life)Check GET /v1/account. Move to Solo at $97 a month, or Agency at $497 a month.
429Above your plan's rate limit (5 a second on Free, 200 on Agency)Honour the Retry-After header and slow the loop.
Empty contact_emailsYou left out reveal_email: true, or we hold no addressSet reveal_email to true. Check has_email in the response.
Export refusedOver 50,000 rows a request or 500,000 rows a daySplit the file. Bulk CSV export is not on Free.

FAQ

Can I get an email from a LinkedIn URL for free?

Yes, up to 1,000 records for life on the Free plan, no card. Each matched profile spends one record. A profile with no match spends none.

Does LeadOcean scrape LinkedIn to do this?

No page is fetched when you call the API. You send the URL as a lookup key, and the answer comes from the LeadOcean dataset (693M people, 366M with an email as of September 2026). The dataset is refreshed monthly, and each record carries its own fetched_at date.

What if the profile has no LinkedIn URL on file?

About a fifth of people that search can find have no LinkedIn URL. Search by company domain or title first, then enrich by person_id from meta.source_ids.person_id.

How do I go the other way, from an email to a profile?

Send email instead of linkedin_url to the same endpoint. See searching LinkedIn by email.

Which email should I send to?

Send to verified or catch_all_valid work addresses. Read how to find someone's email from LinkedIn and the shorter overview for the manual options. Pricing for volume is on /pricing.

Turn LinkedIn URLs into verified emails

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

Get your free API key →