Size the list first, then pull it. A company search is one POST /v1/companies/search call with flat JSON filters, a limit from 1 to 100 and a cursor for the next page.
TL;DR
- Time: about 20 minutes from an empty terminal to a paged list and one enriched company.
- Cost: about 106 LeadOcean records on the example. Counting is free, so size the list before you spend anything.
- Result: a script that walks a filtered company list page by page and writes it to a file, plus an agent prompt for the same query.
Prerequisites
- A LeadOcean account. The free plan gives you 1,000 records, one-off, no card. Sign up at app.leadocean.io/register.
- An API key from the app, exported as
$LEADOCEAN_API_KEY. Free keys carry both the search and enrich scopes. curland Python 3 withrequestsinstalled.- Optional: Claude Code or Claude Desktop, if you want the MCP version in Step 6.
export LEADOCEAN_API_KEY="paste-your-key-here"Step 1: Check the key and your balance
Call GET /v1/account first. It is free, and it tells you the plan, the records left on Free or the fair-use pace on a paid plan, and your rate.
curl -s "https://api.leadocean.io/v1/account" \
-H "x-api-key: $LEADOCEAN_API_KEY"A 401 here means the key is wrong. A 403 later means the key lacks the scope an endpoint needs. Fix both in the app before you go on.
Step 2: Pick filters from the closed lists
Enum filters take exact strings. Guessing a value does not fail loudly: you get an empty page.
Company search has 34 filters. Industry has 534 values and headcount comes in eight brackets. Resolve a value before you use it:
curl -s "https://api.leadocean.io/v1/enums/industry?q=software" \
-H "x-api-key: $LEADOCEAN_API_KEY"
curl -s "https://api.leadocean.io/v1/enums/employee_range" \
-H "x-api-key: $LEADOCEAN_API_KEY"The industry lookup for "software" returns seven values, among them Computer Software and Software Development. The brackets are 1-10, 11-50, 51-200, 201-500, 501-1000, 1001-5000, 5001-10000 and 10001+.
The example search is software companies headquartered in the US with 51 to 200 employees. Use employeeRange rather than minEmployees and maxEmployees when a bracket will do. The docs call it broader and cheaper. A numeric range only returns companies where we hold a figure, so a company with no headcount on file never appears.
Start with the filters that are always filled in, then add the thin ones. Industry, headcount bracket and headquarters country are the safe core. Other filters worth knowing:
keywordsmatches across company description, specialties and categories. Several values OR together.technologiesandtechnologyCategoriesfilter on what a website runs. They cover about 7.8M crawled sites, so they narrow hard.hqContinentandhqSalesRegionset a wide net. UsehqCountryfor a single market.minFoundedandmaxFoundeduse the founding year. Like every numeric range, they skip companies with no year on file.excludeNameandexcludeIndustrycut out noise after your first test page.
Funding, revenue, investor and NAICS filters work in search. Size the list on the core filters first, then expect those filters to narrow it further.
Step 3: Size the list before you pull it
A count is free when you send count=true as a query parameter with limit set to 1. The answer is in meta.total, and data comes back empty.
curl -s -X POST "https://api.leadocean.io/v1/companies/search?count=true&limit=1" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "content-type: application/json" \
-d '{
"industry": ["Computer Software"],
"employeeRange": ["51-200"],
"hqCountry": ["US"]
}'Put count in the query string, not the JSON body. The body rejects unknown fields, and the query string is where the free sizing call is documented.
meta.total stops at 100,000. If meta.totalIsExact is false, the total is a floor and you should narrow the filters. If meta.totalWithheld is set, one filter could not be counted and the number is hidden on purpose.
For an exact company total with breakdowns, call GET /v1/companies/count. It takes the same filters as query parameters, returns no rows and costs nothing.
curl -s -G "https://api.leadocean.io/v1/companies/count" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
--data-urlencode "industry=Computer Software" \
--data-urlencode "employeeRange=51-200" \
--data-urlencode "hqCountry=US"Step 4: Pull a test page
Spend a few records to check the shape before you page through hundreds. Each company returned counts one record.
curl -s -X POST "https://api.leadocean.io/v1/companies/search" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "content-type: application/json" \
-d '{
"industry": ["Computer Software"],
"employeeRange": ["51-200"],
"hqCountry": ["US"],
"limit": 5
}'This costs 5 records. Check meta.credits: it reports the records counted for the call, and it should read 5.
Filters are arrays of strings, even for one value. The body is flat. There are no nested and or or blocks.
Step 5: Page through the results with the cursor
Each response carries meta.nextCursor. Send it back unchanged as cursor with the same filters to get the next page. When it is null, the walk is over.
import json, os, requests
URL = "https://api.leadocean.io/v1/companies/search"
HEADERS = {
"x-api-key": os.environ["LEADOCEAN_API_KEY"],
"content-type": "application/json",
}
FILTERS = {
"industry": ["Computer Software"],
"employeeRange": ["51-200"],
"hqCountry": ["US"],
}
PAGE_SIZE = 50
MAX_PAGES = 2 # 100 records. Raise it once you trust the filters.
cursor, pages, rows = None, 0, 0
with open("companies.jsonl", "w") as out:
while pages < MAX_PAGES:
body = {**FILTERS, "limit": PAGE_SIZE}
if cursor:
body["cursor"] = cursor
r = requests.post(URL, headers=HEADERS, json=body, timeout=30)
if r.status_code == 429:
raise SystemExit("rate limited: wait for Retry-After and rerun")
r.raise_for_status()
payload = r.json()
for company in payload["data"]:
out.write(json.dumps(company) + "\n")
rows += 1
pages += 1
meta = payload["meta"]
cursor = meta.get("nextCursor")
if not cursor:
break
print(f"wrote {rows} companies in {pages} pages")Three rules for the cursor. Pass back exactly the string you were given: an edited or hand-built cursor is refused with 400 Invalid cursor. Do not reuse a cursor with different filters. Do not store one: it is a position in one walk, so restart the query instead.
One search pages through at most 10,000 rows by default. At that point meta.depthCapped is true and nextCursor stays null. To cover a bigger market, split it into several narrower searches, for example one per headcount bracket.
Now enrich one company from the list. Enrichment takes a domain or a LinkedIn company URL. It does not take a name, so search first and enrich the domain you got back.
curl -s -X POST "https://api.leadocean.io/v1/companies/enrich" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "content-type: application/json" \
-d '{"domain":"acme.com"}'A hit costs 1 record. A company we hold nothing for is a 404 and costs nothing.
Step 6: Run the same search from an agent
The hosted MCP server at https://api.leadocean.io/mcp has 13 tools and signs in with OAuth 2.1, so no key is pasted. The company tools are leadocean_count_companies (free), leadocean_search_companies and leadocean_get_company. Add the server to Claude Code with one command.
claude mcp add --transport http leadocean https://api.leadocean.io/mcpThen give the agent the same query in plain words:
Find US software companies with 51 to 200 employees.
Size the list first and tell me the number before you pull anything.
Then pull the first 10 and list name, domain and headcount.The agent resolves the enum values with leadocean_list_enum_values and sizes the list with leadocean_count_companies. Both are free. It then pulls the 10 companies, which costs 10 records. If you want contacts at those companies rather than the companies themselves, use best people search APIs for the next step.
What you get
Sizing runs through the free leadocean_count_companies MCP tool, the same count as GET /v1/companies/count. Run on 21 October 2026 with industry: Computer Software, employeeRange: 51-200 and hqCountry: US. The breakdowns are trimmed to the top five states:
{
"total": 460,
"totalIsExact": true,
"facets": {
"hq_state": [
{ "value": "California", "count": 136 },
{ "value": "New York", "count": 36 },
{ "value": "Texas", "count": 26 },
{ "value": "Massachusetts", "count": 25 },
{ "value": "Florida", "count": 24 }
]
},
"credits": 0,
"source": "leadocean",
"unsupportedFilters": []
}Read it as 460 companies. credits is 0 because sizing is free. unsupportedFilters is empty, so every filter was applied. Computer Software is one of seven industry values that match "software". Add Software Development and the others for the wider market.
A company record comes back as leadocean.company.v1. This is the response shape from the API reference, with placeholder values:
{
"success": true,
"data": [
{
"company_data": { "...": "name, domain, description, industries, company_employees, address" },
"company_detected_technologies": [],
"company_metrics": { "...": "completion_score, marketability_score" },
"meta": { "...": "sources, fetched_at, schema" }
}
],
"meta": {
"source": "leadocean",
"count": 1,
"limit": 50,
"credits": 1,
"nextCursor": "opaque-string-from-the-api"
}
}A value LeadOcean does not hold is null, never guessed. Technology data covers about 7.8M crawled sites, so that list is empty for many companies. Revenue and funding are thin, so do not build a filter on either alone.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing or wrong x-api-key header | Copy the key again from the app and check $LEADOCEAN_API_KEY is set in this shell |
| 403 | The key lacks the search or enrich scope | Create a key with the scope the endpoint needs |
| 429 | Above your plan's rate on this key (5 a second on Free, 200 on Agency) | Wait for the Retry-After header, then retry. Run the walk in sequence, not in parallel |
| 402 | Free plan only: the 1,000 one-off records are spent | Check GET /v1/account. Move to Solo at $97 a month or Agency at $497 on pricing |
Empty data with no error | An enum value that is not an exact string, or filters that are too narrow | Resolve each value with /v1/enums/{name}, and drop filters one at a time |
meta.depthCapped is true, nextCursor is null | The search paged through 10,000 rows | Split the market into narrower searches, or ask support if you have a genuine large export |
FAQ
How much does a company search cost?
Each company returned is one record. Counting with count=true and limit=1 costs nothing. Free gives you 1,000 records once. Solo is $97 a month and Agency $497 a month, with a fair-use pace instead of credits. See pricing.
Can I search by company name?
Yes. Use the name filter for name keywords, or q for free text over name and description. Enrichment is different: it needs a domain or a LinkedIn company URL.
How do I get more than 100 rows?
Page with the cursor. limit is capped at 100 per page, and one search walks at most 10,000 rows. For more, run several narrower searches.
Does this return contacts at the company?
No. Company search returns companies. For the people at one company, call GET /v1/companies/{domain}/people, which stops at 10,000 rows. For enrichment details, read the company enrichment API guide.
Is there a ranked comparison of tools?
Yes. Best company search engines compares the options with sourced prices.
Run your first company search with a free API key
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →