Documentation

Enrich a person

Hand us a LinkedIn profile URL, a work email or a phone number and get the whole person back — verified contact points, current role, work history, education and skills — as leadocean.person.v1.

The endpoint

POST /v1/people/enrich · 1 record · scope enrich

One person per call. Send exactly one identifier — a LinkedIn profile URL, a work email, a phone number or a person_id — and sending two is a 400 that names all four. Returns the full leadocean.person.v1 record. A person we hold nothing for is a 404, and a 404 costs nothing.

Enrichment returns contact points, so it needs a person we hold some for. A search row whose has_email and has_phone are both false has nothing to return by any identifier — person_id is neither better nor worse than a LinkedIn URL there. Checking those two flags before you spend a record is the cheapest filter you have. A small number of contactable rows will still 404; that also costs nothing.

FieldTypeMeaning
linkedin_urlstring (URL)LinkedIn profile URL. The one to reach for — it is unambiguous.
emailstringWork email address. Use it when you have an address and want the person behind it.
phonestringA number you already hold, in any common format — the reverse direction: who is this.
person_idstring of digitsThe numeric id we hold for a person, published at meta.source_ids.person_id on every people-search row and on an enriched record. Send it back exactly as it was returned. It is the only identifier that reaches the roughly one person in five we hold no LinkedIn URL for, since search returns email flags rather than addresses.
reveal_emailboolean · default falseInclude every email address and phone number we hold. No extra record.

Revealing contact points

Email addresses and phone numbers are withheld unless you ask for them: set reveal_email: true and contact_data.contact_emails[] and contact_data.contact_phones[] come back filled in. It costs no extra record — the call is one record either way — so set it whenever you actually want the contact points.

Without it you still get has_email and email_status, which is enough to decide whether a person is worth revealing at all. Each address carries its own type, a status, a verified_batch_date and the source’s score, best address first — and email_type repeats the type of that best address as a flag, so search and enrichment answer the question the same way.

Read type before you send. work means the address is at the company the person works at now. work_other is a business address at a different company — usually one they have left, so it is the most likely to bounce or to reach whoever inherited the mailbox. personal is webmail. The emailType search filter takes the same values, and work_any asks for the first two together — deliberately, rather than by accident.

The statuses are verification results, not guesses. verified is the one to send to, and so is catch_all_valid: the domain accepts everything, but we confirmed this particular mailbox exists through the provider's identity check (Microsoft 365 managed tenants and Google Workspace). catch_all means the domain accepts everything and nothing more is known, so it tells you little either way. invalid, spam_trap, abuse and disposable are addresses to drop rather than try. risky, role (a shared mailbox like info@) and derived are for you to decide about. untested means nobody has checked this address yet — it is the largest group, it is not a negative signal, and it shrinks with every verification batch. unknown is different: we did check and could not tell either way, which is a weaker signal than untested rather than a stronger one. The full set, with what each one means, is published live on the filter reference rather than listed here, because it grows.

When we checked is now on every address. verified_batch_date is the verification run the status came from, as YYYY-MM-DD. A whole batch shares one date, so it is not a timestamp for that particular address — millions of them read 2026-09-20. Use it to judge how stale a verdict is, never to decide whether one exists: status already answers that. null does not mean unverified — it means we did not run the check ourselves and the status came from a source we hold to the same standard, so a verified address with no date is still verified.

verified means somebody checked. Since 20 September 2026 we publish verified and catch_all only where a deliverability check actually happened — our own verifier, or a source we hold to the same standard. A data supplier telling us an address is good is not a check, and those addresses are published as untested instead. That made the verified population smaller and every address in it tested, so a count you took before that date will not match one you take after. Nothing was removed; the difference moved to untested and is first in the queue for the next verification round.
Nothing is invented to fill a gap. A field we do not hold is null, and if contact_emails comes back empty we do not have one. Where an address was built from a company’s pattern rather than observed, it says so — status: derived — and it is never counted as verified.

The person schema

leadocean.person.v1 is stable across sources and versions. Unknown values are null; dates are YYYY-MM-DD strings.

profile_dataprofile_id, profile_url, first / last / full name, headline, summary, picture, languages, address (city, state, country, country_code), status, tags, expertises, metrics (including connections_count), profile_is_decision_maker, last modified and last seen dates
contact_datacontact_emails[] — each with status and verified_batch_date — and contact_phones[] (only with reveal_email), contact_current_experiences[], still-at-company status, has_email, email_status, email_type
resume_dataexperiences[], educations[], certifications[], awards[], skills[]
metakey, key_hash, sources[], fetched_at, schema

Experience objects carry company_name, company_domain, company_industry, company_employees, job_title and its normalised form under job_title_details, job_seniority, job_functions[], start and end dates and a current flag.

Search and enrich return the same person. An enriched record adds contact points — emails and phone numbers — and is otherwise the record you would have seen in a search result, field for field.

Worked example

Enrich one person by LinkedIn URL, with contact points revealed.

curl -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","reveal_email":true}'
200 OK · application/json
{
  "success": true,
  "data": {
    "profile_data": {
      "profile_full_name": "Sarah Chen",
      "profile_headline": "Founder & CEO at CloudSync",
      "profile_url": "https://www.linkedin.com/in/…"
    },
    "contact_data": {
      "has_email": true,
      "email_status": "verified",
      "contact_emails": [
        { "email": "sarah@cloudsync.com", "type": "work", "status": "verified" }
      ],
      "contact_phones": [
        { "phone": "+1 415 555 0189", "type": "mobile" }
      ],
      "contact_current_experiences": [{
        "company_name": "CloudSync",
        "company_domain": "cloudsync.com",
        "job_title": "Founder & CEO",
        "company_employees": { "number_of_employees": 120 }
      }]
    }
  },
  "meta": { "credits": 1, "source": "own" }
}

Errors, status codes and rate limits are on records, limits and errors. To do this from Claude or ChatGPT instead of code, see MCP for agents.