Lookalike search breaks because the tool hides the query. It turns your reference profile into a bag of keywords. You cannot see what it matched on, so you cannot fix it. The fix is to write the query down as filters, size it for free, and check each hit's career history before you spend anything.
Crustdata's write-up (checked October 2026) names the failure modes well: keyword flattening, stale roles and big-company bias. This page shows the same fixes as requests you can run.
Crustdata describes the mechanism in three parts. The tool tokenizes the profile, so "GCP" and "cloud" become loose words. It ranks by overlap, so a long career with many old keywords beats a short, current one. It over-represents large employers, because they have the most profiles. Each is fixable once the query is visible.
TL;DR
- Replace "find more like this" with a filter set you can read: current title, seniority, profile keywords, country, company size.
- Count the audience for free first, then pull one page of 25, then check career history on the few you care about.
- Takes about 30 minutes and spends about 30 records. Counts cost nothing.
Prerequisites
- A LeadOcean API key in
$LEADOCEAN_API_KEY. The Free plan has 1,000 records for life and no card. curlandjq. Requests go tohttps://api.leadocean.iowith the headerx-api-key.- One reference profile you like. Write down its current title, seniority, country, company size and two or three skills.
Step 1: Decompose the reference profile into filters
Turn the profile into explicit fields. Each field is a filter you can loosen alone.
{
"reference": "Senior platform engineer, 150-person company, US, runs GCP and Kubernetes",
"filters": {
"title": ["Site Reliability Engineer", "Platform Engineer"],
"seniority": ["senior"],
"keywords": ["GCP", "Kubernetes"],
"country": ["US"],
"employeeRange": ["51-200", "201-500"]
}
}Three notes. title matches the current role, so a 2016 job does not count. keywords search the person's own profile text, and several values OR together. employeeRange is your answer to big-company bias: you pick the stage.
Step 2: Size it for free before you pull anything
Counts spend no records. Run the vague version and the explicit version and compare.
// What a "find more like this" tool effectively runs: one fuzzy phrase
// MCP tool: leadocean_count_leads
{ "q": "cloud infrastructure engineer GCP", "facets": false }
// The explicit version from Step 1
{
"title": ["Site Reliability Engineer", "Platform Engineer"],
"keywords": ["GCP", "Kubernetes"],
"seniority": ["senior"],
"country": ["US"],
"facets": false
}The same counts over REST use 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 '{"title":["Site Reliability Engineer","Platform Engineer"],"keywords":["GCP","Kubernetes"],"seniority":["senior"],"country":["US"]}'meta.total is capped at 100,000. If the explicit query still returns a huge number, add filters. If it returns almost nothing, drop one at a time.
Step 3: Pull one page of the explicit query
Search body is flat JSON, filters are arrays, limit runs 1 to 100. Pull 25 and read them before pulling more.
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": ["Site Reliability Engineer", "Platform Engineer"],
"keywords": ["GCP", "Kubernetes"],
"seniority": ["senior"],
"country": ["US"],
"limit": 25
}' | jq '.meta, (.data | length)'That spends 25 records. Page further with cursor from meta.nextCursor. One search pages to 10,000 rows, and meta.depthCapped tells you when you hit it. Narrow the filters instead of walking the index.
Step 4: Check recency and career history on the finalists
This is the part keyword tools skip. Person enrich returns the current role in contact_current_experiences[] and past roles in resume_data.experiences[]. 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 '{"person_id": "PERSON_ID_FROM_STEP_3"}' \
| jq '.data | {current: .contact_data.contact_current_experiences, past: .resume_data.experiences}'Now apply your own rules: current title matches, the GCP work is in recent roles, the company is in your size band. Enrichment that finds nobody is a 404 and costs nothing. Past employers carry no company_id yet, so match them by name.
Step 5: Score recency with a rule, not a percentage
A match percentage hides why someone ranked. Write the rule instead and apply it to the enrich output. For example: the most recent role is the target title, and GCP appears in the last two roles. Anyone who fails goes in a "maybe" list, not the shortlist.
jq '[.data.resume_data.experiences[:2][] | select((.job_title // .title // "") | test("platform|site reliability|SRE"; "i"))] | length' enriched.jsonSave one enrich response as enriched.json first. A result of 0 means neither of the two most recent past roles was a platform job. The rule is crude on purpose. You can read it, argue with it and change it.
Step 6: Widen sideways, one filter at a time
Adjacent pools are a filter change, not a guess. Swap one field and recount for free.
{ "title": ["DevOps Engineer", "Infrastructure Engineer"], "keywords": ["GCP", "Kubernetes"], "seniority": ["senior"], "country": ["US"] }Or move the industry. Use leadocean_list_enum_values to resolve exact industry strings, because enum values are exact and case matters. Recount after each change, and keep a note of which change added how many people.
When the list is right, export it. POST /v1/exports takes up to 50,000 rows a request and 500,000 a day. Bulk CSV export is not on Free, so use the app's Exports page or a paid plan for this last step.
What you get
These are real free counts from the leadocean_count_leads MCP tool, run on 2026-10-21. They count mailable addresses (verified, catch_all_valid or catch_all), not everyone.
| Query | Mailable total | Same filters, any email |
|---|---|---|
q: "cloud infrastructure engineer GCP" | 179,562 across about 48,980 employers | 479,190 |
| Explicit: title SRE or Platform Engineer, keywords GCP or Kubernetes, seniority senior, country US | 60 across 54 employers | 130 |
| Same plus employeeRange 51-200 or 201-500 | 6 across 6 employers | 15 |
The fuzzy query returns thousands of people you cannot audit. The explicit one returns a list small enough to read. That is the point, not a flaw: every row passed a rule you wrote.
A search row looks like this (response shape from the API reference, placeholder values). Rows are thin: no contact points until you enrich.
{
"success": true,
"data": [
{
"profile_data": {
"profile_full_name": "Jane Doe",
"profile_headline": "Senior Platform Engineer at Acme"
},
"contact_data": {
"has_email": true,
"email_status": "verified",
"contact_current_experiences": [
{ "job_title": "Senior Platform Engineer", "company_name": "Acme", "company_domain": "acme.com" }
]
},
"meta": { "source_ids": { "person_id": "1234567" }, "fetched_at": "2026-10-01" }
}
],
"meta": { "count": 1, "limit": 25, "credits": 1, "nextCursor": "opaque-string", "depthCapped": false }
}Every field you filtered on is visible in the record. Nothing is a score.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing or wrong x-api-key header | Check $LEADOCEAN_API_KEY is exported and sent as x-api-key. |
| 429 | Above your plan's rate limit (5 requests a second on Free, 200 on Agency) | Wait for Retry-After, then slow the loop. |
| 402 | The 1,000 free records are spent | Upgrade to Solo ($97 a month) or Agency ($497 a month). |
| Empty results | A bad enum value, such as Senior instead of senior | Resolve values with leadocean_list_enum_values. Enum strings are exact. |
| 404 on enrich | Nobody matched that person_id | Free. Try a different identifier. |
Search stops with depthCapped: true | One search pages to 10,000 rows | Add filters and run a narrower search. |
FAQ
Why do lookalike tools return so many wrong candidates?
They convert the reference profile into keyword tokens and hide the query. Overlapping words score high even when the role, seniority or company stage differs. Explicit filters make each mismatch fixable.
Does LeadOcean have a "find similar" button?
No. It gives you the filters, a free count and the career history, and you decide what "similar" means. The explicit query is the lookalike.
How do I avoid people who left the role years ago?
title filters the current role. Then check resume_data.experiences[] on enrich to see when the relevant work happened.
Does a bigger result list mean a better search?
No. The fuzzy query above returns 179,562 mailable people, and nobody can review that. A list of 60 that all passed written rules is more useful to a recruiter than 60,000 ranked by a hidden score.
Why is the explicit count so small?
Profile keywords and a company-size band both need to be held on the record. A record without a company headcount is never returned for a size filter. Loosen one filter at a time and recount.
Can I run this from an AI agent?
Yes. The hosted MCP server at https://api.leadocean.io/mcp exposes 13 tools, and the counts above came from one of them. For the company side, see best lookalike company search tools. For signals beyond profiles, see web search API to enrich candidates. Terms are in the lookalike audiences glossary entry. Plans are on pricing. More guides are in the blog hub.
Write the query you can read, then count it for free
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →