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 Company List with an API (Size, Pull, Page)

Count the list for free, narrow it until it is the right size, then pull it page by page. Real requests, real output.

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

TL;DR

  • Size the list first with GET /v1/companies/count. It is free and returns an exact total plus breakdowns.
  • Pull it with POST /v1/companies/search, 100 companies a page, following meta.nextCursor.
  • The whole recipe takes about 15 minutes and spends about 100 LeadOcean records (one page of 100). The Free plan has 1,000 records for life.

A company list is the base of account-based outbound. Build it by filter, not by pasting names. Then you can rerun it next month and get the same shape.

Prerequisites

  • A LeadOcean API key from app.leadocean.io. The Free plan is $0, needs no card and includes search.
  • The key in your shell as $LEADOCEAN_API_KEY.
  • curl and jq. Python 3 with requests for the paging step.
  • Rate limit: 5 requests a second on Free, 50 on Solo, 200 on Agency. Stay under it or you get a 429.
bash
export LEADOCEAN_API_KEY="paste-your-key-here"

Step 1: Define the list as filters

Write the ICP as filters before you call anything. Company filters are flat: strings, ints and arrays of strings.

Our example: US cybersecurity companies with 51 to 500 employees that have at least one mailable contact.

NeedFilterValue
CountryhqCountry["US"]
SizeemployeeRange["51-200", "201-500"]
What they dokeywords["cybersecurity"]
ReachablehasMailableContactstrue

Enum values are exact strings. Look them up free before you send them.

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

keywords matches a phrase in the company description, specialties, tags or name. It also accepts an exact industry label, which matches every company filed under that industry. A bare industry label alone misses companies that describe themselves in their own words, so start with keywords.

Step 2: Size the list for free

Count before you pull. GET /v1/companies/count takes the company-search filters and spends no records.

bash
curl -s -G "https://api.leadocean.io/v1/companies/count" \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  --data-urlencode "hqCountry=US" \
  --data-urlencode "employeeRange=51-200,201-500" \
  --data-urlencode "keywords=cybersecurity" \
  --data-urlencode "hasMailableContacts=true" | jq '.data | {total, totalIsExact}'

Arrays in a query string are comma-separated. The same count is available to an AI client as the leadocean_count_companies MCP tool.

The answer on 2026-10-21 was 1,443 companies. If the total is above what you can work, add a filter and count again. Each count is free, so iterate here and not on paid pages.

Step 3: Read the breakdown and narrow

The count response also carries facets by industry, headcount band, HQ country, HQ state and NAICS. Use them to decide where to cut.

bash
curl -s -G "https://api.leadocean.io/v1/companies/count" \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  --data-urlencode "hqCountry=US" \
  --data-urlencode "employeeRange=51-200,201-500" \
  --data-urlencode "keywords=cybersecurity" \
  --data-urlencode "hasMailableContacts=true" \
  | jq '.data.facets.hq_state[:3]'

On 2026-10-21 the top states were Virginia (206), California (160) and Texas (122). To work one state, add hqState. A state name or its code both match, so TX finds Texas.

One search pages to 10,000 rows. A list above that returns meta.depthCapped: true and no further cursor. Narrow the filters and run several smaller searches instead.

Step 4: Pull the first page

POST /v1/companies/search takes the same filters as a flat JSON body. Each company returned counts one record. Use fields to keep the rows small.

bash
curl -s -X POST "https://api.leadocean.io/v1/companies/search" \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "hqCountry": ["US"],
    "employeeRange": ["51-200", "201-500"],
    "keywords": ["cybersecurity"],
    "hasMailableContacts": true,
    "limit": 100,
    "fields": ["company_data.company_name", "company_data.company_domain"]
  }' | jq '{count: .meta.count, total: .meta.total, next: .meta.nextCursor}'

limit runs from 1 to 100. This call spends 100 records. To test the shape for almost nothing, send "limit": 5 first.

Step 5: Page through the rest

Take meta.nextCursor from each response and send it back as cursor. Stop when it is absent.

python
import os, requests

URL = "https://api.leadocean.io/v1/companies/search"
HEADERS = {"x-api-key": os.environ["LEADOCEAN_API_KEY"]}
body = {
    "hqCountry": ["US"],
    "employeeRange": ["51-200", "201-500"],
    "keywords": ["cybersecurity"],
    "hasMailableContacts": True,
    "limit": 100,
    "fields": ["company_data.company_name", "company_data.company_domain"],
}

rows = []
while True:
    r = requests.post(URL, headers=HEADERS, json=body, timeout=30)
    r.raise_for_status()
    page = r.json()
    rows.extend(page["data"])
    cursor = page["meta"].get("nextCursor")
    if not cursor:
        break
    body["cursor"] = cursor

print(len(rows), "companies")

The full example list is 1,443 companies, so it spends 1,443 records. That is above the Free plan's 1,000 records for life. Cap the loop at 10 pages on Free, or run it on Solo ($97 a month), where paid plans use a fair-use pace and not credits.

Step 6: Add the people at each company

A company list is a means. The next call pulls decision makers at a domain from the list. Send domain as an array to the people search.

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 '{
    "domain": ["acme.com"],
    "jobLevel": ["C-Team", "VP"],
    "limit": 10
  }'

Each person returned counts one record. Use /v1/companies/count and the free people count to size this step first. For the full enrichment fields, see our company enrichment API guide.

What you get

The free leadocean_count_companies output, run on 2026-10-21 with the filters from Step 2 (trimmed to the first three entries of each facet). Over REST the same fields sit under data.

json
{
  "total": 1443,
  "totalIsExact": true,
  "facets": {
    "employee_range": [
      { "value": "51-200", "count": 1100 },
      { "value": "201-500", "count": 343 }
    ],
    "hq_state": [
      { "value": "Virginia", "count": 206 },
      { "value": "California", "count": 160 },
      { "value": "Texas", "count": 122 }
    ],
    "naics_primary": [
      { "value": "541511", "count": 626 },
      { "value": "541512", "count": 228 },
      { "value": "541611", "count": 177 }
    ]
  },
  "credits": 0
}

The credits: 0 line is the point: sizing cost nothing. That total counts companies with at least one mailable contact (verified, catch_all_valid or catch_all email), so it is smaller than the whole market for those filters.

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

json
{
  "success": true,
  "data": [
    {
      "company_data": {
        "company_name": "Acme Security",
        "company_domain": "acme.com"
      }
    }
  ],
  "meta": { "count": 100, "total": 1443, "nextCursor": "opaque-string" }
}

Troubleshooting

ErrorCauseFix
401Missing or wrong x-api-key headerCheck $LEADOCEAN_API_KEY is set in this shell and sent as x-api-key
429Requests above the plan's rate limit (5 a second on Free, 200 on Agency)Wait for the Retry-After header, then slow the loop or move up a plan
402The account is out of records (Free is 1,000 for life)Check GET /v1/account, then move to a paid plan. Counts still work
Empty dataA bad enum value or a filter that matches no companiesLook the value up in GET /v1/enums. Values are exact strings
meta.depthCapped: trueOne search reached its 10,000-row page depthNarrow the filters and run smaller searches
Export refused above 50,000 rowsThe per-request ceiling on POST /v1/exports (500,000 rows a day)Split the people export into several requests with narrower filters

FAQ

How many companies can one search return?

A search pages to 10,000 rows by default, 100 per page. Past that the response has meta.depthCapped set to true and no cursor. Split the list by state, size band or industry.

Does counting cost anything?

No. GET /v1/companies/count and the leadocean_count_companies MCP tool spend no records. Only companies you receive from a search count.

Is there a bulk CSV for a company list?

POST /v1/exports takes a people filter set, not company filters. For a company CSV, page the company search and write the rows yourself, as in Step 5. Bulk CSV export is not on the Free plan.

Can I run this from an AI client instead of curl?

Yes. LeadOcean has a hosted MCP server with 13 tools. leadocean_count_companies sizes a company list for free and leadocean_search_companies pulls it.

What does the first page cost on each plan?

On Free it takes 100 of your 1,000 records. On Solo and Agency, a fair-use pace applies and there are no credits to top up. See pricing.

For the wider picture, see list building with LeadOcean and the best lead list building tools. Crustdata documents its own approach on its company discovery page.

Build your first company list with a free API key

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

Get your free API key →