You pick enrichment tools by running them all on the same people and counting what comes back. Vendor fill-rate claims do not predict how a tool behaves on your accounts.
This guide is a method, not a report. We did not run this test for you, and we will not invent a winner. You build the test set, score eleven tools, and keep five. The test set comes from LeadOcean because it costs about 100 records and returns an email_status you can check.
TL;DR
- Build one 100-person test set from your own ICP, run every tool on it, and score four numbers: match rate, verified-email rate, bounce rate and cost per usable contact.
- It takes about 3 hours of your time and about 100 LeadOcean records: 50 search rows plus 50 enrichments. The sizing count in Step 1 is free.
- Keep five tools, one per job: primary source, gap filler, phone, verifier and workflow glue. Cut any tool that does not win a job.
Prerequisites
- A LeadOcean account. The free plan gives you 1,000 records, one-off, with no card: start free.
- An API key in your shell:
export LEADOCEAN_API_KEY=...(the app lists your keys). curl,jqand Python 3. Every request goes tohttps://api.leadocean.iowith thex-api-keyheader.- Trial or free access to the tools you are testing, and a small sending domain you can afford to burn for the bounce test.
Step 1: Size your ICP before you sample it
Count first. It is free and tells you if your ICP is big enough to sample fairly. Send count=true as a query parameter with limit 1 in the body.
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 '{
"jobLevel": ["VP"],
"jobFunction": ["Sales & Business Development"],
"country": ["US"],
"employeeRange": ["51-200"],
"limit": 1
}' | jq '.meta | {total, totalIsExact, distinctCompanies}'Swap in your own ICP. A test set drawn from a pool of 200 people tells you less than one drawn from 20,000, because the tools will have seen the same names.
Step 2: Draw a 50-person sample, then double it
Pull a first page of 50 and keep it as your ground truth. Each person returned costs one record. Cap limit at 50 for this pass.
curl -s -X POST "https://api.leadocean.io/v1/people/search" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jobLevel": ["VP"],
"jobFunction": ["Sales & Business Development"],
"country": ["US"],
"employeeRange": ["51-200"],
"limit": 50
}' > sample.json
jq '.meta | {count, nextCursor}' sample.jsonPass meta.nextCursor back unchanged for a second page of 50. The other 50 should come from outside LeadOcean: your CRM, closed-won contacts, last quarter's bounced list. A test set drawn only from one vendor flatters that vendor. Mix both, and label each row with its origin.
Step 3: Build the input file every tool receives
Give all eleven tools the same keys. A LinkedIn profile URL is the most portable one. List the fields in a row first, since paths can differ between versions.
jq '.data[0].profile_data | keys' sample.json
jq -r '.data[] | [.meta.source_ids.person_id] | @csv' sample.json > ids.csvThen add the profile URL and company domain columns from the keys you found, and save the result as input.csv. Upload the same file to each tool, or loop through its API. Record the date and the tool's plan for every run.
Step 4: Get the LeadOcean answer with email status
Enrich each person by person_id with reveal_email set to true. A person we hold nothing for is a 404 and costs nothing. Keep the raw JSON, since you will score it in Step 5.
mkdir -p lo
for ID in $(jq -r '.data[].meta.source_ids.person_id' sample.json); 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}" > "lo/$ID.json"
sleep 0.3
done
ls lo | wc -lFree runs at 5 requests a second, so the pause keeps you under the limit. Use the same email-status rule on every tool's output: treat verified as send, catch_all as a small-batch risk, and everything else as not usable.
Step 5: Score all eleven tools with one script
Put each tool's output in a CSV with the columns person_id, email, status. Then run the same script on every file. Four numbers matter.
import csv, sys
rows = list(csv.DictReader(open(sys.argv[1])))
n = len(rows)
found = [r for r in rows if r["email"]]
usable = [r for r in found if r["status"] in ("verified", "catch_all_valid")]
print(f"match rate: {len(found) / n:.0%}")
print(f"usable rate: {len(usable) / n:.0%}")
cost = float(sys.argv[2]) # what you paid for this run, in dollars
print(f"cost per usable contact: ${cost / max(len(usable), 1):.2f}")Match rate says how often a tool returns anything. Usable rate says how often that something is safe to send. Cost per usable contact puts tools with different pricing models on one scale. Use monthly list price first and the billed-yearly figure after it, and write down which you used.
The fourth number is the bounce rate. Send 25 emails per tool from the burner domain, then count hard bounces after 48 hours. LeadOcean gives no refund for bounced emails, so measure bounces before you pay at volume. Check each vendor's own refund terms.
Step 6: Cut eleven to five
Keep one tool per job. Do not keep two tools for the same job.
| Job | Keep the tool that wins on |
|---|---|
| Primary source | Highest usable rate on your 100 people, at a pace you can run all month |
| Gap filler | Most usable emails on the rows the primary missed |
| Phone | Highest direct-dial hit rate on the rows you must call |
| Verifier | Lowest bounce rate on the 25-email send |
| Workflow glue | Connects your CRM and sequencer without code |
A tool that wins no row of this table gets cut, however good its demo was. If your team does not call, drop the phone job and keep four. Our view: test the verifier as hard as the primary source.
Step 7: Check the result against your sales team
Numbers pick the shortlist. Reps confirm it. Give the five survivors to three reps for two weeks and ask one question each Friday: which tool did you open first?
Log every lookup a rep makes with the tool name, the outcome and the time it took. A tool with a slightly higher usable rate that needs six clicks can lose to one that fills the CRM field on its own. Then compare the log with Step 5. If a rep keeps opening a tool the score ranked fourth, find out why before you cut it.
Run one more check on price. Multiply your monthly lookup volume by each tool's cost per usable contact from Step 5. Compare that to the plan prices on the vendors' own pricing pages, and note the month you read them. On LeadOcean, Solo is $97 a month, Agency is $497 a month and Platform is $1,997 a month. Paid plans run on a fair-use pace, not credits, so your cost per usable contact is the plan price divided by the volume you run. See pricing.
What you get
The free count for the Step 1 filter, from the MCP leadocean_count_leads tool on 21 October 2026. These are mailable counts: emailStatus verified, catch_all_valid or catch_all, the default.
{
"total": 18567,
"totalIsExact": true,
"totalAll": 60456,
"distinctCompanies": 12192,
"credits": 0
}So 18,567 of the 60,456 matching people hold a mailable address, across 12,192 companies. That is a big enough pool for a fair 100-person sample.
A search row, as a response shape from the API reference. Values are placeholders and profile_data is abbreviated:
{
"success": true,
"data": [
{
"profile_data": {},
"contact_data": {
"has_email": true,
"has_phone": false,
"email_status": "verified",
"email_type": "work"
},
"meta": { "source_ids": { "person_id": "1234567" } }
}
],
"meta": { "count": 1, "nextCursor": "..." }
}After the reveal, contact_data.contact_emails holds objects with email, type, status and verified_batch_date, for example jane.doe@acme.com, work, verified.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing or wrong x-api-key | Re-copy the key from the app and check echo $LEADOCEAN_API_KEY |
| 429 | Above the plan's rate limit (5 requests a second on Free, 200 on Agency) | Keep the sleep in the loop, or honor Retry-After |
| 402 | The 1,000 free records are spent | Upgrade, or shrink the sample. Counts and 404s stay free |
| 404 on enrich | We hold nothing for that person_id | Skip it. It costs nothing and counts as a miss in your score |
Empty data array | A bad enum value, such as vp where jobLevel wants VP | Check exact strings with GET /v1/enums or leadocean_list_enum_values |
| Export refused | Over 50,000 rows a request or 500,000 a day, or Free plan | Bulk CSV export is not on Free. Split the export by filter |
FAQ
Why 100 people and not 1,000?
A hundred rows is enough to separate a 40% usable rate from a 70% one. It is not enough to rank two tools 3 points apart. If two tools tie, re-run both on a fresh 100 before you decide.
Can I run the test on the free plan?
Yes. The method spends about 100 of your 1,000 free records, and the count in Step 1 spends none. The other ten tools need their own free or trial access.
Should I test the vendors' own sample data?
No. Send them your people. Samples picked by a vendor show the vendor's best rows.
Where do the other related guides fit?
The enrichment API roundup covers what each API offers. The RevOps list and the enterprise list help you build the eleven-tool long list.
Do I need eleven tools?
No. Eleven is a long list to start from. Cut it to five after Step 6, and re-test once a year, since coverage and pricing change.
Run your 100-person enrichment test on LeadOcean
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →