GET /v2/people/email/work takes one LinkedIn URL or person id and returns one work address 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 data is and how we check it, read Find a work email. All lookups are listed in the Enrichments hub.
Quick start (curl)
Create a free account, copy a key with the enrich scope and export it as LEADOCEAN_API_KEY.
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"Have a person_id from a search row instead? Send it as contact_id:
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)
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 person. 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})` : "No qualifying address");
}Python (requests)
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 person. Not billed.")
else:
r.raise_for_status()
data = r.json()["data"]
print(f"{data['email']} ({data['status']})" if data else "No qualifying address")Request
Send exactly one identifier. Sending both, or neither, is a 400. The key needs the enrich scope.
| Parameter | Type | Required | Meaning |
|---|---|---|---|
linkedin_url | string (URL) | One of the two | A personal profile URL, linkedin.com/in/... |
contact_id | string (digits) | One of the two | The id at meta.source_ids.person_id on a people-search row. Works for the one person in five with no LinkedIn URL. |
person_id | string (digits) | Alias | The 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. To go from a name and a company, search by title and domain first, then send the row's id.
Response
Response shape from the API reference. The address below is a placeholder.
{
"success": true,
"data": {
"email": "jane.doe@acme.example",
"status": "verified",
"verified_batch_date": "2026-09-14"
}
}| Field | Meaning |
|---|---|
data | One address object, or null when no address qualifies. null is a 200, not a 404. |
data.email | The work address, at the domain of the person's current employer. |
data.status | verified, catch_all_valid or catch_all. Nothing else is returned here. |
data.verified_batch_date | The date 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 work email". 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.
| Status | Meaning | What to do |
|---|---|---|
| 400 | Both identifiers sent, neither sent, or a field failed validation. | Read details, send one identifier. |
| 401 | Missing, wrong or revoked key. | Check the x-api-key header. |
| 402 | Free plan: the 1,000 records are spent. They do not reset. | Upgrade. |
| 403 | The key lacks the enrich scope, or the account is suspended. | Create a key with the scope. |
| 404 | We hold no record for this person at all. | Stop. It costs nothing. Try the other identifier. |
| 429 | Over 100 requests a second, or 1 a minute on a paid account past its records. | Wait for Retry-After, then retry. |
| 503 | The store behind the lookup is unreachable (code: no_source). | Retry with backoff. |
A 404 and a data: null 200 are different facts. The 404 means we hold nothing, so retry with the other identifier. The null means the person exists and no address qualifies, and a retry will not change it.
Rate limits and cost
One call is 1 record. A 200 with data: null still costs 1 record, because the lookup ran. A 404, 400, 401, 402, 403, 429 or 503 costs nothing. 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, email_status and email_type, so you can skip people who hold no address.
Bulk
Past a few thousand lookups, use POST /v1/exports instead of looping. One export row is 1 record whatever the columns. An export takes up to 50,000 rows and needs the search scope plus enrich for email columns.
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 for the person, not one winner, so read email_N_type and email_N_status to pick the work address.
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. Prefer clicks? The Exports page at app.leadocean.io builds the same file.
FAQ
Does a call 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 person, costs nothing. Filter on has_email in search first to avoid paying for empty answers.
Can I find a work email by name and company domain?
Not on this endpoint. There is no name lookup. Search by title and company domain, then send the row's contact_id here. For a walkthrough in an editor, see find work emails in Windsurf.
Can an agent call it without an API 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 reveal_email set to true, 1 record, and pick the work address from the list. A miss there costs nothing. To run a list from Claude, use the Find Work Email skill.
Get your work email API key free
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →