You can automate candidate sourcing in Claude Code with one MCP URL, one brief file and four calls: count, search, enrich, export. This guide runs a real role end to end against LeadOcean, with the counts we measured on 21 October 2026. It is the short version. It stops where LeadOcean stops, and says so.
TL;DR
- Claude Code reads your role brief from CLAUDE.md, sizes the talent pool with a free count, then pulls only the rows you approve.
- It takes about 25 minutes the first time. The example role spends about 161 of your 1,000 free records: 10 to spot-check, 3 to enrich, 148 for the full list.
- Counts, filter lookups and enum lookups cost nothing. Every person returned costs one record.
Prerequisites
- A LeadOcean account and API key from app.leadocean.io. The free plan has no card and 1,000 records, one-off. Free keys get both the search and enrich scopes.
- The key in your shell as
$LEADOCEAN_API_KEY. - Claude Code installed, plus
curlandjqfor the REST steps.
export LEADOCEAN_API_KEY="paste-your-key-here"Step 1: Write the role brief in CLAUDE.md
Put the search in a file, not in your head. Claude Code's docs say CLAUDE.md files give Claude persistent instructions that it reads at the start of every session (Claude Code docs, September 2026). Write the filters with LeadOcean's own filter names so the agent has nothing to translate.
## Candidate sourcing
### Role: Senior Python engineer, Austin, software company
Search filters (LeadOcean names):
- title: ["Software Engineer"]
- seniority: ["senior"]
- country: ["US"]
- city: ["Austin"]
- industry: ["Software Development"]
- keywords: ["python"] (matches the person's own headline, summary and skills)
- employeeRange: ["51-200", "201-500", "501-1000"]
- emailStatus: ["verified", "catch_all_valid", "catch_all"]
### Exclusions
- Skip anyone whose profile_url is already in ats-export.csv.
- excludeCompanyName: [the names of our active clients]
### Rules
- Count first, search second, export last. State the record cost before each step.
- Stop and ask before spending more than 200 records in a session.
- Never guess skills or years of experience. Use only fields the API returned.
- Write rows to shortlists/. Show at most 10 rows in chat.Save it as CLAUDE.md in the folder where you run Claude Code, and keep one section per open role. The last two rules matter most. They keep the agent from spending records you did not approve, and from inventing experience the data does not show.
Step 2: Connect the LeadOcean MCP server
Add the hosted server as an HTTP MCP server, then sign in. Claude Code's docs say to run /mcp inside a session to authenticate a remote server with OAuth (Claude Code docs, September 2026). No key is pasted into the chat.
claude mcp add --transport http leadocean https://api.leadocean.io/mcp
claude mcp list/mcpPick leadocean and approve the sign-in. The server has 13 tools, and seven never spend records. The full table is on the Claude Code MCP page.
Step 3: Size the talent pool for free
Count before you search. Ask for the base filters first, then add one filter at a time and watch the pool shrink.
Use leadocean_count_leads with title ["Software Engineer"], country ["US"],
seniority ["senior"], keywords ["python"],
employeeRange ["51-200","201-500","501-1000"], city ["Austin"],
industry ["Software Development"]. Report the total and say which email
statuses it counted. Then count again without city and industry. Do not call any tool that spends records.The same count over REST is a search with count=true as a query parameter and limit at 1. It returns meta.total and no rows.
curl -s "https://api.leadocean.io/v1/people/search?count=true" \
-H "x-api-key: $LEADOCEAN_API_KEY" -H "content-type: application/json" \
-d '{
"title": ["Software Engineer"],
"country": ["US"],
"seniority": ["senior"],
"city": ["Austin"],
"industry": ["Software Development"],
"keywords": ["python"],
"employeeRange": ["51-200", "201-500", "501-1000"],
"emailStatus": ["verified", "catch_all_valid", "catch_all"],
"limit": 1
}' | jq '.meta | {total, totalIsExact}'The REST call names the three email statuses itself. We ran the sequence below with the free MCP count tool on 21 October 2026, which counts those same three statuses by default, so every figure is mailable people only. It is not everyone we hold for the title.
| Filters | Mailable people |
|---|---|
| title Software Engineer, country US | 544,159 |
| plus seniority senior | 224,314 |
| plus keywords python | 84,698 |
| plus employeeRange 51 to 1,000 | 14,543 |
| plus city Austin | 413 |
| plus industry Software Development | 148 |
A pool of 148 is small enough to read. A pool of 14,543 is a reason to add city, industry or excludeTitle before you pull anything. Exclusions only shrink the pool, so a count with them is never larger than the one in the table.
Step 4: Spot-check ten rows
Look at real rows before you buy a hundred. Ten records is a cheap test of whether the title and keyword filters match your idea of the role. Title keywords are loose, so expect a few stray roles in the sample.
Use leadocean_search_leads with the same filters and limit 10. Show a table of name,
headline, company and email_status. Flag any headline that is not a software engineer
so I can add it to excludeTitle. Save the rows to shortlists/sample.json.Search returns thin records: identity, headline, current role and company, location and email flags. It never returns addresses. Over REST, drop count and set limit to 10. Create the folder first.
mkdir -p shortlists
curl -s "https://api.leadocean.io/v1/people/search" \
-H "x-api-key: $LEADOCEAN_API_KEY" -H "content-type: application/json" \
-d '{
"title": ["Software Engineer"],
"country": ["US"],
"seniority": ["senior"],
"city": ["Austin"],
"industry": ["Software Development"],
"keywords": ["python"],
"employeeRange": ["51-200", "201-500", "501-1000"],
"emailStatus": ["verified", "catch_all_valid", "catch_all"],
"limit": 10
}' | tee shortlists/sample.json \
| jq '.data[] | {name: .profile_data.profile_full_name, headline: .profile_data.profile_headline, person_id: .meta.source_ids.person_id, email_status: .contact_data.email_status}'Each row carries meta.source_ids.person_id. Keep it, because Step 5 uses it. About a fifth of the people search can find have no LinkedIn URL, so the id is the only key that always works.
Step 5: Enrich the keepers
Enrich only the people you would contact. One enrich call is one record, and reveal_email returns every email and phone we hold at no extra charge. The example takes three.
For the first three rows in shortlists/sample.json, call leadocean_get_lead with the row's
person_id and reveal_email true. Report email, email_status and
contact_still_at_company_status. Save the results to shortlists/enriched.json.curl -s 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 4>", "reveal_email": true}' \
| jq '.data.contact_data | {email_status, email_type, contact_still_at_company_status}'Read contact_still_at_company_status before anyone writes to a candidate. The API reference lists it under contact_data. Read email_status too. There is no refund or credit-back for a bounced address.
Step 6: Export the full list to CSV
Use the export endpoint for the bulk pull, not a paging loop through the chat. Bulk CSV export is not on the free plan: it is included on Solo ($97 a month) and Agency ($497). On Free, page the Step 4 search at limit 100 with the cursor instead. An export takes the same filters, a limit and a column preset. The full preset reveals emails and phones and needs the enrich scope. It takes up to 50,000 rows a request and 500,000 a day.
curl -s https://api.leadocean.io/v1/exports \
-H "x-api-key: $LEADOCEAN_API_KEY" -H "content-type: application/json" \
-d '{
"name": "Senior Python engineers, Austin, 51 to 1,000 staff",
"filters": {
"title": ["Software Engineer"],
"country": ["US"],
"seniority": ["senior"],
"city": ["Austin"],
"industry": ["Software Development"],
"keywords": ["python"],
"employeeRange": ["51-200", "201-500", "501-1000"],
"emailStatus": ["verified", "catch_all_valid", "catch_all"]
},
"limit": 148,
"preset": "full"
}' | tee export.json | jq '.data | {id, status, requested, estimatedTotal, pollAfter}'Poll until the status is done, then download the file.
ID=$(jq -r '.data.id' export.json)
curl -s https://api.leadocean.io/v1/exports/$ID -H "x-api-key: $LEADOCEAN_API_KEY" | jq '.data.status'
curl -s https://api.leadocean.io/v1/exports/$ID/download -H "x-api-key: $LEADOCEAN_API_KEY" -o candidates.csvThen hand the file back to Claude Code with the rules from Step 1.
Read candidates.csv and ats-export.csv. Remove anyone whose profile URL is in the ATS file.
Write shortlists/final.csv with name, headline, company, email_1_address and email_1_status.
Do not add any column the CSV does not already have.You can also start the same export from the Exports page in the LeadOcean app. It has a filter builder, a column picker, the record price before you start and a Download button.
What you get
You get a mailable shortlist as a CSV, plus the free counts that sized it. This is the raw output of the Step 3 count for the last row of the table, run on 21 October 2026 with leadocean_count_leads. It cost 0 records. Trimmed:
{
"total": 148,
"totalIsExact": true,
"totalAll": 364,
"distinctCompanies": 106,
"facets": {
"seniority": [{ "value": "senior", "count": 148 }],
"company_size": [
{ "value": "51-200", "count": 58 },
{ "value": "201-500", "count": 55 },
{ "value": "501-1000", "count": 35 }
],
"industry": [{ "value": "Software Development", "count": 148 }]
}
}An enrich call returns one person. This is the response shape from the API reference, with placeholder values.
{
"success": true,
"data": {
"profile_data": {
"profile_full_name": "Jane Doe",
"profile_headline": "<headline>",
"profile_url": "https://www.linkedin.com/in/janedoe"
},
"contact_data": {
"contact_emails": [{ "email": "jane.doe@acme.com", "type": "work", "status": "verified" }],
"contact_phones": [{ "phone": "+14155550133", "type": "mobile" }],
"email_status": "verified",
"contact_still_at_company_status": "<status>"
},
"resume_data": {
"experiences": [
{ "job_title": "Software Engineer", "company_name": "Acme", "start_date": "2019-03", "end_date": "2022-06", "company_id": null }
],
"educations": [{ "educational_establishment": "<school>", "diploma": "<degree>" }],
"r_skills": ["python"]
}
},
"meta": { "source_ids": { "person_id": "<person_id>" }, "schema": "leadocean.person.v1" }
}Since 17 October 2026, resume_data carries career history (LeadOcean API reference, October 2026). It holds past roles with title, company name and dates, plus education, certifications and skills, each when we hold it. Past employers have no company_id yet, so name the company rather than looking it up. The current role sits in contact_data.contact_current_experiences.
This recipe does not cover job-change alerts. Crustdata's guide adds a monitoring step with its Watcher API (Crustdata blog, September 2026). If you need alerts when a candidate moves, pair this recipe with a tool that has them. This one covers finding and reaching people.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing, wrong or revoked key | Re-copy the key from app.leadocean.io and re-export $LEADOCEAN_API_KEY. |
| 403 | The key lacks a scope. A full export needs enrich | Use a key with both scopes, or set "preset": "search", which has no contact columns. |
| 429 | Above the plan's rate limit (5 requests a second on Free, 50 on Solo, 200 on Agency), or an export ceiling | Wait the Retry-After seconds. Send pages of 100 rather than more calls. Export 429s return a reset time. |
| 402 | Free plan: the 1,000 records are spent | Check GET /v1/account, lower limit, or move to Solo or Agency on pricing. Free records do not reset. |
| Empty results or 400 | A filter value outside the catalogue, such as country: "USA" | Resolve it with leadocean_list_enum_values or GET /v1/enums/country and pass the exact string (US). |
400 export_limit_rows | limit is above 50,000 rows for one export | Split the pool by country or headcount band and run several exports. |
FAQ
How many records does a sourcing run cost?
One record per person returned or exported. The example spends about 161: 10 for the spot-check, 3 for enrichment and 148 for the full list. Sizing is free, so you know the cost before you start. Paid plans start at Solo, $97 a month, with a fair-use pace instead of credits.
Does it return work history, education or skills?
Yes, since 17 October 2026. Enrichment fills resume_data with past roles, education, certifications and skills when we hold them. To filter by skill at search time, use keywords, which matches the person's own profile text.
Can I find a candidate's personal email?
Yes, with GET /v2/people/email/personal. It takes linkedin_url or person_id and costs one record per call. The API reference says our verifier has never tested a personal address, so treat each one as untested.
Does Claude Code see my API key?
Not through the MCP route. The server uses OAuth sign-in and you paste no key into the chat. The REST steps read $LEADOCEAN_API_KEY from your shell.
What next?
Connect other clients on the Claude Code MCP page. Apply the same pattern to accounts with lead enrichment in Claude or competitive intelligence. Turn a GitHub handle into a record with candidate enrichment from GitHub. More recipes live in the blog hub.
Source your first candidate pool in Claude Code, free
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →