Rank tools on two numbers: the share of a fixed sample that still holds the right job, and the list price of one record. This guide builds both from a 50-person sample you control.
TL;DR
- Pull 50 people once, run them through each tool, and score the share whose title and employer are still right.
- It takes about 45 minutes and spends 50 LeadOcean records. Sizing the audience is free.
- Price is a separate column. Divide each plan's list price by what it actually gives you per record, monthly price first, billed-yearly figure after.
Prerequisites
- A LeadOcean API key in
$LEADOCEAN_API_KEY. The free plan has 1,000 records for life and no card. - A trial or free key for each tool you want to rank.
curl,jqand Python 3.- Ten minutes per tool to check profiles on LinkedIn by hand.
Step 1: Size the sample for free
Pick one narrow audience and size it before you spend anything. A count spends no 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 '{
"jobLevel": ["VP"],
"jobFunction": ["Sales & Business Development"],
"country": ["US"],
"limit": 1
}'Enum values are exact strings. If a filter returns nothing, check the spelling with leadocean_list_enum_values or GET /v1/enums.
Step 2: Pull 50 people as the fixed sample
Use one page of 50. Do not cherry-pick: a hand-picked sample hides stale rows.
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"],
"limit": 50
}' > sample.jsonThis costs 50 records. Save the list. Every other tool gets the same people, so you compare like with like.
Step 3: Run the same people through each tool
Look each person up in the other tools by LinkedIn URL or email, whichever the tool accepts. Record the title and employer each one returns in one CSV.
person,truth_title,truth_company,tool,tool_title,tool_company
Jane Doe,VP Sales,Acme,toolA,VP Sales,Acme
Jane Doe,VP Sales,Acme,toolB,Director of Sales,Globextruth_* is what the person's public LinkedIn profile shows today. Check it by hand, on the same day you run the tools. Note that LeadOcean's dataset is refreshed monthly and each record carries its own fetched_at date, so log that date for every LeadOcean row.
Step 4: Score freshness
A row is fresh when the employer matches. A row is fully fresh when the title matches too. Two numbers per tool, nothing fancier.
import csv, collections
hit = collections.defaultdict(lambda: [0, 0, 0]) # rows, employer ok, title ok
for r in csv.DictReader(open("scores.csv")):
h = hit[r["tool"]]
h[0] += 1
h[1] += r["tool_company"].strip().lower() == r["truth_company"].strip().lower()
h[2] += r["tool_title"].strip().lower() == r["truth_title"].strip().lower()
for tool, (n, emp, ttl) in sorted(hit.items(), key=lambda x: -x[1][1]):
print(f"{tool}: employer {emp}/{n} ({emp/n:.0%}), title {ttl}/{n} ({ttl/n:.0%})")Employer match is the number that matters for outbound. A wrong title is an awkward email. A wrong employer is a bounce or a wasted call.
Fifty rows is a spot check, not a benchmark. A gap of three or four rows is noise. Rerun on a second audience before you trust a ranking.
Step 5: Put a price on one record
List price per record is plan price divided by records, and every vendor counts differently. Use the vendor's own unit, then convert.
| Tool | Entry list price (monthly first, yearly after) | What a record costs |
|---|---|---|
| Apollo | Basic $65 per seat a month, 2,500 credits; $49 billed yearly (pricing, September 2026) | An email is 1 credit, a phone 8 credits (API pricing, September 2026). About $0.026 an email on the monthly price |
| Lusha | Starter $49.90 a month, 400 credits; $37.45 billed yearly (pricing, September 2026) | About $0.12 a credit monthly, $0.09 yearly |
| People Data Labs | Pro from $98 a month for 350 credits; $78 billed yearly (pricing, September 2026) | $0.28 a credit at entry. No public 1M price |
| ZoomInfo (GTM AI) | Pay as you go from $20; $0.45 a credit at 100 credits, $0.25 at 100,000 (pricing, September 2026) | One credit per record. No public 1M price |
| Coresignal | Mini $49 a month, 2,500 credits (pricing, September 2026) | A Base employee record is 10 credits (docs, September 2026), so about $0.20 a record at Mini |
| Crustdata | No public dollar price (pricing, September 2026) | No public 1M price |
| LeadOcean | Free $0, Solo $97, Agency $497 a month, Platform $1,997 a month (pricing) | No per-record price on paid plans: a fair-use pace, not credits |
Credits are not records. Apollo credits expire each cycle and do not roll over. Multiply a rate only where the vendor publishes the rate for that volume. Do not extend an entry rate to 1M.
Step 6: Rank on both columns
Sort by employer match first, then price. Drop any tool that scores under your bar, whatever its price. Cheap stale data costs more in bounces than good data costs in credits.
Do not rank on refresh claims. Coresignal's homepage says its company data is delivered daily (Coresignal, September 2026), but that says nothing about how old each person row is. Your own test is the evidence that counts.
Step 7: Check the misses with career history
When a tool disagrees with LinkedIn, find out who is wrong and how old the row is. Person enrich returns past roles, so you can see whether the person moved recently. Enrich takes exactly one of linkedin_url, email, phone or person_id.
curl -s -X POST "https://api.leadocean.io/v1/people/enrich" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"linkedin_url": "https://www.linkedin.com/in/jane-doe-example"}' \
| jq '{current: .data.contact_data.contact_current_experiences, past: .data.resume_data.experiences, fetched_at: .data.meta.fetched_at}'The current role is in contact_data.contact_current_experiences[]. Past roles, with dates, are in resume_data.experiences[]. Past employers carry no company_id yet, so match them by name. An enrichment that finds nobody is a 404 and costs nothing.
Use this to sort misses into three bins. Moved in the last 90 days: the tool is stale. Moved more than a year ago: the tool is badly stale. Never moved: your hand check is wrong, so fix the truth column.
Step 8: Price the rows you keep
Price the rows you keep, not the rows you pull. Divide each tool's per-record price by its employer match rate from Step 4.
price, match = 0.28, 0.70 # per-record list price, employer match on your sample
print(f"${price / match:.2f} per usable row") # $0.40Rank on employer match, not on index size. A larger index with a lower match rate gives you fewer usable rows. Size is a coverage number. Freshness is a different number.
Test the field you will send on. If you send email, add a bounce check on the sample. Read email_status before sending, because LeadOcean does not refund or credit back bounced emails.
What you get
The free count from Step 1, run on 21 October 2026 through the leadocean_count_leads MCP tool for VPs in sales and business development in the US:
{
"total": 129494,
"totalIsExact": true,
"totalAll": 350517,
"distinctCompanies": 58121,
"credits": 0
}total counts only people with a verified, catch-all-valid or catch-all email, the tool's default. totalAll is the same filters with no email condition. distinctCompanies is an estimate.
The search in Step 2 returns a page of people plus a meta block. Response shape from the API reference, placeholder values:
{
"success": true,
"data": [
{
"profile_data": { "profile_full_name": "Jane Doe", "profile_url": "https://www.linkedin.com/in/jane-doe-example" },
"contact_data": {
"contact_current_experiences": [
{ "job_title": "VP Sales", "company_name": "Acme", "company_domain": "acme.com" }
]
},
"meta": { "source_ids": { "person_id": "p_0000000" }, "fetched_at": "2026-10-01" }
}
],
"meta": { "count": 50, "nextCursor": "NEXT_CURSOR" }
}Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing or wrong x-api-key | Check $LEADOCEAN_API_KEY is exported and sent as the x-api-key header |
| 429 | Above the plan's rate limit: 5 requests a second on Free, 200 on Agency | Slow the loop and honour the Retry-After header |
| 402 | Out of records on the Free plan | Free is 1,000 records for life. Move to a paid plan or narrow the sample |
Empty data | A filter value is not an exact enum string | Resolve it with GET /v1/enums or leadocean_list_enum_values |
meta.depthCapped is true | One search pages to 10,000 rows | Narrow the filters and run several searches |
| Export refused | Over 50,000 rows a request or 500,000 a day | Split the export. Bulk CSV is not on Free |
FAQ
How many people should I test?
Fifty is enough to see a gross gap. For a buying decision, use 200 or more across two audiences. Each extra LeadOcean page of 100 costs 100 records.
Does a higher employer match always mean fresher data?
No. A small sample can be lucky. Rerun it on a different audience and look at the people who changed jobs in the last year, since they are where stale data shows up.
Why not rank on published claims?
Most vendors do not publish a refresh cadence, and those that do mean different things by it. A claim of daily delivery says nothing about how old each person row is. See what data freshness means.
What about price at 1M records?
People Data Labs, ZoomInfo GTM AI, Lusha, Coresignal and Crustdata publish no 1M price, so do not multiply an entry rate out to 1M. Read the B2B data prospecting guide and the sales intelligence data guide, then ask each vendor for a written quote. LeadOcean plans are on the pricing page.
More tool comparisons are in the blog hub.
Test your own 50 people on LeadOcean data
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →