TL;DR
- Score every API on the same five calls: a free size check, a search, an enrichment, a miss, and a rate-limit burst. Developer experience shows up in those, not in feature lists.
- The first API takes about 30 minutes and the rest about 10 each. On LeadOcean the whole run spends 4 records.
- Our scorecard is below. We did not run these nine in production for you, so the table uses each vendor's own docs, with sources and the month checked.
Prerequisites
- A LeadOcean API key from the free plan, exported as
$LEADOCEAN_API_KEY. Free is 1,000 records for life, no card, 5 requests a second. curlandjq.- A free or trial key for each competitor you want to score. Some have none (see the table).
- A text file for the scorecard. Five columns are enough.
Step 1: Fix the five checks
Pick the checks before you open any docs. Otherwise the vendor with the nicest docs site wins.
- Free size check. Can you count an audience before you pay? Counts that cost nothing remove most budget surprises.
- Search call. Is the request body flat JSON, are filters arrays, how do you page?
- Enrichment call. What identifiers does it take, and what comes back?
- Miss behavior. Does a person with no data cost you a credit?
- Rate limit. What is the ceiling on the key you will actually use, and what does a 429 tell you?
Score each 0 to 2. Maximum is 10.
Step 2: Size the audience for free
LeadOcean has two free sizing paths: the leadocean_count_leads MCP tool and count=true on search with limit=1. Run the second from the shell.
curl -s "https://api.leadocean.io/v1/people/search?count=true&limit=1" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "content-type: application/json" \
-d '{"title":["Head of Engineering","VP Engineering"],"country":["US"],"technologyCategories":["Payment processors"]}' \
| jq '.meta'count is a query parameter, not a body field. The body rejects unknown fields. meta.total is capped at 100,000, and the call spends no records.
Step 3: Pull three rows and enrich one
Search with a flat body, filters as arrays, and limit between 1 and 100. Three rows cost three records.
curl -s https://api.leadocean.io/v1/people/search \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "content-type: application/json" \
-d '{"title":["Head of Engineering"],"country":["US"],"limit":3}' \
| jq '{n: (.data|length), next: .meta.nextCursor}'Enrich takes exactly one of linkedin_url, email, phone or person_id. There is no name plus domain lookup, so search by domain first and enrich by person_id.
curl -s https://api.leadocean.io/v1/people/enrich \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "content-type: application/json" \
-d '{"person_id":"REPLACE_WITH_ID_FROM_SEARCH","reveal_email":true}'Since October 2026 the enrich response also carries career history in resume_data: past roles, education, certifications and awards. Score that in check 3 if you need it.
Step 4: Force a miss and a burst
A miss is where billing models differ most. Send an identifier we hold nothing for.
curl -s -o /dev/null -w "%{http_code}\n" \
https://api.leadocean.io/v1/people/enrich \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "content-type: application/json" \
-d '{"email":"nobody@acme.example"}'A person we hold nothing for returns 404 and costs nothing. Then check your usage with GET /v1/usage, which is also free, to confirm the counter did not move.
Now the burst. On Free the limit is 5 requests a second, so send 8 in parallel and expect 429 with a Retry-After header.
seq 8 | xargs -P 8 -I{} curl -s -o /dev/null -w "%{http_code}\n" \
https://api.leadocean.io/v1/account -H "x-api-key: $LEADOCEAN_API_KEY"Step 5: Fill the scorecard from the docs
Run steps 2 to 4 against each competitor with its own free key. Where there is none, read the docs and record what they state. This is what the nine looked like in September 2026.
| API | Free allowance | Enrichment rate limit | Source |
|---|---|---|---|
| LeadOcean | 1,000 records for life | 5 a second Free, 50 Solo, 200 Agency | facts: leadocean.io/pricing |
| Apollo | 75 credits a month | 1,000 a minute on paid plans | rate limits, pricing |
| People Data Labs | 100 credits a month, contact data obscured | 1,000 a minute on paid plans | usage limits, pricing |
| FullEnrich | 50 trial credits | 60 calls a minute | volume docs, pricing |
| Hunter | 50 credits a month | 15 a second on enrichment endpoints | API docs, pricing |
| Prospeo | 100 credits a month | 5 a second on Starter | rate limits, pricing |
| Crustdata | No public price | 15 a minute on person enrich | rate limits, pricing |
| Lusha | 40 credits a month | 50 a minute on Pro | API guide, pricing |
| LeadMagic | No free plan | 600 a minute on Email Finder | Email Finder, plans |
All competitor figures were read in September 2026. Limits differ in unit (second against minute) and in scope (per key, per team, per endpoint), so convert before you compare.
How we read it
- Highest ceiling on paper: Apollo and People Data Labs at 1,000 a minute on paid plans. That is about 17 a second.
- Lowest ceiling: Crustdata at 15 a minute and FullEnrich at 60 a minute by default.
- Miss billing: People Data Labs, FullEnrich, Hunter, Prospeo and LeadMagic state that a miss costs nothing for their main lookups. Check the exact endpoint before you rely on it.
- Free allowance: LeadOcean's 1,000 records are one-off and larger than any monthly free allowance in the table. The tradeoff is that they do not renew.
Step 6: Score and decide
Give each API 0, 1 or 2 per check, then add up. Use the same rubric every time.
| Check | 0 | 1 | 2 |
|---|---|---|---|
| Free size check | None | Counts cost credits | Counts are free |
| Search call | No filters, or a query-string DSL | Filters but awkward paging | Flat JSON, arrays, cursor |
| Enrichment call | One input type | Several, with gaps | Documented inputs and a clear 404 |
| Miss behavior | Misses are billed | Unclear in the docs | Misses are free |
| Rate limit | Under 1 a second, unexplained | Documented, per-minute | Documented, per-second, with Retry-After |
Then weigh the score against your own volume. A 60-a-minute limit is fine for a nightly job of 5,000 rows. It is a wall for a signup form that enriches in real time.
Two cautions. First, a free key and a paid key can behave differently, so rerun the burst on the plan you will use. Second, do not score on data quality here. This test measures the API, not the match rate. Take a sample of 200 of your own contacts for that, and send the same list to every vendor.
Step 7: Log the result
Keep one row per API in a file so you can rerun it in a quarter. Docs change. Rate limits move, free plans appear and vanish, and a score from last year is a guess.
printf "%s,%s,%s,%s\n" "$(date +%F)" "leadocean" "free-size=2,search=2,enrich=2,miss=2,rate=2" "notes" >> scorecard.csvFill the scores from what you saw, not from what the pricing page promises. If a vendor blocks your test or the docs contradict each other, write that down as an open item instead of guessing.
What you get
Here is the free count call from the LeadOcean MCP server, run on 2026-10-21. It spent 0 records.
{
"query": {"title": ["Head of Engineering", "VP Engineering"], "country": ["US"], "technologyCategories": ["Payment processors"]},
"total": 30,
"totalIsExact": true,
"totalAll": 53,
"distinctCompanies": 19
}total counts only mailable people (verified, catch_all_valid or catch_all). totalAll is the same filters with no email condition. The gap is the point: always say which one you quote.
A search row looks like this (response shape from the API reference, placeholder values):
{
"success": true,
"data": [{
"profile_data": {"profile_full_name": "Jane Doe"},
"contact_data": {
"contact_current_experiences": [{"job_title": "Head of Engineering", "company_name": "Acme", "company_domain": "acme.com"}],
"has_email": true,
"email_status": "verified"
},
"meta": {"source_ids": {"person_id": "123456", "person_group_id": "123456"}}
}],
"meta": {"count": 3, "limit": 3, "nextCursor": "<opaque cursor>"}
}Send meta.source_ids.person_id back to POST /v1/people/enrich in Step 3. Pass meta.nextCursor back unchanged to page.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing or wrong x-api-key | Re-export $LEADOCEAN_API_KEY and check there is no trailing newline |
| 429 | Above the plan's rate limit (5 a second on Free, 200 on Agency) | Back off for the Retry-After value, or move up a plan |
| 402 | The 1,000 free records are spent | Open app.leadocean.io and pick a plan, or narrow the sample |
Empty data | Enum filter value is not an exact string | Look it up with leadocean_list_enum_values, for example jobLevel C-Team |
| Count looks stuck at 100,000 | meta.total is capped there | Add filters until the total falls below the cap |
| Export refused | Over 50,000 rows a request or 500,000 a day | Split the request by country or seniority |
FAQ
Is this a ranking?
No. It is a method plus a table of documented limits. Developer experience depends on your volume, language and budget, so rank the nine yourself with the scorecard.
Which API is best for a small team that writes code?
The one that lets you finish the five checks without a card. LeadOcean's free plan covers all five. Hunter, Apollo, Prospeo and People Data Labs have free plans too, with monthly allowances.
Why test misses?
Pipelines retry. If every miss costs a credit, a retry loop on a bad list burns budget fast.
Can I do this from an AI client instead of curl?
Yes. LeadOcean's hosted MCP server at https://api.leadocean.io/mcp has 13 tools. Sign in with OAuth, no key pasted. See data enrichment for developers.
Where do I read more?
Start with company enrichment API and best lead enrichment tools for developers. Prices are on the pricing page.
Score LeadOcean on your own five calls
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →