Pre-Black Friday: 20% off, for as long as you stayEARLYBFon Solo and AgencyEnds Nov 28Start free

How-to guide

How to Enrich a Lead in Real Time With a People Data API (Signup to Scored Record)

One request per lead, a 404 that costs nothing, and an email status you can branch on. Real queries and the response shape.

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

Real-time enrichment means one API call per lead, made when the lead arrives, with the answer back before your next step runs. The loop below does that with one request per person.

"Real time" here means on request. The dataset is refreshed monthly and each record carries its own fetched_at date. Read it before you trust a field that changes often, such as a job title.

TL;DR

  • Enrich one person with POST /v1/people/enrich, using exactly one of linkedin_url, email, phone or person_id.
  • It takes about 30 minutes to wire up. Each enriched lead spends 1 record. Sizing, misses and the 404 case spend 0.
  • Branch on contact_data.email_status before anything gets sent. No refund exists for bounced addresses.

Prerequisites

  • A LeadOcean account on the Free plan: 1,000 records for life, no card, 5 requests a second. Register at app.leadocean.io.
  • An API key from the app, stored as $LEADOCEAN_API_KEY.
  • curl and jq, plus Python 3 with requests for Step 4.
  • A source of leads: a signup form, a webhook or a CSV. Anything that gives you an email or a LinkedIn URL.

Step 1: Size your audience for free

Count before you spend. A count costs 0 records and tells you whether the ICP exists in the data. Enum values are exact strings, so list them first.

bash
curl -s "https://api.leadocean.io/v1/enums/job_level" \
  -H "x-api-key: $LEADOCEAN_API_KEY" | jq -c '.'

Then count with the search endpoint. Send count=true as a query parameter, not in the JSON body, with limit 1.

bash
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": ["US"],
    "limit": 1
  }' | jq '.meta | {total, totalIsExact, countOnly}'

meta.total is capped at 100,000. Inside an agent, the free leadocean_count_leads MCP tool gives the exact figure with no cap.

Count first for one reason: it stops you buying a list that does not exist. A VP-level sales audience in the US is large. A narrow title in a small country may be a few hundred people, and you want to know that before you build a flow around it.

Step 2: Enrich one lead by email

Send one identifier. Here it is an email from a signup form. Set reveal_email to true to get every address and phone we hold. It costs no extra record.

bash
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
  }' > lead.json

jq '{status: .data.contact_data.email_status, role: .data.contact_data.contact_current_experiences[0]}' lead.json

A hit spends 1 record. A person we hold nothing for is a 404 and spends nothing, so a bad email on your form never costs you.

Step 3: Branch on the result

Do not treat every 200 as sendable. Read email_status and route the lead. Verified and catch-all-valid addresses can go to a sequence. Everything else goes to a different path.

bash
STATUS=$(jq -r '.data.contact_data.email_status // "none"' lead.json)

case "$STATUS" in
  verified|catch_all_valid) echo "route: send" ;;
  catch_all)                echo "route: send, low volume" ;;
  *)                        echo "route: hold, find another contact" ;;
esac

The mailable default in the LeadOcean tools is verified, catch_all_valid and catch_all. Other values include risky, unknown, invalid, role and disposable.

Step 4: Find the person when you only have a company

A signup often gives you a work domain and no person. There is no name-plus-domain lookup. Search by domain first, then enrich by person_id.

python
import os, requests

BASE = "https://api.leadocean.io"
H = {"x-api-key": os.environ["LEADOCEAN_API_KEY"], "content-type": "application/json"}

r = requests.get(f"{BASE}/v1/companies/acme.com/people", headers=H, params={"limit": 5})
rows = r.json()["data"]
person_id = rows[0]["meta"]["source_ids"]["person_id"]

e = requests.post(f"{BASE}/v1/people/enrich", headers=H,
                  json={"person_id": person_id, "reveal_email": True})
print(e.status_code, e.json()["data"]["contact_data"].get("email_status"))

The people search call spends 1 record per person returned. The enrich call spends 1 more.

Pick the right row before you enrich. The search row spends a record, so a limit of 5 costs 5. Filter by title or seniority on the people search endpoint when you want one role.

Step 5: Handle rate limits and misses in a loop

For a queue of leads, respect the plan's rate limit and read Retry-After on a 429. Free is 5 requests a second. Agency is 200.

python
import time

def enrich(email):
    for attempt in range(5):
        r = requests.post(f"{BASE}/v1/people/enrich", headers=H,
                          json={"email": email, "reveal_email": True})
        if r.status_code == 200:
            return r.json()["data"]
        if r.status_code == 404:
            return None
        if r.status_code == 429:
            time.sleep(float(r.headers.get("Retry-After", 1)))
            continue
        r.raise_for_status()
    return None

Run the loop from a queue worker, not from the signup request itself. The signup page should return at once, and the worker enriches in the background a second later. That is still real time for a sales team.

A 402 means the records are spent. On Free that is a hard stop. Paid plans have a fair-use pace instead of credits, so the key slows down and meta.notice names the window.

A common mistake is to retry a 404. A 404 on a clean email means the person is not in the data. It will not change on a retry, so drop the lead to a manual path or enrich by linkedin_url instead.

Step 6: Use career history to score the lead

Since October 2026 the enrich response carries resume_data: past roles, education, certifications and awards, each when held. Past employers have no company_id yet, so match on the company name.

bash
jq '.data.resume_data.experiences[:3]' lead.json

A first-time VP with two earlier sales leadership roles scores differently from a first-time manager. Score on what the fields hold, and skip any field that is absent.

Keep the score simple at first. Three signals are enough: current job level, years in the current role and whether a past role was in the same function. Tune the weights only after you see which leads reply.

Step 7: Cache the answer and skip repeats

Store the response against the lead id the first time you enrich. A second call for the same person spends another record, so check your own table first.

sql
SELECT 1 FROM enriched_leads WHERE email = 'jane.doe@acme.com' AND fetched_at > now() - interval '30 days';

The dataset refreshes monthly, so a 30-day cache loses nothing. Re-enrich only when a lead reactivates after that window or when a rep reports a bounce.

What you get

Step 1 ran through the free leadocean_count_leads tool on 2026-10-21. The filters were jobLevel VP, jobFunction Sales & Business Development and country US.

json
{"total":129494,"totalIsExact":true,"totalAll":350517,"distinctCompanies":58121,"credits":0}

That is a mailable count: the tool defaults to verified, catch_all_valid and catch_all addresses. totalAll is the same filters with no email condition. The call spent 0 records.

An enrich response has this shape. Response shape from the API reference (placeholder values):

json
{
  "success": true,
  "data": {
    "profile_data": { "profile_full_name": "Jane Doe" },
    "contact_data": {
      "has_email": true,
      "has_phone": true,
      "email_status": "verified",
      "email_type": "work",
      "contact_emails": [
        { "email": "jane.doe@acme.com", "type": "work", "status": "verified", "priority": 1 }
      ],
      "contact_phones": [
        { "phone": "+1 415 555 0133", "type": "mobile", "priority": 1 }
      ],
      "contact_current_experiences": [ { "job_title": "VP Sales", "company_name": "Acme" } ]
    },
    "resume_data": { "experiences": [], "educations": [] }
  },
  "meta": { "credits": 1 }
}

Write contact_current_experiences[0] to your CRM as the current role. Write email_status next to the address so reps see the verdict, not just the address.

The email and phone arrays appear only when reveal_email is true. has_email and has_phone answer either way.

Cost of the whole loop

The loop in this guide spends 1 record per lead that is found. A test of three leads spends 3 records, and a miss spends 0. On Free you can enrich up to 1,000 people, one time. At 10,000 leads a month a team will want Solo at $97 a month, or Agency at $497 a month for teams and 200 requests a second.

Bulk work does not need a loop. Past a few thousand leads, create a CSV with POST /v1/exports, up to 50,000 rows a request and 500,000 a day. Bulk CSV export is not on Free.

Troubleshooting

ErrorCauseFix
401Missing, wrong or revoked keyRe-copy the key from app.leadocean.io. The header is x-api-key.
429Above the plan's rate limit (5 a second on Free, 200 on Agency)Wait for Retry-After, then retry. Queue requests at the plan's rate.
402Fewer records left than the request needsCheck GET /v1/account, or move to Solo or Agency.
404 on enrichNobody matched the identifier, or no enrichable identifier is heldCosts nothing. Try linkedin_url or search by domain, then enrich by person_id.
Empty search resultsA filter value outside the list, such as Vice PresidentUse VP. Check GET /v1/enums/job_level.
400 on enrichMore than one identifier in the bodySend exactly one of linkedin_url, email, phone or person_id.

FAQ

Is this real-time data?

It is on-request enrichment, not a live crawl. The dataset is refreshed monthly and each record carries its own fetched_at date. Check that date for fast-changing fields.

What does one enrichment cost?

One record when a person is found. A 404, a count and an empty page cost nothing. Free gives you 1,000 records for life. Solo is $97 a month and Agency is $497 a month, with a fair-use pace instead of credits. See pricing.

Can I enrich from a name and a company domain?

Not in one call. Search by domain, pick the person, then enrich by person_id. The people data API page shows the endpoints side by side.

Can an AI agent do this instead of code?

Yes. The hosted MCP server has 13 tools, including free counting. The same enrich logic works in Claude, as in the recruiting tools guide. For post-level signals, see the social posts API guide.

Enrich your first lead free, in about 30 minutes

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

Get your free API key →