B2B data management comes down to five jobs: size the list, pull it, enrich it, gate it, and refresh it. AI earns its place on the last four, as an agent that calls an API, not as a layer of insight on top.
This page is the short version a technical buyer can run: every step is a request or a prompt, with its record cost.
TL;DR
- Five steps: size a list for free, pull a page, enrich by id, gate on email status, then hand the loop to an agent over MCP. About 30 minutes the first time.
- Sizing costs 0 records. The whole walkthrough spends at most 50 of the 1,000 records on the Free plan.
- Every step below is a request or prompt you can paste. Only the output in "What you get" is labelled as a shape, not a real record.
Prerequisites
- A LeadOcean account. Free is $0, needs no card and gives you 1,000 records for life and 5 requests a second.
- An API key in your shell:
export LEADOCEAN_API_KEY=.... Create it in the app at app.leadocean.io. curland Python 3 for steps 1 to 4.- An MCP client for step 5 (Claude, Claude Code or any client that supports remote MCP with OAuth).
Step 1: Size the list before you spend anything
Sizing first is the habit that saves the most records. A count is free on the API and on MCP.
Send count=true and limit=1 as query parameters. Put the filters in a flat JSON body, as arrays of strings.
curl -s -X POST "https://api.leadocean.io/v1/people/search?count=true&limit=1" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jobLevel": ["Director"],
"jobFunction": ["Operations"],
"country": ["US"]
}'meta.total is capped at 100,000 on this call, and data comes back empty with meta.countOnly true. For an uncapped, exact number with breakdowns, use the MCP tool leadocean_count_leads. For companies, use GET /v1/companies/count or leadocean_count_companies. Both are free.
Run the count with and without the email filter. The gap is the part of the list you cannot mail. On the Director, Operations, US query in "What you get", that gap is large: 287,442 mailable against 816,447 in total.
Size the company side too. GET /v1/companies/count takes the company-search filters and returns an exact total with breakdowns by industry, headcount band, HQ country and HQ state. It tells you whether the list is 200 accounts or 20,000 before you plan the work.
Enum values are exact strings. If a filter returns nothing, check the spelling with leadocean_list_enum_values before you assume the data is missing.
Step 2: Pull one page and keep the id
Run the same filters without count. A page of 25 rows spends 25 records.
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": ["Director"],
"jobFunction": ["Operations"],
"country": ["US"],
"emailStatus": ["verified"],
"limit": 25
}' > page1.jsonLimit is 1 to 100 per page, and you paginate with cursor. One search pages to 10,000 rows. When meta.depthCapped is true there is no nextCursor, so narrow the filters instead of walking the index.
Pull one page first, not the whole list. Read the 25 rows by eye. If titles look wrong, tighten the title filter and run it again. A bad 25 costs 25 records. A bad 25,000 costs far more.
Keep each row's meta.source_ids.person_id. It is the key you send to enrich in step 3.
Step 3: Enrich by person_id, not by name
Enrichment takes exactly one of linkedin_url, email, phone or person_id. There is no name plus domain lookup, so search by domain first and enrich by id.
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_FROM_STEP_2", "reveal_email": true}'reveal_email is false by default and costs no extra record when you set it. A 404 means nobody matched and costs nothing.
Since 17 October 2026 the response also carries resume_data: past roles, education, certifications and awards, each when held. Past employers have no company_id yet, so match them by company name. The current role sits in contact_current_experiences[].
Step 4: Gate on email status and keep only fresh rows
Do not send to every address you receive. There are 13 email statuses, and the count tool treats verified, catch_all_valid and catch_all as mailable by default.
There is no refund for bounces, so read email_status before you send. The dataset is refreshed monthly, and each record carries its own fetched_at date. This script keeps the rows worth loading into your CRM.
import json, datetime
MAILABLE = {"verified", "catch_all_valid", "catch_all"}
MAX_AGE_DAYS = 45
def keep(rec, today=None):
# rec is the "data" object of one POST /v1/people/enrich response
today = today or datetime.date.today()
fetched = datetime.date.fromisoformat(rec["meta"]["fetched_at"][:10])
fresh = (today - fetched).days <= MAX_AGE_DAYS
return rec["contact_data"].get("email_status") in MAILABLE and fresh
records = json.load(open("enriched.json"))
seen, out = set(), []
for r in records:
person = r["meta"]["source_ids"]["person_group_id"]
if person in seen:
continue
seen.add(person)
if keep(r):
out.append(r)
print(f"{len(out)} of {len(records)} rows kept")
json.dump(out, open("clean.json", "w"), indent=2)Two rules hold up in practice. Never write back a row you could not verify, and never overwrite a field your team edited by hand. Store fetched_at next to every imported record so the next refresh knows what is stale.
Dedupe on person_group_id, not on email. person_id names a record, and one person can sit behind more than one record. Two records that share a person_group_id are the same person.
Step 5: Let an agent run the loop over MCP
The hosted MCP server is at https://api.leadocean.io/mcp. It signs in with OAuth 2.1, so you paste no key. It exposes 13 tools. Seven are free, including leadocean_count_leads, leadocean_count_companies, leadocean_list_filters, leadocean_list_enum_values and leadocean_get_account.
Give the agent a rule that keeps it cheap: count first, ask before spending.
You manage a B2B list through the LeadOcean MCP server.
1. Call leadocean_count_leads for: jobLevel Director, jobFunction Operations, country US.
2. Report the mailable total and the unfiltered total. Do not search yet.
3. If I approve, call leadocean_search_leads with limit 25 and emailStatus verified.
4. Stop and show me the 25 rows and the records spent.Connect the client once. Add https://api.leadocean.io/mcp as a remote server and approve the OAuth screen. Clients that support dynamic client registration can register themselves at https://api.leadocean.io/oauth/register.
The agent does the repetitive part: choosing filters, checking enum spellings and reporting the cost. You keep the one decision that spends records.
What you get
Step 1 on MCP returns a real count. This one is from 21 October 2026, run with leadocean_count_leads for jobLevel Director, jobFunction Operations, country US.
{
"total": 287442,
"totalIsExact": true,
"totalAll": 816447,
"distinctCompanies": 107847,
"distinctCompaniesIsExact": false,
"credits": 0,
"defaults": "Counted only addresses worth sending to (emailStatus: verified or catch_all_valid or catch_all)"
}total is the mailable count. totalAll is the same filters with no email filter. The call spent 0 records.
An enriched record has this shape. This is the response shape from the API reference, abbreviated, with placeholder values.
{
"success": true,
"data": {
"profile_data": { "profile_full_name": "Jane Doe" },
"contact_data": {
"email_status": "verified",
"contact_emails": [{ "email": "jane.doe@acme.com", "status": "verified" }],
"contact_phones": [{ "phone": "+14155550133", "type": "mobile" }],
"contact_current_experiences": [{ "job_title": "Director of Operations", "company_name": "Acme" }]
},
"resume_data": { "experiences": [{ "job_title": "Operations Manager", "company_name": "Acme" }] },
"meta": { "fetched_at": "2026-10-01", "source_ids": { "person_id": "123456", "person_group_id": "123456" } }
}
}Read the two numbers together. If total is a third of totalAll, most of the list has no address worth sending to. Decide then whether to narrow by function, add a phone step, or drop the segment. Making that call before a single row is pulled is the whole point of the pipeline.
The record shape shows why the gate in step 4 reads two fields. contact_data.email_status tells you if the address is safe to send to. meta.fetched_at tells you how old the check is. Neither one is a guess you have to make.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing or wrong x-api-key header | Re-export $LEADOCEAN_API_KEY and check you did not paste the key with quotes or a trailing space. |
| 429 | Above the plan's rate limit: 5 a second on Free, 200 on Agency | Honour the Retry-After header, or add a sleep between calls. |
| 402 | Out of records. The Free plan's 1,000 are one-off | Upgrade to Solo ($97 a month) or Agency ($497 a month). See pricing. |
| Empty results | A filter value is not an exact enum string, such as director where Director is expected | Look up the exact value with leadocean_list_enum_values. |
| 404 on enrich | Nobody matched the person_id, email, phone or linkedin_url | Nothing was charged. Try another identifier. |
| Export refused | A request is over 50,000 rows, or the day is past 500,000 rows | Split the pull into several requests and spread them over days. |
FAQ
Is AI doing the data work, or is it an API call?
Both. The data comes from the API. The AI is the agent that sizes lists, picks filters and checks status fields over MCP. Keep the spend decision with a person.
How many records does the whole pipeline cost?
At most 50 on the Free plan: 25 search rows and up to 25 enrichments. Sizing, filter lookups and enrich misses are free.
Why dedupe on person_group_id and not on email?
People change addresses and hold more than one. person_group_id names the person, while person_id names one record and is the key you send back to enrich.
How often should I refresh?
The dataset is refreshed monthly. Re-enrich the rows you still use, and drop any row whose fetched_at is older than your cut-off.
Does it replace a CRM?
No. LeadOcean has an app with an Exports page for CSV downloads. It has no sequencer, dialer or CRM. Load the clean file into the tools you already run. For a wider comparison, read best B2B databases, best B2B data providers and the UK list.
Size your first list for free and see what an agent can do with it
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →