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

How-to guide

How to Manage B2B Data With AI (A Five-Step Pipeline Against a Live API)

Most guides describe AI for B2B data. This one runs it: real queries, real output, and a record count for every step.

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

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.
  • curl and 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.

bash
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.

bash
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.json

Limit 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.

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 '{"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.

python
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.

text
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.

json
{
  "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.

json
{
  "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

ErrorCauseFix
401Missing or wrong x-api-key headerRe-export $LEADOCEAN_API_KEY and check you did not paste the key with quotes or a trailing space.
429Above the plan's rate limit: 5 a second on Free, 200 on AgencyHonour the Retry-After header, or add a sleep between calls.
402Out of records. The Free plan's 1,000 are one-offUpgrade to Solo ($97 a month) or Agency ($497 a month). See pricing.
Empty resultsA filter value is not an exact enum string, such as director where Director is expectedLook up the exact value with leadocean_list_enum_values.
404 on enrichNobody matched the person_id, email, phone or linkedin_urlNothing was charged. Try another identifier.
Export refusedA request is over 50,000 rows, or the day is past 500,000 rowsSplit 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 →