Enrichment API

Reverse Email Lookup API

GET /v2/people/reverse. One email in, the person who holds it out. 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/reverse takes one email address and returns the person who holds it: name, LinkedIn URL, title and employer. It costs 1 record per call. A 404, where nobody holds the address, costs nothing.

This page is the developer reference. For what the data is and where it is thin, read Reverse email lookup. Agents call the same lookup through the leadocean_get_lead MCP tool.

Quick start (curl)

Create a free account, copy a key with the enrich scope and export it as LEADOCEAN_API_KEY. The address below is a placeholder.

bash
curl -s -G "https://api.leadocean.io/v2/people/reverse" \
  --data-urlencode "email=jane.doe@acme.com" \
  -H "x-api-key: $LEADOCEAN_API_KEY"

The endpoint also answers POST with a JSON body:

bash
curl -s -X POST "https://api.leadocean.io/v2/people/reverse" \
  -H "x-api-key: $LEADOCEAN_API_KEY" -H "content-type: application/json" \
  -d '{ "email": "jane.doe@acme.com" }'

Node.js (fetch)

js
const url = new URL("https://api.leadocean.io/v2/people/reverse");
url.searchParams.set("email", "jane.doe@acme.com");

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

if (res.status === 404) {
  console.log("Nobody holds this address. 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.full_name, data.job_title, data.company_domain);
  console.log(data.matched_in_record ? "Exact address on record" : "Matched upstream");
}

Python (requests)

python
import os
import requests

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

if r.status_code == 404:
    print("Nobody holds this address. Not billed.")
else:
    r.raise_for_status()
    data = r.json()["data"]
    print(data["full_name"], data["job_title"], data["company_domain"])

Request

Send exactly one of email or phone, never both. The key needs the enrich scope.

ParameterTypeRequiredMeaning
emailstring (email)One of the twoThe address to look up.
phonestringOne of the twoA number in any common format. Same endpoint, same price. See Reverse Phone Lookup API.

There is no lookup by name and company. Search by company domain first, then enrich by person_id.

Response

Response shape from the API reference. Every value below is a placeholder.

json
{
  "success": true,
  "data": {
    "person_id": "1234567",
    "person_group_id": "1234567",
    "linkedin_url": "https://www.linkedin.com/in/jane-doe",
    "full_name": "Jane Doe",
    "job_title": "Finance Manager",
    "company_name": "Acme",
    "company_domain": "acme.com",
    "query":   { "email": "jane.doe@acme.com" },
    "matched": { "email": "jane.doe@acme.com", "status": "verified",
                 "verified_batch_date": "2026-09-14", "type": "work" },
    "matched_in_record": true
  },
  "meta": { "source": "leadocean", "key_hash": "...", "schema": "leadocean.contact.reverse.v2",
            "stability": "stable", "credits": 1 }
}
FieldMeaning
data.person_idThe record that answered. Send it back as contact_id to the other v2 endpoints.
data.person_group_idThe person across duplicate records. Compare it, never look it up.
data.linkedin_url, full_name, job_title, company_name, company_domainThe identity. Each can be null.
data.queryThe address you sent, as we compared it.
data.matchedThe address as the record holds it, with its own type, status and check date. Null when the record does not echo it back.
data.matched_in_recordtrue when the record lists the exact address. false means a match upstream, not "nobody found".
meta.creditsRecords counted for this call.

This is the one v2 endpoint that sends meta. It returns the identity only. For the full profile, call POST /v1/people/enrich, which costs the same and reads the same cached record.

Errors

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

StatusMeaningWhat to do
400Validation failed. details names the fields.Send exactly one of email or phone.
401Missing, wrong or revoked key.Check the x-api-key header.
402Free plan: the 1,000 records are spent. They do not reset.Upgrade. Not billed.
403The key lacks the enrich scope, or the account is suspended.Create a key with the scope.
404No person holds this address.Stop. It costs nothing.
429Over 100 requests a second, or 1 a minute on a paid account past its records.Wait for Retry-After, then retry. Not billed.
503The store behind the lookup is unreachable (code: no_source).Retry with backoff. Never billed.

Rate limits and cost

A call that returns a person is 1 record, and a 200 with sparse fields still costs 1. A 404, 402, 429 or 503 costs nothing.

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.

Past a paid account's records, calls are paced to 1 a minute and X-Quota-State: over-limit appears in the headers. Alert on that header.

Bulk

POST /v1/exports is the bulk path, but it is built from filters. The docs describe no way to upload a list of emails to it. A list of addresses means one call here per address.

If you want people rather than a lookup, export by filter instead. One row is 1 record whatever the columns, up to 50,000 rows per export. Email columns are numbered: you request email_N_address and email_N_status, and the CSV comes back as email_1_address, email_2_address and so on.

bash
curl -X POST https://api.leadocean.io/v1/exports \
  -H "x-api-key: $LEADOCEAN_API_KEY" -H "content-type: application/json" \
  -d '{
    "name": "Finance managers with an email",
    "filters": { "title": ["Finance Manager"], "country": ["US"], "hasEmail": true },
    "limit": 5000,
    "columns": ["person_id", "profile_url", "email_N_address", "email_N_status"],
    "caps": { "emails": 2 }
  }'

The reply is a 202 with a job id. Poll GET /v1/exports/{id}, then fetch /v1/exports/{id}/download.

FAQ

Does a lookup that finds nobody cost a record?

No. A 404 costs nothing. A 200 that names a person costs 1 record, even when most fields are null.

Can I look up by name and company?

No. Search people by company domain and title, then call POST /v1/people/enrich with the person_id.

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. Pass email to leadocean_get_lead, 1 record, and a miss is free. To pick a tool first, read the best reverse email lookup tools.

Get your reverse email lookup API key free

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

Get your free API key →