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

How-to guide

How to Build a B2B Prospect List Template for Outreach (With the LeadOcean API)

Fourteen columns, each mapped to a LeadOcean export column id. Size the list for free, then export a CSV a rep can send from.

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

A prospect list template is a fixed set of columns. It tells a rep who the person is, why they fit, and whether the address is safe to send to. This guide gives you those columns and the API calls that fill them.

TL;DR

  • Use 14 columns in 4 groups: identity, fit, contact, workflow. Twelve are filled by the export, two by your team.
  • It takes about 20 minutes: write the filters, size the list for free, spot-check 5 rows, export.
  • It spends about 505 LeadOcean records. The free plan has 1,000 for life, but bulk CSV export starts on Solo ($97 a month). On Free, page the search instead (Step 5).

Prerequisites

  • A LeadOcean account on the free plan: app.leadocean.io/register. No card.
  • An API key exported as $LEADOCEAN_API_KEY.
  • curl and jq. A spreadsheet to open the CSV.
  • An ICP written as one sentence. The example here is Directors in sales in the US.

Step 1: Set the columns

Keep the template short enough that every column has an owner. Most templates fail because half the columns are never filled.

GroupTemplate columnLeadOcean export column id
Identityperson_idperson_id
Identityfull_nameprofile_full_name
Identitylinkedin_urlprofile_url
Fitjob_titlecurrent_job_title
Fitcompanycurrent_company_name
Fitcompany_domaincurrent_company_domain
Fitindustrycurrent_company_industry
Fitcountrycountry_code
Contactemailemail_N_address (CSV header email_1_address)
Contactemail_statusemail_N_status (CSV header email_1_status)
Contactphonephone_N_number (CSV header phone_1_number)
Workflowfetched_atfetched_at
Workflowowneradded by your team
Workflowsend_okadded by your team

send_ok is the gate. Set it to true only when email_status is verified, catch_all_valid or catch_all and nobody has flagged the account. LeadOcean does not refund bounced emails, so this column does the protecting.

The ids come from the public catalogue. It is free and needs no key:

bash
curl -s https://api.leadocean.io/v1/exports/columns | jq -r '.data.columns[].id' | head -20

Each column points at a field you can request, so the template and the pull are the same document. Numbered blocks such as emails and phones are requested as email_N_address and phone_N_number, and caps sets how many of each come back.

Two rules keep the template honest. Store the source date with every row, because a title that was right in March may be wrong in October. And keep the provider's raw email_status next to your own send_ok, so a reviewer can see why a row was blocked.

Step 2: Write the ICP as filters

Every filter is an array in a flat JSON body. Enum values are exact strings, so check them first.

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

Save the filters once and reuse them for sizing and export:

bash
cat > icp.json <<'JSON'
{
  "country": ["US"],
  "jobFunction": ["Sales & Business Development"],
  "jobLevel": ["Director"],
  "emailStatus": ["verified", "catch_all_valid", "catch_all"]
}
JSON

jobLevel is Director, not director. The lowercase form belongs to the seniority filter.

Step 3: Size the list for free

Send count=true as a query parameter with limit=1. The data comes back empty and no record is spent.

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 @icp.json | jq '.meta'

meta.total is capped at 100,000 on this call. If it hits the cap, narrow the filters before you export. The MCP tool leadocean_count_leads gives an uncapped exact total and also costs nothing.

Sizing is where you decide whether the ICP is a list or a market. A count of 322,167 is a market. Narrow it by company size, industry or region until one rep can work it in a quarter. Each added filter is another free call, so cut as many times as you like.

Step 4: Spot-check 5 rows

Read five real rows before you commit 500. This costs 5 records.

bash
jq '. + {limit: 5}' icp.json | curl -s -X POST https://api.leadocean.io/v1/people/search \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d @- | jq '.data[] | {name: .profile_data.profile_full_name, title: .contact_data.contact_current_experiences[0].job_title}'

Check that the titles match what you meant. If you see Assistants and Coordinators, add excludeTitle and size again.

Step 5: Export the template

One request writes the CSV with your columns in your order. Rows are capped at 50,000 a request and 500,000 a day. Bulk CSV export is not on the Free plan: Solo includes it, and Agency gives 50k rows a file.

bash
jq '{name: "outreach-template-1", limit: 500, filters: .,
  columns: ["person_id","profile_full_name","profile_url","current_job_title",
            "current_company_name","current_company_domain","current_company_industry",
            "country_code","email_N_address","email_N_status","phone_N_number","fetched_at"],
  caps: {emails: 1, phones: 1}}' icp.json \
| curl -s -X POST https://api.leadocean.io/v1/exports \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  -H "Content-Type: application/json" -d @- | jq '.data | {id, status, requested}'

Poll GET /v1/exports/{id} until status is done, then read downloadUrl. The link works without a key. Add the owner and send_ok columns in your spreadsheet.

You can do the same from the Exports page in the app: filter builder, column picker, price preview and a CSV download.

On the Free plan, skip the export and page the search. Send limit 100 with the same filters and follow meta.nextCursor until you have the rows you want. Each person returned still counts one record, so five pages is 500.

bash
jq '. + {limit: 100}' icp.json | curl -s -X POST https://api.leadocean.io/v1/people/search \
  -H "x-api-key: $LEADOCEAN_API_KEY" -H "Content-Type: application/json" -d @- \
  | jq '.meta | {nextCursor, depthCapped}'

A single search pages to 10,000 rows. When meta.depthCapped is true there is no next cursor, so narrow the filters and run a second search.

Step 6: Review before it reaches a rep

The export is raw data. Three checks turn it into a list someone can send from.

First, deduplicate on person_id. The export already removes repeats inside one file, but two files from two filter sets will overlap.

Second, set send_ok. A one-line spreadsheet rule does it: true when email_1_status is verified, catch_all_valid or catch_all. Everything else stays false.

Third, read fetched_at. The dataset is refreshed monthly, and each record carries its own date. If a row is older than your sending window, enrich it by person_id before you write to it.

The first two checks fit in a few lines of Python:

python
import csv

MAILABLE = {"verified", "catch_all_valid", "catch_all"}
seen, rows = set(), []
for r in csv.DictReader(open("outreach-template-1.csv")):
    if r["person_id"] in seen:
        continue
    seen.add(r["person_id"])
    r["owner"] = ""
    r["send_ok"] = str(r["email_1_status"] in MAILABLE).lower()
    rows.append(r)

w = csv.DictWriter(open("outreach-ready.csv", "w", newline=""), fieldnames=rows[0].keys())
w.writeheader(); w.writerows(rows)

What you get

This is the free sizing call, run through the MCP tool leadocean_count_leads on 2026-10-21.

The filters were country US, jobLevel Director and jobFunction Sales & Business Development.

json
{
  "total": 322167,
  "totalIsExact": true,
  "totalAll": 825690,
  "distinctCompanies": 99327,
  "credits": 0
}

The count uses the tool's default of verified, catch-all-valid and catch-all addresses. The unfiltered total is 825,690, so the email filter removes 503,523 people.

The export CSV has one row per person. This is the response shape from the API reference for the export job, with placeholder values:

json
{
  "success": true,
  "data": {
    "id": "exp_0000",
    "name": "outreach-template-1",
    "status": "queued",
    "requested": 500,
    "estimatedTotal": 322167,
    "reveal": "email_phone",
    "perRow": 1
  }
}

A template row looks like this:

csv
person_id,profile_full_name,current_job_title,current_company_domain,email_1_address,email_1_status,phone_1_number
1234567890,Jane Doe,Director of Sales,acme.com,jane.doe@acme.com,verified,+1 415 555 0133

Read the two totals together. The 322,167 are people you can mail today. The other 503,523 have no address worth sending to under that filter. Sending to them is how a domain gets a bad reputation, so the template keeps them out through send_ok.

distinctCompanies tells you the spread. The 322,167 mailable Directors work at about 99,327 employers, roughly three a company. If you want one contact per account, say so in the template and dedupe on current_company_domain.

Troubleshooting

ErrorCauseFix
401Missing or wrong x-api-key headerRe-export $LEADOCEAN_API_KEY from the keys page of the app
429Above the plan's rate limit: 5 requests a second on Free, 200 on AgencyWait for Retry-After, or upgrade on /pricing
402Fewer records left than the export needs, or a Free key out of its 1,000Lower limit, or move to a paid plan
Empty resultsA misspelled enum value, such as director for jobLevelLook up the exact strings with GET /v1/enums
400 export_limit_rowslimit is above 50,000Split into several exports with narrower filters
429 export_limit_dailyMore than 500,000 rows exported in a dayWait for resetsAt in the response

FAQ

How many columns should a prospect list template have?

Fourteen is enough for outreach. More columns raise the chance that a field is empty or stale. Add a column only when someone acts on it, such as a segment tag or an account owner from your CRM.

Do phone numbers cost extra?

No. Every row costs one record whatever columns you pick. Solo and above include phone numbers and bulk CSV export. The Free plan can reveal phones inside its 1,000 records.

Why keep email_status in the template?

It is the only protection you have against bounces. LeadOcean does not refund them, so read email_status before you send.

What should the owner column hold?

A rep name or a team queue. It stops two people from emailing the same account. Fill it before the list goes into your sequencer, not after.

Can I adapt the template for calling or tech-stack lists?

Yes. For calling, filter to rows with a phone_1_number and sort on it. See how to build a prospect list for cold calling. For technology, add the technology filters to the same body, as in building a prospect list by tech stack. For a wider ICP walkthrough, read how to build a targeted prospect list.

Fill your prospect list template with real rows

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

Get your free API key →