Pre-Black Friday: 20% off, for as long as you stayEARLYBFon Solo and AgencyEnds Nov 28Start free

How-to guide

How to Audit Your Enrichment Stack (a SaaS Team, Up to 8 Tools)

Run the same 25 contacts through every tool you pay for, score what comes back and keep the ones that earn their seat.

Get your free API key →Free to start. No credit card. 1,000 records to spend whenever you like.

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, jq and 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.

bash
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.

bash
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.json

That 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.

bash
jq -r '.data[].meta.source_ids.person_id' sample.json > testset_ids.txt
jq '.data[0].profile_data | keys' sample.json

Step 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.

bash
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.txt

The 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.

text
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 RevOps

The 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.

python
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.

text
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.

json
{
  "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:

json
{
  "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

ErrorCauseFix
401Missing or wrong x-api-keyCheck $LEADOCEAN_API_KEY is exported in this shell.
429Over the plan's rate limit (5 a second on Free, 200 on Agency)Honor Retry-After, or raise the sleep in the loop.
402The 1,000 free records are spentUpgrade, or run the test on a smaller sample.
Empty dataA filter value that is not an exact enum stringResolve it with GET /v1/enums or leadocean_list_enum_values.
404 on enrichLeadOcean holds nothing for that personFree. Log it as a miss for that tool and move on.
Export refusedOver 50,000 rows a request or 500,000 a day, or on the Free planSplit 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 →