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

How-to guide

How to Generate B2B Leads (From an ICP to a Mailable List)

Four steps, one filter set, one free sizing call. Written for people who would rather read a query than a pep talk.

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

You generate B2B leads by turning your ideal customer profile into filters, sizing the list before you spend anything, and pulling only the people who match. This guide does that with one filter set against the LeadOcean API.

TL;DR

  • Write the ICP as a flat JSON filter set, then check the size with a free count before you pull a single row.
  • Takes about 20 minutes. The sample spends 25 records. The full list spends one record per row.
  • On the Free plan you have 1,000 records for life, so a 2,090-row list needs Solo ($97 a month) for bulk CSV export.

Prerequisites

  • A LeadOcean account. The free plan gives you 1,000 records, no card.
  • An API key in your shell: export LEADOCEAN_API_KEY=...
  • curl and jq.
  • Optional: the LeadOcean MCP server at https://api.leadocean.io/mcp if you want to run steps 1 and 2 from Claude or ChatGPT.

Step 1: Write the ICP as filters

An ICP is a filter set, not a paragraph. Turn each sentence of your ICP into one field and keep it flat.

Example ICP: "VPs of sales at US software companies with 51 to 200 employees." In LeadOcean that is:

json
{
  "jobLevel": ["VP"],
  "jobFunction": ["Sales & Business Development"],
  "industry": ["Software Development"],
  "employeeRange": ["51-200"],
  "hqCountry": ["US"],
  "emailStatus": ["verified", "catch_all_valid", "catch_all"]
}

Two rules keep this honest. Every filter is an array of strings, and enum values are exact strings, so "VP" works and "vp" does not. The emailStatus line makes the list mailable. Drop it and you are counting people you cannot email.

Step 2: Resolve enum values before you guess

Industry has 534 values and country has 254. Guessing returns empty results. List the exact strings first.

From an MCP client, ask:

text
Use leadocean_list_enum_values for name=industry, q="software".
Then do the same for job_function and employee_range.

The tool is free and spends no records. It returns the exact labels, such as Software Development and Sales & Business Development. Copy them into your filter set unchanged.

Step 3: Size the list for free

Check the size before you pull anything. A people search with count=true and limit=1 costs nothing. Note that count=true goes in the query string, not the JSON body.

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": ["VP"],
    "jobFunction": ["Sales & Business Development"],
    "industry": ["Software Development"],
    "employeeRange": ["51-200"],
    "hqCountry": ["US"],
    "emailStatus": ["verified", "catch_all_valid", "catch_all"]
  }' | jq '.meta | {total, totalIsExact, distinctCompanies}'

meta.total is capped at 100,000, so a broad ICP shows a floor. If the number is too big, add a filter. If it is under 100, loosen one. Aim for a list you can actually work.

You can run the same count from MCP with leadocean_count_leads. For company-level sizing, GET /v1/companies/count and leadocean_count_companies are free as well.

Step 4: Pull a sample, then export

Pull 25 rows first and read them. If the titles look wrong, fix the filters now, not after you have spent the whole list.

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": ["VP"],
    "jobFunction": ["Sales & Business Development"],
    "industry": ["Software Development"],
    "employeeRange": ["51-200"],
    "hqCountry": ["US"],
    "emailStatus": ["verified", "catch_all_valid", "catch_all"],
    "limit": 25
  }' | jq '.meta, .data[0]'

Search returns a thin projection with no contact points. Page with meta.nextCursor. One search pages to 10,000 rows, then meta.depthCapped turns true and no cursor is issued. Narrow the filters instead of walking further.

When the sample looks right, export the whole list. Exports take up to 50,000 rows a request and 500,000 a day. Bulk CSV export is not on Free.

bash
curl -s -X POST "https://api.leadocean.io/v1/exports" \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "vp-sales-us-software-51-200",
    "limit": 2090,
    "filters": {
      "jobLevel": ["VP"],
      "jobFunction": ["Sales & Business Development"],
      "industry": ["Software Development"],
      "employeeRange": ["51-200"],
      "hqCountry": ["US"],
      "emailStatus": ["verified", "catch_all_valid", "catch_all"]
    }
  }'

Prefer a UI? The Exports page in the app has the same filter builder, a column picker, the record price before you start and a CSV download.

What you get

The free count for this ICP, run with leadocean_count_leads on 2026-10-21. We passed the first five filters. The tool's default email filter (verified, catch_all_valid or catch_all) stands in for the emailStatus line.

json
{
  "total": 2090,
  "totalIsExact": true,
  "totalAll": 5685,
  "distinctCompanies": 1204,
  "credits": 0
}

Read it like this. 2,090 people match and have an email worth sending to (verified, catch_all_valid or catch_all). Without the email condition the same filters match 5,685 people. They sit at 1,204 distinct companies, so about 1.7 buyers per account. The call cost 0 records.

A search row, response shape from the API reference, placeholder values:

json
{
  "success": true,
  "data": [
    {
      "profile_data": { "full_name": "Jane Doe", "headline": "VP Sales at Acme" },
      "contact_data": {},
      "resume_data": {},
      "meta": {}
    }
  ],
  "meta": { "count": 1, "limit": 25, "credits": 1, "nextCursor": "..." }
}

Troubleshooting

ErrorCauseFix
401Missing or wrong x-api-key headerCheck $LEADOCEAN_API_KEY is exported and the header name is x-api-key.
429Above the plan's rate limit (5 a second on Free, 200 on Agency)Wait for Retry-After, then slow the loop.
402Out of recordsFree is 1,000 records for life. Move to Solo ($97 a month) or Agency ($497 a month).
Empty dataAn enum value that does not match exactlyResolve it with leadocean_list_enum_values. "VP" is valid, "vp" is not.
meta.depthCapped is trueOne search stops at 10,000 rowsAdd filters and run several smaller searches.
Export refusedOver 50,000 rows a request or 500,000 a day, or Free planSplit the export. Bulk CSV needs Solo or higher.

FAQ

How many leads do I need?

Fewer than you think. A list you can personalize beats one you cannot. Start with the 25-row sample, then size the full list to what one rep or one sequence can work in a month.

Does the count spend records?

No. A count, a breakdown and an empty page cost nothing. Only people or companies you receive count as records.

Can I generate leads from a company list instead?

Yes. Size the company list with GET /v1/companies/count, then use GET /v1/companies/{domain}/people to pull the people at each domain. See what a lead list is for the structure.

Which tool should I use to build the list?

If you want an API or an MCP server and your own sequencer, this recipe works. For a ranked comparison, read best lead list building tools, and for the use case see list building. More guides are in the blog.

How fresh is the data?

The dataset is refreshed monthly, and each record carries its own fetched_at date. Read email_status before you send, because bounced emails are not refunded.

Build your first ICP list free

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

Get your free API key →