You can compare ten enrichment tools fairly with one account file, one script per vendor and one scoring sheet. This page is that harness, not a set of results.
We have not run a ten-vendor test, and we will not invent one. A claim like "we tested 10 company enrichment tools on the same 500 accounts" is only useful if you can see the file and the scoring. So the steps below give you both, and the LeadOcean leg is runnable today.
TL;DR
- Build one file of 500 domains from your real pipeline, score every vendor on the same fields, and judge fill rate and accuracy separately.
- The LeadOcean leg takes about 2 hours and spends up to 500 records, inside the 1,000 on the free plan.
- Run each vendor on its free tier or a trial export where one exists. Do not buy ten annual plans to find out.
Prerequisites
- A LeadOcean API key in
$LEADOCEAN_API_KEY(the free plan works: 1,000 records for life, no card, 5 requests a second). curl,jqand Python 3.- Your 500 accounts as a CSV with one column named
domain. - A free or trial login for each other vendor you want in the test.
Step 1: Pick the 500 accounts
Sample from accounts you actually sell to, not from a clean list. Take 500 domains from your CRM: mix closed-won, open pipeline and cold targets.
Include the hard cases on purpose. Aim for about a third small companies under 50 staff, a third mid-size, a third large. Add some non-US domains if you sell abroad.
python3 - <<'PY'
import csv, random
rows = list(csv.DictReader(open("crm_accounts.csv")))
random.seed(42)
sample = random.sample(rows, 500)
with open("accounts.csv", "w", newline="") as f:
w = csv.writer(f)
w.writerow(["domain"])
for r in sample:
w.writerow([r["domain"].strip().lower()])
PY
wc -l accounts.csvThe fixed seed matters. Anyone should be able to rebuild your sample.
Step 2: Size the universe for free
Before you spend a record, check that your target segment exists in the data. The company count is free and exact.
curl -s "https://api.leadocean.io/v1/companies/count" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-G \
--data-urlencode 'industry=Software Development' \
--data-urlencode 'employeeRange=51-200' \
--data-urlencode 'hqCountry=US' | jq '.total'The MCP tool leadocean_count_companies takes the same filters and costs nothing. If you only want a coarse read of coverage for your segment, this is it. It does not replace the per-account test in step 3.
Step 3: Enrich all 500 on LeadOcean
Send each domain to POST /v1/companies/enrich and save the raw JSON. Keep the raw response, because you will score it later and you will want to re-check disagreements.
import csv, json, os, time, urllib.request
KEY = os.environ["LEADOCEAN_API_KEY"]
URL = "https://api.leadocean.io/v1/companies/enrich"
out = open("leadocean_raw.jsonl", "w")
for row in csv.DictReader(open("accounts.csv")):
req = urllib.request.Request(
URL,
data=json.dumps({"domain": row["domain"]}).encode(),
headers={"x-api-key": KEY, "content-type": "application/json"},
method="POST",
)
try:
body = json.load(urllib.request.urlopen(req))
out.write(json.dumps({"domain": row["domain"], "status": 200, "body": body}) + "\n")
except urllib.error.HTTPError as e:
out.write(json.dumps({"domain": row["domain"], "status": e.code}) + "\n")
time.sleep(0.25) # stays under 5 requests a second on Free
out.close()A 404 means the company is not in the data. It costs nothing, and it is a result you must count as a miss. Do not drop those rows.
Step 4: Run the same file through the other nine
Each vendor gets the identical accounts.csv. Use whichever input the vendor supports best, but record what you used: domain, company name or LinkedIn URL. A vendor that needs a name where you gave a domain has been handicapped.
For each vendor, save three things: the raw output, the date, and the plan or tier you ran it on. If a vendor throttles a free tier below 500 lookups, say so in the sheet and test the same first N accounts on every tool.
Check each vendor's own documentation for how an unmatched account is billed. The rule differs, and it changes the real cost of your test.
Step 5: Normalize into one table
Different vendors name the same field differently. Map every output to one shared set of columns before you score anything.
import json, csv
FIELDS = ["domain", "name", "industry", "employee_count", "hq_country",
"founded_year", "linkedin_url", "technologies"]
def pick(d, *paths):
for p in paths:
cur = d
for k in p.split("."):
cur = cur.get(k) if isinstance(cur, dict) else None
if cur not in (None, "", []):
return cur
return None
with open("leadocean_flat.csv", "w", newline="") as f:
w = csv.DictWriter(f, fieldnames=FIELDS)
w.writeheader()
for line in open("leadocean_raw.jsonl"):
r = json.loads(line)
data = (r.get("body") or {}).get("data") or {}
cd = data.get("company_data") or {}
w.writerow({
"domain": r["domain"],
"name": pick(cd, "name"),
"industry": pick(cd, "industry"),
"employee_count": pick(cd, "employee_count"),
"hq_country": pick(cd, "hq_country"),
"founded_year": pick(cd, "founded_year"),
"linkedin_url": pick(cd, "linkedin_url"),
"technologies": pick(data, "company_detected_technologies"),
})The key names inside company_data above are placeholders. Open one raw response from step 3 and adjust the paths to what it returns.
Step 6: Score three numbers per vendor
Score three things and never blend them into one number.
- Match rate: accounts returned out of 500.
- Fill rate per field: of the matched accounts, how many have a non-empty value in each field you care about.
- Accuracy: hand-check 50 randomly chosen accounts per vendor against the company's own website and LinkedIn page.
Fill rate is cheap and tempting. It is also easy to game: a tool that returns a plausible guess scores high on fill and low on accuracy. Only the hand check catches that.
import csv
rows = list(csv.DictReader(open("leadocean_flat.csv")))
matched = [r for r in rows if r["name"]]
print("match rate:", len(matched), "/ 500")
for f in ["industry", "employee_count", "hq_country", "founded_year"]:
n = sum(1 for r in matched if r[f])
print(f, "fill:", n, "/", len(matched))Put one vendor per row and one metric per column in the sheet. Add the date you ran it. Enrichment data changes monthly, so a result older than a quarter is a stale result.
Step 7: Read the results like a buyer
Rank vendors per field, not overall. A tool that wins on headcount may lose on technologies, and you may only need one of them.
Then price the winner at your real volume. Fill rate on 500 accounts means little if the plan that delivers it costs ten times your budget at 50,000. Use monthly list price first and the billed-yearly figure after it, and write down the date you read each pricing page.
Last, look at the misses. List the accounts that every vendor failed on. They show you which gap no tool will close for you.
What you get
Step 2 returned this on 2026-10-21 from the free leadocean_count_companies tool, for US companies in the Software Development industry with 51 to 200 staff:
{
"total": 9261,
"totalIsExact": true,
"facets": {
"hq_state": [
{ "value": "California", "count": 2398 },
{ "value": "New York", "count": 842 },
{ "value": "Texas", "count": 776 }
]
},
"credits": 0,
"unsupportedFilters": []
}That is a count of companies in one segment, not a mailable count and not a fill rate. The state list is cut to three rows here.
A single enrichment answers in the shape below (response shape from the API reference, placeholder values):
{
"success": true,
"data": {
"company_data": { "name": "Acme", "domain": "acme.com" },
"company_detected_technologies": {},
"company_metrics": {}
},
"meta": {}
}Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing or wrong x-api-key header | Check $LEADOCEAN_API_KEY is exported in the shell that runs the script |
| 404 | The company is not in the data | Count it as a miss. It costs nothing. Do not retry |
| 429 | Above the plan's rate limit (5 a second on Free, 200 on Agency) | Honor Retry-After, or slow the loop |
| 402 | The 1,000 free records are spent | Move to a paid plan, or cut the sample |
Empty count or unsupportedFilters filled | A bad enum value or an unsupported filter | Resolve values with leadocean_list_enum_values first |
| Export refused | Over 50,000 rows a request or 500,000 a day | Split the file. Bulk CSV export is not on Free |
FAQ
Why 500 accounts?
It is large enough to show differences in fill rate and small enough to hand-check a slice. Below about 200, one odd segment can swing the result.
Can I run this entirely on the free plan?
For the LeadOcean leg, yes. 500 enrichments fit inside the 1,000 records, and misses cost nothing. Other vendors have their own free limits, so check them.
Should I test people data too?
Test it separately. Person enrich takes one of linkedin_url, email, phone or person_id, and email accuracy needs its own bounce-based check. Mixing it into the company test hides both results.
How often should I rerun it?
Once a quarter. LeadOcean refreshes its dataset monthly, and every record carries its own fetched_at date, so you can see how old a value is.
What should I do about vendors that block automated testing?
Do not work around the block. Use the vendor's own export or upload feature, or leave that vendor out and say why in your sheet. A gap you disclose is better than a number you cannot defend.
Is a vendor's own match-rate claim worth citing?
Only next to your own number. A vendor measures on its own sample. Your file has your mix of sizes, countries and industries, and that mix decides the result.
Where do I compare prices?
See the pricing page for LeadOcean. For the other nine, read each vendor's own pricing page and note the month you read it.
For background on what the fields mean, read what company enrichment is. For tool picks by use case, see the best lead enrichment tools for SaaS companies. The API itself is covered in the company enrichment API guide.
Run your own 500-account test with 1,000 free records
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →