TL;DR
- Profile enrichment takes one identifier (a LinkedIn URL, an email, a phone number or a person_id) and returns the person's role, employer, contact points and career history.
- It takes about 15 minutes and spends about 4 records. Sizing the list first, and any enrichment that finds nobody, cost nothing.
- You will count a list, search it, enrich one person by person_id and read the fields you can trust.
Prerequisites
- A LeadOcean account. The free plan gives you 1,000 records for life, no card, 5 requests a second. Start free.
- An API key from app.leadocean.io, exported as
$LEADOCEAN_API_KEY. curlandjq.- Placeholder values below (Acme, acme.com, Jane Doe) stand in for your own data.
What profile enrichment is
Profile enrichment is a lookup. You hold a thin record, such as a name on a LinkedIn URL, and the API fills in the rest: current title, employer, seniority, email status, phones and past roles.
The identifier is the key. LeadOcean takes exactly one of linkedin_url, email, phone or person_id. There is no name plus domain lookup. If you only have a company and a job title, search first, then enrich the hit.
That makes the workflow three steps: size the list, search it, enrich the people you actually want. Counting is free. Search and enrich cost one record per person, so you pay only for the people you pick.
Step 1: Size the list for free
Count before you spend. Send count=true as a query parameter, never in the JSON body, and limit 1 in the body.
curl -s -X POST "https://api.leadocean.io/v1/people/search?count=true" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jobLevel": ["VP"],
"jobFunction": ["Sales & Business Development"],
"country": ["CA"],
"limit": 1
}' | jq '.meta | {total, totalIsExact, distinctCompanies, countOnly}'The data array comes back empty and meta.countOnly is true. meta.total is capped at 100,000, so check totalIsExact before you quote it. meta.distinctCompanies tells you how many employers the matches span.
Enum values are exact strings. If you are unsure of one, call GET /v1/enums or the free MCP tool leadocean_list_enum_values first.
Step 2: Search for the people to enrich
Search returns a thin row per person and costs one record per row. Keep limit small while you test.
curl -s -X POST "https://api.leadocean.io/v1/people/search" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jobLevel": ["VP"],
"jobFunction": ["Sales & Business Development"],
"country": ["CA"],
"limit": 3
}' > search.json
jq '.meta.nextCursor' search.jsonThe body is flat JSON and filters are arrays. limit runs 1 to 100, and you page with cursor: pass back exactly the meta.nextCursor string. One search pages to 10,000 rows, then meta.depthCapped is true. Narrow the filters instead of walking the index.
Each row carries meta.source_ids.person_id. About a fifth of the people search finds have no LinkedIn URL, and search rows carry email flags, not addresses. The person_id lets you enrich them anyway.
Step 3: Enrich one person by person_id
Send the id from the search row. Add reveal_email to get the addresses and phones we hold. It costs no extra record.
PERSON_ID=$(jq -r '.data[0].meta.source_ids.person_id' search.json)
curl -s -X POST "https://api.leadocean.io/v1/people/enrich" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"person_id\": \"$PERSON_ID\", \"reveal_email\": true}" \
| jq .You can enrich from the identifier you already hold instead:
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}'Send one identifier per call. A person we hold nothing for returns a 404 and costs nothing.
Step 4: Read the fields you can trust
Check contact_data.email_status before you send anything. LeadOcean does not refund bounces.
curl -s -X POST "https://api.leadocean.io/v1/people/enrich" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email": "jane.doe@acme.com", "reveal_email": true}' \
| jq '.data.contact_data | {has_email, has_phone, email_status, email_type}'Treat verified as sendable. catch_all_valid and catch_all are mailable but riskier. risky, unknown, invalid, role, disposable and spam_trap are not.
Since October 2026 the same call returns resume_data: past roles, education, certifications and awards, each when held. Past employers do not carry a company_id yet, so you cannot join them to company records.
When to enrich, and when not to
Enrich late. A search row is enough to qualify a person by title, level, country and employer. Spend the enrichment record only on people who pass that filter.
Enrich on a trigger, not on a schedule. A new signup, a form fill or a CRM record with only an email are good triggers. Re-enriching a whole table every week spends records on people who did not change.
Do not expect a lookup from a name alone. The call needs an identifier. A name without a LinkedIn URL, email, phone or person_id gets you nothing.
How the three calls differ
| Call | Input | Returns | Records |
|---|---|---|---|
Count (count=true, limit 1) | Filters | A total and breakdowns | 0 |
| Search | Filters | Thin rows with a person_id | 1 per row |
| Enrich | One identifier | The full person record | 1 per person found |
The pattern is count, search, enrich. Count tells you whether the list is worth building. Search gives you the ids. Enrich reads the people you picked.
What you get
This is the response shape from the API reference, with placeholder values. It is not a real person.
{
"success": true,
"data": {
"profile_data": { "...": "name, headline, location" },
"contact_data": {
"contact_emails": [ { "...": "address, status" } ],
"contact_phones": [ { "phone": "+1 415 555 0133" } ],
"has_email": true,
"has_phone": true,
"email_status": "verified",
"email_type": "work",
"contact_current_experiences": [ { "...": "title, company, dates" } ]
},
"resume_data": { "...": "past roles, educations, certifications, awards" },
"meta": {}
},
"meta": { "source": "leadocean" }
}contact_emails and contact_phones appear only when you send reveal_email. has_email and has_phone always appear, so you can see whether a contact exists before you ask for it. The current role sits in contact_current_experiences, past roles in resume_data.
Doing it from an AI client
The hosted MCP server at https://api.leadocean.io/mcp signs in with OAuth, so no key is pasted. It exposes 13 tools. For this workflow:
leadocean_count_leadssizes a people list for free.leadocean_search_leadsreturns rows, one record each.leadocean_get_leadenriches one person, one record. A miss costs nothing.
Ask your client for "count VPs of sales in Canada, then enrich the first three". It will count first, because the count is free. For companies, leadocean_count_companies does the same for free.
Doing it in bulk
For thousands of profiles, do not loop enrich calls. Export instead. POST /v1/exports takes up to 50,000 rows a request and 500,000 a day. The Exports page in app.leadocean.io has a filter builder, a column picker and a price preview before you start. Bulk CSV export is not on the Free plan. See plans.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing or wrong x-api-key header | Check $LEADOCEAN_API_KEY is exported and the header is x-api-key. |
| 402 | Records spent. Free is 1,000 for life | Upgrade to Solo ($97 a month) or Agency ($497 a month). A 402 costs nothing. |
| 429 | Above the plan's rate limit: 5 a second on Free, 200 on Agency | Wait for Retry-After, then retry. Space calls out or move up a plan. |
| 404 on enrich | We hold nothing for that identifier, or nothing we can enrich by | Try another identifier. A 404 is free and will not change on a retry. |
| Empty search results | A bad enum value, such as VP spelled as vp | Call GET /v1/enums and copy the exact string. |
Invalid cursor (400) | Edited, stored or reused cursor | Pass back meta.nextCursor unchanged, or restart the query. |
FAQ
What is the difference between profile enrichment and company enrichment?
Profile enrichment adds person-level data: title, seniority, contact points, career history. Company enrichment adds firmographics: industry, headcount, HQ. See company enrichment. LeadOcean has a separate endpoint for each: /v1/people/enrich and /v1/companies/enrich.
Can I enrich from a name and a company?
No. There is no name plus domain lookup. Search by domain or filters first, then enrich by person_id.
How many records does an enrichment cost?
One, when it finds a person. A 404 costs nothing. reveal_email adds every email and phone we hold at no extra record. See pricing.
How is this different from a lead enrichment API?
It is the same idea at a narrower scope: a lead enrichment API may append firmographics, scores and intent as well. See lead enrichment APIs.
Can an agent run this?
Yes, through the MCP tools above. See RevOps AI agents. Keep the free count call first in the loop so the agent sizes before it spends. More guides are on the blog.
Enrich your first profile with a free API key
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →