Enrichment API

LinkedIn to Email API

GET /v2/people/email/work. One LinkedIn profile URL in, one checked work email out, or null. 1 record per call, a 404 is free.

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

GET /v2/people/email/work takes one LinkedIn profile URL and returns one work email at the person's current employer, or null. It costs 1 record per call, including a call that returns null. A 404 costs nothing.

This page is the developer reference. For what the email is and how it is checked, read LinkedIn URL to email. All lookups are listed in the Enrichments hub. To go the other way, see the Email to LinkedIn API.

Quick start (curl)

Create a free account, copy a key with the enrich scope and export it as LEADOCEAN_API_KEY.

bash
curl -s -G "https://api.leadocean.io/v2/people/email/work" \
  --data-urlencode "linkedin_url=https://www.linkedin.com/in/jane-doe" \
  -H "x-api-key: $LEADOCEAN_API_KEY"

No profile URL for the person? Send the person_id from a search row as contact_id:

bash
curl -s "https://api.leadocean.io/v2/people/email/work?contact_id=1234567" \
  -H "x-api-key: $LEADOCEAN_API_KEY"

The endpoint also answers POST with a JSON body of the same fields.

Node.js (fetch)

js
const url = new URL("https://api.leadocean.io/v2/people/email/work");
url.searchParams.set("linkedin_url", "https://www.linkedin.com/in/jane-doe");

const res = await fetch(url, {
  headers: { "x-api-key": process.env.LEADOCEAN_API_KEY },
});

if (res.status === 404) {
  console.log("No record for this profile. Not billed.");
} else if (!res.ok) {
  throw new Error(`${res.status} ${(await res.json()).error?.message}`);
} else {
  const { data } = await res.json();
  console.log(data ? `${data.email} (${data.status})` : "Person found, no qualifying email");
}

Python (requests)

python
import os
import requests

r = requests.get(
    "https://api.leadocean.io/v2/people/email/work",
    params={"linkedin_url": "https://www.linkedin.com/in/jane-doe"},
    headers={"x-api-key": os.environ["LEADOCEAN_API_KEY"]},
    timeout=10,
)

if r.status_code == 404:
    print("No record for this profile. Not billed.")
else:
    r.raise_for_status()
    data = r.json()["data"]
    print(f"{data['email']} ({data['status']})" if data else "Person found, no qualifying email")

Request

Send exactly one identifier. Sending both, or neither, is a 400. The key needs the enrich scope, which Free keys have.

ParameterTypeRequiredMeaning
linkedin_urlstring (URL)One of the twoA personal profile URL, linkedin.com/in/... Company pages are not people.
contact_idstring (digits)One of the twoThe id at meta.source_ids.person_id on a people-search row. Reaches the one person in five with no LinkedIn URL.
person_idstring (digits)AliasThe original name of contact_id. Same behaviour. Never send both.

There is no lookup by name, and this endpoint takes no email or phone as input.

Response

Response shape from the API reference. The address below is a placeholder.

json
{
  "success": true,
  "data": {
    "email": "jane.doe@acme.example",
    "status": "verified",
    "verified_batch_date": "2026-09-14"
  }
}
FieldMeaning
dataOne address object, or null when no address qualifies. null is a 200, not a 404.
data.emailThe work address, at the domain of the person's current employer.
data.statusverified, catch_all_valid or catch_all. Nothing else is returned here.
data.verified_batch_dateDate of the verification run, YYYY-MM-DD. null means the check was not ours. It does not mean unverified.

There is no found field and no meta object. A former employer's address is never returned, and neither is an address nobody has tested. So null means "none that qualify", not "no email exists". Quota state rides in the X-Quota-State, X-Quota-Reset and X-RateLimit-Limit headers.

Errors

Every failure is { "success": false, "error": { "message": "...", "details": ... } }. Status codes are from the limits docs, September 2026.

StatusMeaningWhat to do
400Both identifiers sent, neither sent, or a field failed validation.Read details, send one identifier.
401Missing, wrong or revoked key.Check the x-api-key header.
402Free plan: the 1,000 records are spent. They do not reset.Upgrade.
403The key lacks the enrich scope, or the account is suspended.Create a key with the scope.
404We hold no record for this profile at all.Stop. It costs nothing. Check the URL, or search for the person and use contact_id.
429Over 100 requests a second, or 1 a minute on a paid account past its records.Wait for Retry-After, then retry.
503The store behind the lookup is unreachable (code: no_source).Retry with backoff.

Rate limits and cost

What a call costs depends on how it ends.

OutcomeStatusCost
Email returned2001 record
Person found, no qualifying email (data: null)2001 record
No record for the profile404Nothing
Rejected request400, 401, 402, 403, 429, 503Nothing

The call does not report its own price. Read GET /v1/usage, which is free. The limit is 100 requests per second per key, on every plan.

Free is 1,000 records, one-off, no card. Pro is $499 a month, flat, with no per-record price inside fair use. See pricing.

Size before you spend. A people search with count=true and limit=1 is free and returns the total in meta.total, capped at 100,000. Each search row carries has_email and email_status, so you can skip people who hold no address.

Bulk

The docs describe no batch version of this endpoint, so a list of LinkedIn URLs means one call per URL. Stay under 100 requests per second and honour Retry-After on a 429.

If you want people matching a filter rather than a list you already hold, use POST /v1/exports. One export row is 1 record whatever the columns, up to 50,000 rows, and it needs the search scope plus enrich for email columns. It takes filters, not a list of URLs.

Email columns are numbered: email_N_address, email_N_type, email_N_status and email_N_verified_date come back as email_1_address, email_2_address and so on. caps sets how many per person (default 3). An export lists every address we hold, so read email_N_type and email_N_status to pick the work one.

bash
curl -X POST https://api.leadocean.io/v1/exports \
  -H "x-api-key: $LEADOCEAN_API_KEY" -H "content-type: application/json" \
  -d '{
    "name": "VP Sales, work email verified",
    "filters": { "title": ["VP Sales"], "country": ["US"], "emailType": ["work"], "emailStatus": ["verified"] },
    "limit": 5000,
    "columns": ["person_id", "profile_url", "email_N_address", "email_N_type", "email_N_status"]
  }'

The reply is a 202 with a job id. Poll GET /v1/exports/{id}, then fetch /v1/exports/{id}/download. The Exports page at app.leadocean.io builds the same file.

FAQ

Does a LinkedIn URL that returns null cost a record?

Yes. A 200 with data: null costs 1 record, because the lookup ran. A 404, where we hold no record for the profile, costs nothing. The person exists but no address passed the status rule, so a retry will not change it.

Can I run it from an AI agent without a key?

Yes. The MCP server at https://api.leadocean.io/mcp signs in with OAuth 2.1, so no key is pasted. There is no separate work email tool: use leadocean_get_lead with linkedin_url and reveal_email set to true, 1 record. A miss there costs nothing. Setup is in LinkedIn to Email over MCP.

Can I go from an email back to the LinkedIn profile?

Yes. GET /v2/people/reverse takes an email and returns the person and their profile URL. Code is in the Email to LinkedIn API, and the overview is email to LinkedIn.

Get your LinkedIn to email API key free

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

Get your free API key →