Your CRM data is a mess because contacts age and nobody owns the fix. The cure is the same every time: find the gaps, enrich from one source, write back only what you trust. These 8 enrichment tools all do the middle step. The recipe below uses LeadOcean for the requests, because every call is shown with real output.
TL;DR
- Export your contacts, count the gaps (missing email, stale title, no company domain), then enrich only those rows.
- A 25-row test takes under an hour and spends about 25 LeadOcean records. A person who is not found costs nothing.
- Free gives you 1,000 records for life, no card. That covers a small CRM end to end.
Prerequisites
- A CRM export as CSV with at least
emailorlinkedin_urlper row. - A LeadOcean API key in
$LEADOCEAN_API_KEY(free at app.leadocean.io, no card). curland Python 3 withrequestsinstalled.
Step 1: Audit what is actually broken
Count the gaps before you spend anything. Four numbers matter: rows with no email, rows with no job title, rows with no company domain and duplicates.
import csv
from collections import Counter
rows = list(csv.DictReader(open("crm_export.csv")))
gaps = Counter()
seen = Counter(r["email"].lower() for r in rows if r.get("email"))
for r in rows:
if not r.get("email"): gaps["no_email"] += 1
if not r.get("title"): gaps["no_title"] += 1
if not r.get("domain"): gaps["no_domain"] += 1
gaps["duplicate_emails"] = sum(1 for n in seen.values() if n > 1)
print(len(rows), dict(gaps))Enrich the rows with a key to look up (an email or a LinkedIn URL). Deal with the rest by hand.
Step 2: Pick the tool for the job
Pick on entry price and on whether you can call it from a script. Each has an API, but plan access differs (Cognism needs a package that includes it). Check the plan before you build.
| Tool | Entry paid plan, monthly list | Billed yearly | Free tier |
|---|---|---|---|
| LeadOcean | Solo $97 a month | n/a | 1,000 records for life, no card |
| Apollo | Basic $65 per seat, 2,500 credits | $49 per seat | 75 credits a month |
| Hunter | Starter $49, 2,000 credits | $34 | 50 credits a month |
| FullEnrich | Pro from $29, 500 credits | $26 (6,000 credits a year) | 50 credits trial |
| Lusha | Starter $49.90, 400 credits | $37.45 | 40 credits a month |
| Clay | Launch $185, 15,000 Actions and 2,500 Data Credits | $167 | 100 Data Credits a month |
| People Data Labs | Pro $98, 350 credits | $78 | 100 credits a month, contact data obscured |
| Cognism | No public price | No public price | None listed |
Sources, all checked September 2026: Apollo, Hunter, FullEnrich, Lusha, Clay, People Data Labs, Cognism.
Our pick: a developer with a script wants an API with flat pricing, so start with LeadOcean's free plan. A team that lives in a spreadsheet UI should look at Clay. See the wider list in best CRM data enrichment tools.
Step 3: Enrich one row and read the answer
Enrich takes exactly one of linkedin_url, email, phone or person_id. Send a single known contact first.
curl -s -X POST https://api.leadocean.io/v1/people/enrich \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email": "jane.doe@acme.com", "reveal_email": true}'One call costs one record. If nobody matches you get a 404 and pay nothing. Since October 2026 the response also carries career history (resume_data: past roles, education, certifications), useful for spotting job changes.
Step 4: Size the full refresh for free
Before you loop over 10,000 rows, size the audience you want to top up. A count spends no records. Send count=true as a query parameter with limit=1.
curl -s -X POST "https://api.leadocean.io/v1/people/search?count=true&limit=1" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"country": ["US"], "jobLevel": ["VP"], "jobFunction": ["Sales & Business Development"]}'Over REST, meta.total is capped at 100,000, so a big audience shows 100,000 with totalIsExact false. The MCP count below has no such cap.
Company lists work the same way with GET /v1/companies/count, and the MCP tool leadocean_count_companies does it from a chat. Use exact enum strings. Look them up with leadocean_list_enum_values or GET /v1/enums.
Step 5: Enrich the gaps in a loop and write back
Loop over the audit rows from Step 1. Respect the rate limit (5 requests a second on Free) and write back only fields you got.
import os, time, csv, requests
URL = "https://api.leadocean.io/v1/people/enrich"
H = {"x-api-key": os.environ["LEADOCEAN_API_KEY"]}
rows = list(csv.DictReader(open("crm_export.csv")))
out = []
for r in rows:
if not r.get("email"):
continue
while True:
resp = requests.post(URL, headers=H, json={"email": r["email"]}, timeout=30)
if resp.status_code != 429:
break
time.sleep(int(resp.headers.get("Retry-After", "1")))
if resp.status_code == 404:
continue # not found, costs nothing
resp.raise_for_status()
c = resp.json()["data"].get("contact_data") or {}
role = (c.get("contact_current_experiences") or [{}])[0]
out.append({**r,
"email_status": c.get("email_status"),
"current_title": role.get("job_title"),
"current_domain": role.get("company_domain")})
time.sleep(0.25)
if out:
with open("crm_enriched.csv", "w", newline="") as f:
w = csv.DictWriter(f, fieldnames=list(out[0].keys()))
w.writeheader()
w.writerows(out)
print(len(out), "rows enriched")Import the file with your CRM's own CSV importer, mapping on email. For a one-off of tens of thousands of rows use POST /v1/exports (Solo and up) or the Exports page in app.leadocean.io. Read email_status before you send anything: there is no refund for bounces.
What you get
A free count run on 2026-10-21 through the leadocean_count_leads MCP tool, filters US, VP, Sales & Business Development:
total: 129494 (mailable: verified, catch_all_valid, catch_all)
totalAll: 350517 (same filters, no email condition)
distinctCompanies: 58121 (estimate above 40,000)
credits: 0The enrich response has this shape (response shape from the API reference, placeholder values):
{
"success": true,
"data": {
"profile_data": { "profile_full_name": "Jane Doe", "profile_headline": "VP Sales at Acme" },
"contact_data": {
"has_email": true,
"has_phone": false,
"email_status": "verified",
"email_type": "work",
"contact_emails": [{ "email": "jane.doe@acme.com", "type": "work", "status": "verified" }],
"contact_current_experiences": [
{ "job_title": "VP Sales", "company_name": "Acme", "company_domain": "acme.com" }
]
},
"resume_data": {
"experiences": [{ "job_title": "Sales Director", "company_name": "Example Co" }]
},
"meta": { "fetched_at": "2026-10-01" }
},
"meta": { "credits": 1, "source": "own" }
}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 a second on Free, 200 on Agency) | Sleep for the Retry-After value and slow the loop |
| 402 | Records spent (1,000 for life on Free) | Upgrade to Solo ($97 a month) or Agency ($497 a month) |
| 404 on enrich | Nobody matched that identifier | Skip the row. It cost nothing |
| Empty search results | Bad enum value, such as VP spelled Vice President | Resolve the exact string with GET /v1/enums |
| Export refused | Over 50,000 rows a request or 500,000 rows a day | Split the request and run the rest tomorrow |
FAQ
How many records does a CRM clean-up use?
About one per contact you successfully enrich. Counts, 404s and empty pages are free, so audit first.
Should I overwrite existing CRM fields?
No. Fill empty fields and flag changed titles for review. Write email_status to its own field.
How often should I re-run it?
The dataset refreshes monthly, and each record has its own fetched_at date. Quarterly is enough for most CRMs.
Can I do this without code?
For fresh contacts, yes. The Exports page in app.leadocean.io has a filter builder, a column picker and a price preview before you download. For rows already in your CRM, use the loop above, or ask an MCP client to run leadocean_get_lead per row.
For the longer tool list see dirty CRM data: 9 enrichment tools, what CRM enrichment adds and pricing.
Clean your CRM with a free API key
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →