TL;DR
- You size TAM, SAM and SOM with three free company counts, pull the SOM list once, then re-run the counts every month with a short script.
- It takes about 25 minutes to set up. The run spends about 100 of your 1,000 free records, all on the list in Step 4. Counts cost 0.
- LeadOcean refreshes its dataset monthly, so a monthly check is the right rhythm. Faster polling shows the same numbers.
Prerequisites
- A LeadOcean account from app.leadocean.io. The free plan has no card and 1,000 records, one-off.
- A key in your shell as
$LEADOCEAN_API_KEY, pluscurland Python 3 withrequests. - Optional: the MCP server at
https://api.leadocean.io/mcpin Claude, for the people count in Step 3. It signs in with OAuth, so no key is pasted.
export LEADOCEAN_API_KEY="paste-your-key-here"Step 1: Pin the segment with the real filter names
Write the market as filters before you count anything. A TAM that lives in a slide has no query behind it, so nobody can re-run it. A TAM is one set of filters, a SAM adds a geography or a size band, and a SOM adds what you can actually sell to this year.
Filter and enum names are exact strings. Look them up first. Both calls are free.
curl -s "https://api.leadocean.io/v1/filters/companies" \
-H "x-api-key: $LEADOCEAN_API_KEY"
curl -s "https://api.leadocean.io/v1/enums/industry" \
-H "x-api-key: $LEADOCEAN_API_KEY"The example market is software companies. The industry enum holds 534 values and has no plain "Software". The value we want is Computer Software. Headcount comes as eight employeeRange brackets, and HQ country takes two-letter codes such as US.
| Layer | Filters |
|---|---|
| TAM | industry: ["Computer Software"] |
| SAM | TAM plus employeeRange: ["51-200", "201-500"] |
| SOM | SAM plus hqCountry: ["US"] |
One caveat. Size filters only match companies whose value we hold. A company with no headcount on file never appears in the SAM, so treat the SAM as a floor.
Step 2: Count each layer for free
Send each layer to POST /v1/companies/search with count=true and limit=1 as query parameters. The body is flat JSON and filters are arrays. The count call returns no rows, so it spends nothing.
count () {
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 "$1" | jq '.meta | {total, totalIsExact}'
}
count '{"industry":["Computer Software"]}'
count '{"industry":["Computer Software"],"employeeRange":["51-200","201-500"]}'
count '{"industry":["Computer Software"],"employeeRange":["51-200","201-500"],"hqCountry":["US"]}'meta.total is capped at 100,000. If a layer is bigger, totalIsExact is false and the number is a floor. For an exact total with no cap, use GET /v1/companies/count or the MCP tool leadocean_count_companies. Both are free, take the same filters and break the total down by industry, headcount band, HQ country, HQ state and NAICS.
Put the three totals in a sheet next to your price per customer. TAM is the first count times your annual contract value. SAM and SOM follow the same arithmetic.
A worked example with placeholder numbers: a SOM of 2,000 companies at a $10,000 annual contract is a $20M serviceable obtainable market. Change one filter and the sheet updates. That is the point of keeping the filters in code. For the definitions, see what TAM means.
Step 3: Size the buyers inside the segment
A company count tells you how many accounts exist. It does not tell you how many people you can reach. For that, ask the MCP server. The people count is free too.
Count mailable people with jobLevel C-Team or VP at companies in the
Computer Software industry with 51-200 or 201-500 employees and an HQ in the US.
Also count the same without the HQ filter, and without the size filter.
Use leadocean_count_leads. Do not search or export.Claude maps that to leadocean_count_leads with industry, employeeRange, hqCountry and jobLevel. The tool counts only verified, catch-all-valid and catch-all addresses unless you say otherwise.
Step 4: Pull the SOM list once
Pull the list only when the SOM looks right. Each company returned counts one record, so set limit to what you will actually use. One page is 100 companies and 100 records.
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", "201-500"],
"hqCountry": ["US"],
"limit": 100
}' > som-page-1.json
jq -r '.data[].company_data.company_domain // empty' som-page-1.json | sort > som-domains.txtTo get the next page, pass back meta.nextCursor unchanged as cursor. A single walk stops after 10,000 rows by default. For a bigger SOM, split it by hqState or employeeRange and run several walks. For a full list of people in one segment, use POST /v1/exports on Solo ($97 a month) and above. It takes up to 50,000 rows a request. Bulk CSV export is not on Free.
Keep som-domains.txt. It is your baseline for the diff in Step 5.
The list is also the input for the next job. Enrich a company with POST /v1/companies/enrich, or find its buyers with GET /v1/companies/{domain}/people. Both spend records, so run them on the accounts you will work, not the whole SOM.
Step 5: Monitor with a monthly snapshot
The dataset is refreshed monthly. Each record also carries its own fetched_at date. So you re-run the counts once a month and log the totals.
import csv, datetime, os, requests
URL = "https://api.leadocean.io/v1/companies/search"
HEAD = {"x-api-key": os.environ["LEADOCEAN_API_KEY"]}
SEGMENTS = {
"tam": {"industry": ["Computer Software"]},
"sam": {"industry": ["Computer Software"], "employeeRange": ["51-200", "201-500"]},
"som": {"industry": ["Computer Software"], "employeeRange": ["51-200", "201-500"], "hqCountry": ["US"]},
"new_2026": {"industry": ["Computer Software"], "hqCountry": ["US"], "minFounded": 2026},
}
rows, today = [], datetime.date.today().isoformat()
for name, body in SEGMENTS.items():
meta = requests.post(URL, params={"count": "true", "limit": 1}, json=body, headers=HEAD).json()["meta"]
rows.append([today, name, meta.get("total"), meta.get("totalIsExact"), bool(meta.get("totalWithheld"))])
with open("tam-snapshots.csv", "a", newline="") as f:
csv.writer(f).writerows(rows)Run it from cron on the second day of each month: 0 7 2 * * python3 tam_snapshot.py. The new_2026 row is a cheap proxy for new entrants. You can add funding filters such as minLastFundingYear as another row. If a filter cannot be applied, meta.total comes back null with meta.totalWithheld set, and the script logs that instead of a wrong number.
Read the CSV as a trend. A TAM that grows 1 percent in a month is noise. A SOM that jumps, say, 8 percent in a month is a signal to refresh your target list and your forecast.
Pull the list again only when a layer moves by more than you care about, say 5 percent. Then diff the new domains against som-domains.txt. The diff costs records, so the free counts decide when to spend them.
What you get
The company counts below come from leadocean_count_companies on 2026-10-21. Each is exact and cost 0 records.
| Layer (industry Computer Software) | Companies |
|---|---|
| TAM: worldwide, all sizes | 27,143 |
| SAM: 51-200 and 201-500 employees | 1,552 |
| SOM: SAM with HQ in the US | 728 |
The SAM count shows the size-filter caveat from Step 1: 18,437 of the 27,143 companies sit in the 1-10 band. The SOM splits into 460 companies at 51-200 and 268 at 201-500, and 204 of them have an HQ in California.
The people counts come from leadocean_count_leads on the same day. They count mailable people, not companies, using the default email filter: verified, catch-all-valid or catch-all.
| Segment (industry Computer Software, jobLevel C-Team or VP) | Mailable people | Same filters, no email condition |
|---|---|---|
| Worldwide, all company sizes | 10,916 | 70,265 |
| Worldwide, 51-200 and 201-500 employees | 3,403 | 31,528 |
| HQ in the US, 51-200 and 201-500 employees | 2,013 | 8,187 |
The last row breaks down as 1,005 c_suite, 540 vp and 468 owner by seniority band, and 1,030 at 51-200 and 983 at 201-500 by company size. Each tool call returned "credits": 0.
The count-only search in Step 2 returns the envelope below. This is the response shape from the API reference, and the total is a placeholder:
{
"success": true,
"data": [],
"meta": { "source": "leadocean", "count": 0, "total": 1234, "totalIsExact": true, "countOnly": true }
}A list page from Step 4 returns an array of company documents. Each has company_data, company_detected_technologies, company_metrics and meta.
The page-level meta carries nextCursor.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing, wrong or revoked key | Re-copy the key from app.leadocean.io and export $LEADOCEAN_API_KEY again. |
| 429 | Above the plan's rate limit per account (5 a second on Free, 50 on Solo, 200 on Agency) | Wait the Retry-After seconds. The monthly script sends four calls, so this only hits a large fan-out. |
| 402 | Free plan: the 1,000 records are spent | Counts stay free. Lower limit on list pulls or move to Solo at $97 a month. Free records do not reset. |
| Zero results or a total of 0 | A filter value outside the catalogue, such as industry: "Software" | Call GET /v1/enums/industry and use the exact string it returns. |
meta.total is null with totalWithheld | A filter in the request could not be applied, so a number would be wrong | Drop that filter from the count and say it narrows the list further at search time. |
meta.depthCapped is true | One walk reached 10,000 rows | Narrow the filters and run several smaller searches, or use POST /v1/exports. |
FAQ
How many records does TAM monitoring cost?
Counts cost 0. The example spends 100 records on one page of the SOM list and then nothing each month. Only a re-pull of the list costs records, one per company returned.
How often does the data change?
The dataset is refreshed monthly. A monthly snapshot catches every change the data holds. Hourly polling returns the same totals until the next refresh.
Can LeadOcean push an alert when the market changes?
Not today. The API reference, October 2026, lists no webhook or watcher endpoint, so you poll with the script above. Crustdata's market analysis page, September 2026, describes watcher APIs that alert you by Slack or into your system. If push alerts matter more to you than a flat price, check that first.
What if my market is not an industry?
Use keywords for company description text, technologyCategories for what a company runs on its site, or hqSalesRegion for a territory. The same count call works with any filter from Step 1.
Can an AI agent run this?
Yes. The same counts work through the MCP server, and you can wire the snapshot into an agent loop. See a real-time B2B data provider for AI agents and real-time company data for investment platforms. More recipes are in the blog hub.
Size and monitor your market, free
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →