TL;DR
- Do not trust a vendor's coverage claim. Run one fixed sample of 25 contacts through every tool you pay for and score the output yourself.
- It takes about 45 minutes. Sizing is free. The LeadOcean side of the test spends about 50 of your 1,000 free records.
- The score that matters is cost per usable contact, not fill rate alone. A tool that fills every field with stale emails still loses.
This is a method, not a survey. We did not poll anyone, and we will not quote a number we did not measure. The one count below came from a LeadOcean run on the date shown.
A SaaS stack often holds more than one enrichment tool: one bundled with the CRM, one with the sequencer, one left over from a trial. The audit tells you which to keep.
Prerequisites
- A LeadOcean account on the free plan (no card). Create a key in app.leadocean.io and export it as
$LEADOCEAN_API_KEY. - Logins for the tools you want to test. Up to 8 is workable by hand. More than that, drop the weakest on price alone.
curl,jqand Python 3.- A spreadsheet. You will paste each tool's output into one sheet.
Step 1: Define the segment and size it for free
Pick the segment you actually sell to, then count it before you spend anything. Counts cost zero records.
curl -s -X POST "https://api.leadocean.io/v1/people/search?count=true" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": ["Revenue Operations"],
"industry": ["Software Development"],
"jobLevel": ["Director", "VP"],
"country": ["US"],
"limit": 1
}' | jq '.meta'count=true goes in the query string, not the JSON body, which rejects unknown fields. meta.total is capped at 100,000. Enum values are exact strings, so check them with GET /v1/filters or the leadocean_list_enum_values MCP tool before you guess.
Step 2: Pull a fixed sample of 25
Take 25 people from that segment. This is the test set. Every tool sees the same people, or the comparison means nothing.
curl -s -X POST "https://api.leadocean.io/v1/people/search" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": ["Revenue Operations"],
"industry": ["Software Development"],
"jobLevel": ["Director", "VP"],
"country": ["US"],
"limit": 25
}' > sample.jsonThat spends 25 records. Search pages return up to 100 rows with limit, and you continue with cursor. One search pages to 10,000 rows (meta.depthCapped flags the end), so narrow the filters instead of walking deep.
Save the person_id of each row for the LeadOcean pass. For the other tools, export a name, company and LinkedIn URL per person from the same rows, since most tools accept those as inputs. List the keys first, because paths can differ between versions.
jq -r '.data[].meta.source_ids.person_id' sample.json > testset_ids.txt
jq '.data[0].profile_data | keys' sample.jsonStep 3: Enrich the same 25 with LeadOcean
Enrichment takes exactly one of linkedin_url, email, phone or person_id. Use person_id: about a fifth of the people search finds have no LinkedIn URL. Add reveal_email to get the address back.
while read -r id; do
curl -s -X POST "https://api.leadocean.io/v1/people/enrich" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"person_id\": \"$id\", \"reveal_email\": true}" \
>> leadocean_out.jsonl
echo >> leadocean_out.jsonl
sleep 0.25
done < testset_ids.txtThe sleep keeps you under the Free plan's 5 requests a second. A person LeadOcean holds nothing for returns a 404 and costs nothing. Each hit costs one record.
Emails come back in contact_data.contact_emails[] and phones in contact_data.contact_phones[].phone. Since October 2026 the response also carries career history in resume_data, which is useful for checking whether a title is current.
Step 4: Run the other tools on the same 25
Feed the same 25 people to each tool. Use CSV upload, a CRM sync or its API. Note three things per tool: the inputs you gave it, how long it took, and what it charged.
Do not use a tool's own sample list. Use your 25. Write every result into one sheet with the same columns.
person, tool, email_1_address, email_1_status, phone_1_number, title_current
Jane Doe, leadocean, jane@acme.com, verified, +1 415 555 0133, Director of RevOps
Jane Doe, tool_b, jane@acme.com, , , Director of RevOpsThe email and phone columns use LeadOcean's export header names, so a later bulk export drops into the same sheet.
Step 5: Score fill rate, then usable rate
Fill rate is the share of rows where a field is not empty. Usable rate is stricter: an email counts only if it is verified, and a phone only if it connects when you call it.
import csv
from collections import defaultdict
rows = defaultdict(lambda: {"n": 0, "email": 0, "usable": 0, "phone": 0})
with open("results.csv") as f:
for r in csv.DictReader(f):
t = rows[r["tool"]]
t["n"] += 1
if r["email_1_address"]:
t["email"] += 1
if r["email_1_status"] in ("verified", "catch_all_valid"):
t["usable"] += 1
if r["phone_1_number"]:
t["phone"] += 1
for tool, t in rows.items():
n = t["n"]
print(f'{tool:12} email {t["email"]/n:.0%} usable {t["usable"]/n:.0%} phone {t["phone"]/n:.0%}')If a tool gives no email status at all, count its usable rate as zero until you verify the addresses separately. Send five of the emails from each tool to a seed inbox you own and note the bounces. Twenty-five rows is a small sample. It is enough to catch a tool that is clearly worse, not to rank two close ones.
Step 6: Compute cost per usable contact
Divide what you pay by the usable contacts you get.
cost per usable contact = monthly price / (contacts enriched per month x usable rate)Use the monthly list price, then the billed-yearly figure beside it. For LeadOcean the plans are Free at $0 for 1,000 records for life, Solo at $97 a month, Agency at $497 a month and Platform at $1,997 a month. Paid plans have a fair-use pace instead of credits, so there is no per-record price to plug in. Use the volume you really run through it.
For the other tools, read the price off the pricing page and write the date next to it. If a tool shows no public price at your volume, write that down. Do not extrapolate a rate to a volume it does not list.
Then decide. Keep the tool with the best usable rate for each field you need. Cancel any tool whose unique contribution is under a tenth of your rows.
What you get
The sizing call from Step 1, run on 2026-10-21 with the leadocean_count_leads MCP tool. It cost 0 records.
{
"total": 938,
"totalIsExact": true,
"totalAll": 1538,
"distinctCompanies": 712,
"credits": 0
}That is 938 US Directors and VPs with "Revenue Operations" in the title at Software Development companies. The count includes only emails that are verified, catch_all_valid or catch_all. Without the email condition the same filters match 1,538 people. It is a mailable count of those filters, nothing more.
An enrichment record, as a response shape from the API reference. Values are placeholders and profile_data is abbreviated:
{
"profile_data": {},
"contact_data": {
"has_email": true,
"has_phone": true,
"email_status": "verified",
"email_type": "work",
"contact_emails": [{ "email": "jane.doe@acme.com", "type": "work", "status": "verified" }],
"contact_phones": [{ "phone": "+14155550133", "type": "mobile" }],
"contact_current_experiences": []
},
"resume_data": {},
"meta": { "source_ids": { "person_id": "1234567" } }
}Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing or wrong x-api-key | Check $LEADOCEAN_API_KEY is exported in this shell. |
| 429 | Over the plan's rate limit (5 a second on Free, 200 on Agency) | Honor Retry-After, or raise the sleep in the loop. |
| 402 | The 1,000 free records are spent | Upgrade, or run the test on a smaller sample. |
Empty data | A filter value that is not an exact enum string | Resolve it with GET /v1/enums or leadocean_list_enum_values. |
| 404 on enrich | LeadOcean holds nothing for that person | Free. Log it as a miss for that tool and move on. |
| Export refused | Over 50,000 rows a request or 500,000 a day, or on the Free plan | Split the export or narrow the filters. |
FAQ
Why 25 contacts?
It is the default search page and it fits in one afternoon. It will not separate two close tools, but it exposes a tool that fails on your segment.
Can I run this on a bigger sample?
Yes. One search pages to 10,000 rows on any plan. Bulk CSV export takes up to 50,000 rows a request, and it is not on the Free plan.
Does the test cover phone numbers?
Add GET /v2/people/phone for a dedicated lookup. Phones also appear in enrichment output under contact_phones.
How often should we repeat it?
Twice a year, and before every renewal. The LeadOcean dataset is refreshed monthly, and each record carries its own fetched_at date.
Related reading:
Test your enrichment stack against 1,000 free records
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →