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

How-to guide

How to Build Multi-Layer Candidate Filtering (with Claude and the LeadOcean API)

Four layers, cheapest first. Count for free, filter in the query, pull a small page, and let Claude judge career history on the survivors only.

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

Run the cheap filters first and the expensive one last. Free counts and query filters cut a market of thousands to a few dozen people. Claude then reads career history for only those few.

TL;DR

  • Four layers: free count, structured filters, keyword filters, then a Claude judge on career history.
  • Time: about 25 minutes. Records: about 35 (25 from search, 10 from enrich). The counts cost nothing.
  • Everything fits inside the free plan: 1,000 records for life, no card.

Prerequisites

  • A LeadOcean API key from app.leadocean.io, stored as $LEADOCEAN_API_KEY.
  • Claude Code, or Claude with a remote MCP connector. The MCP server is at https://api.leadocean.io/mcp and signs in with OAuth, so no key is pasted.
  • curl and jq for the REST steps.
  • A role brief. This guide uses a firmware engineer in the US at a company with 51 to 500 employees.

Step 1: Size the pool with a free count

Layer one is a count. It spends no records, so run it before every search.

Connect LeadOcean to Claude Code.

bash
claude mcp add --transport http leadocean https://api.leadocean.io/mcp

Then ask Claude to count, one filter at a time.

text
Use leadocean_count_leads. Count title "Firmware Engineer" in country US.
Then add employeeRange 51-200 and 201-500. Then add keywords "embedded C" and "RTOS".
Report total, totalAll and distinctCompanies for each. Do not search.

Over REST, the same sizing call is a search with count=true as a query parameter and limit=1. The parameter goes in the URL, not the JSON body.

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":["Firmware Engineer"],"country":["US"],"limit":1}' | jq '.meta'

meta.total caps at 100,000. If a count is near that, add filters before you do anything else.

Step 2: Add structured filters

Layer two is the filters that do not need judgment: title, country, company size. Titles are job title keywords. Wrap a value in brackets for an exact match.

Check enum values before you guess. They are exact strings, and a wrong one returns nothing.

text
Call leadocean_list_enum_values with name employee_range, then name job_level.

The company size values are 51-200 and 201-500, and seniority values are lowercase (senior, director). Add excludeTitle for the noise you already know about:

json
{
  "title": ["Firmware Engineer", "Embedded Software Engineer"],
  "excludeTitle": ["Intern", "Test", "Manager"],
  "country": ["US"],
  "employeeRange": ["51-200", "201-500"],
  "excludeCompanyName": ["Acme"]
}

Use excludeCompanyName for your own employer so you never source from yourself.

Step 3: Add keyword filters on the profile text

Layer three checks what the person wrote about their own work. keywords matches the profile headline, summary, skills and job descriptions. Each value is a phrase whose words must all appear, and several values OR together.

This is the layer that separates a firmware engineer from someone who mentions firmware once. Use excludeKeywords to drop the false friends.

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": ["Firmware Engineer"],
    "country": ["US"],
    "employeeRange": ["51-200", "201-500"],
    "keywords": ["embedded C", "RTOS"],
    "excludeKeywords": ["quality assurance"],
    "limit": 25,
    "fields": [
      "profile_data.profile_full_name",
      "contact_data.contact_current_experiences",
      "meta.source_ids"
    ]
  }' > candidates.json
jq '.meta' candidates.json
jq -r '.data[].meta.source_ids.person_id' candidates.json > ids.txt

This spends 25 records. The body is flat JSON, every filter is an array, and limit runs from 1 to 100. Page with meta.nextCursor only if the first 25 are good. One search pages to 10,000 rows.

Step 4: Judge career history with Claude

Layer four is the only one that needs a model. It runs on 10 people, not 10,000.

Person enrich returns career history since October 2026: past roles, education, certifications and awards. Enrich takes exactly one of linkedin_url, email, phone or person_id. Search first, then enrich by person_id.

bash
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_SEARCH"}' > candidate_01.json

Repeat for the top 10 and save each file. Then give Claude Code this prompt:

text
Read candidate_*.json. Role: Senior Firmware Engineer, US, startup to mid-size.
For each candidate return JSON: name, verdict (strong | maybe | no), and three fields:
- years_in_firmware: sum only roles whose title or description is firmware work
- recency: is the most recent role still firmware? yes | no
- evidence: one quote of up to 15 words from a past role that supports the verdict
Rules: ignore the headline. Judge only data.resume_data.experiences and data.contact_data.contact_current_experiences.
If a field is missing, say "not held". Never guess.
Sort strong first. Output one table, then the JSON.

Two rules keep the judge honest. Make it quote the evidence, and make it say "not held" instead of guessing. Past employers carry no company_id yet, so match them by name.

What you get

On 21 October 2026, the free leadocean_count_leads tool returned these counts for the same role, layer by layer. Counts use the tool's default of mailable addresses (verified, catch-all valid or catch-all).

LayerFilters addedMailableEveryone matched
1title "Firmware Engineer", country US5,50810,998
2employeeRange 51-200, 201-5005241,735
3keywords "embedded C", "RTOS"289911

The 289 span 214 distinct companies. Layer four then reads 10 of them.

Enrich response shape from the API reference and the enrichment docs, with placeholder values.

jsonc
{
  "success": true,
  "data": {
    "profile_data": { "profile_full_name": "Jane Doe" },
    "contact_data": {
      "contact_current_experiences": [
        { "job_title": "Senior Firmware Engineer", "company_name": "Acme", "company_domain": "acme.com" }
      ]
    },
    "resume_data": {
      "experiences": [
        { "job_title": "Embedded Software Engineer", "company_name": "Example Co" /* plus job_seniority, start and end dates */ }
      ],
      "educations": [],
      "certifications": []
    }
  }
}

The docs name the experience fields (company_name, job_title, job_seniority, start and end dates) but not every key, so print one record with jq before you write code against the dates.

Troubleshooting

ErrorCauseFix
401Missing or wrong x-api-key headerCheck $LEADOCEAN_API_KEY is exported in the shell that runs curl.
429Above the plan's rate limit: 5 requests a second on Free, 200 on AgencyWait the Retry-After seconds. Enrich candidates one at a time.
402The 1,000 free records are spentUpgrade to Solo at $97 a month, or narrow the layers so fewer records are pulled.
200 with no rowsA bad enum value, such as Senior where the value is seniorRun leadocean_list_enum_values and copy the exact string.
404 on enrichNobody matched the person_idFree, nothing is billed. Re-run the search and take a fresh ID.
400 or 429 on an export400: over 50,000 rows in one export. 429: past 500,000 rows a daySplit the filters into batches under the ceiling. Bulk CSV export is not on Free.

FAQ

How many records does this cost?

About 35 for a test run: 25 from the search page and 10 from enrich. Counts, filter lists and a 404 enrich cost nothing. The free plan has 1,000 records for life.

Can I run this without Claude Code?

Yes. Steps 1 to 3 are plain curl. For step 4, paste the saved JSON into any Claude chat that has the prompt, or connect the LeadOcean MCP server as a remote connector. Claude's MCP docs show how (September 2026).

Why not let Claude judge everyone?

Cost and noise. A model reading thousands of profiles spends time and tokens on people a keyword filter would have dropped. The Crustdata guide (September 2026) argues the same order: cheap filters first, the model last.

Does this give me emails and phones?

Search returns has_email and has_phone flags, not addresses. Enrich with reveal_email: true returns the addresses. Phones come from GET /v2/people/phone, and each call is one record. Read email_status before you send anything, because bounced emails are not refunded.

Where do I go from here?

Build the sourcing brief with build an ICP for B2B. Then wire the shortlist into outreach with Claude Code email sequences, or see the wider recruiting workflows with Claude. More guides are in the blog, and plans are on pricing.

Filter candidates in layers, and pay only for the survivors

Free to start. No credit card. 1,000 records to spend whenever you like.

Get your free API key →