A recruiting workflow in Claude is five steps: brief, count, pull, enrich, draft. Claude Code runs the steps, the LeadOcean API supplies the records, and you approve every call that spends one.
TL;DR
- The workflow has five steps: write the role brief, size the pool, pull candidates, read career history for the best few, draft outreach.
- It takes about 25 minutes. The example spends about 35 of your 1,000 free records: 25 for the pull, 10 for enrichment.
- Sizing, filter lookups and enum lookups cost nothing. Only people you receive count as records.
Prerequisites
- A LeadOcean account and API key from app.leadocean.io. The free plan has no card and 1,000 records, one-off. Its keys get both the search and enrich scopes.
- The key in your shell as
$LEADOCEAN_API_KEY. - Claude Code installed, plus
curlandjq.
export LEADOCEAN_API_KEY="paste-your-key-here"
mkdir recruiting && cd recruitingStep 1: Write the role brief and connect the data
Put the role in a CLAUDE.md file in the project folder. Claude Code's memory docs describe CLAUDE.md as persistent instructions it reads at the start of a session. Use exact filter values, not prose.
## Search: Staff Backend Engineer, Germany
### Role filter
- Titles: Staff Software Engineer, Staff Backend Engineer
- Job function: Engineering (exact enum value)
- Country: DE (ISO code, not "Germany")
- Contactable only: emailStatus verified, catch_all_valid, catch_all
### Rules
- Use the leadocean MCP server. Call leadocean_count_leads before any tool that spends records.
- Never call search, get_lead or export without my go-ahead. Say how many records it will spend first.
- Judge fit from the record only. If a field is empty, say so. Never guess a skill or a salary.Now add the hosted LeadOcean MCP server. Claude Code's docs say to run /mcp inside a session to sign in to a remote server with OAuth. No key goes into the chat.
claude mcp add --transport http leadocean https://api.leadocean.io/mcp
claude/mcpPick leadocean, approve the sign-in, then confirm the server lists 13 tools. Seven of them never spend records.
Step 2: Size the talent pool for free
Count before you pull. A count spends no records, however often you run it, and it tells you whether the search is worth running.
Run leadocean_count_leads with the role filter in CLAUDE.md. Report the total, how many
employers it spans and which email statuses it counted. Do not call any tool that
spends records.The same count over REST is a people 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": ["Staff Software Engineer", "Staff Backend Engineer"],
"jobFunction": ["Engineering"],
"country": ["DE"],
"emailStatus": ["verified", "catch_all_valid", "catch_all"],
"limit": 1
}' | jq '.meta | {total, totalIsExact, distinctCompanies, countOnly}'The total is capped at 100,000. If the pool is too big, add employeeRange or keywords. If it is under 100, widen the title list.
These filters do most of the work for a candidate search:
titlematches job title keywords. Wrap a value in brackets, such as["[Staff Engineer]"], for an exact match.keywordssearches the person's own headline, summary, skills and job descriptions. Use it for a stack, such as["kafka"].seniorityis a finer band in lowercase (senior,manager,director).employeeRangefilters by employer size, for example["51-200"].excludeCompanyNamekeeps you from pulling people at your own client or employer.
LeadOcean has 50 people filters. An enum value that does not match can return an empty result or a 400, so resolve each one with leadocean_list_enum_values before you trust a zero.
Step 3: Pull 25 candidates
Pull a small batch first. Each person returned costs one record, so 25 rows is a cheap test of the title match. The body is flat JSON, filters are arrays, and limit runs from 1 to 100.
curl -s https://api.leadocean.io/v1/people/search \
-H "x-api-key: $LEADOCEAN_API_KEY" -H "content-type: application/json" \
-d '{
"title": ["Staff Software Engineer", "Staff Backend Engineer"],
"excludeTitle": ["student", "intern", "working student"],
"jobFunction": ["Engineering"],
"country": ["DE"],
"keywords": ["distributed systems"],
"emailStatus": ["verified", "catch_all_valid", "catch_all"],
"limit": 25
}' > candidates.json
jq '{rows: (.data | length), nextCursor: .meta.nextCursor}' candidates.jsonSearch rows are thin: profile, current role and email flags. Each row carries a person_id at meta.source_ids.person_id. Step 4 uses it.
Sourcing for an agency instead? Search the hiring side. GET /v1/companies/{domain}/people lists the people we hold at one employer, one record per person returned.
curl -s "https://api.leadocean.io/v1/companies/acme.com/people?limit=10" \
-H "x-api-key: $LEADOCEAN_API_KEY" | jq '.meta | {count, nextCursor}'Step 4: Read career history for the best 10
Do not enrich everyone. Let Claude pick the rows worth a record, then enrich only those. Enrich takes exactly one of linkedin_url, email, phone or person_id, and reveal_email returns the address.
Read candidates.json. Pick the 10 people whose current role and employer best fit the
brief in CLAUDE.md. Write their person_id values to ids.txt, one per line, and show me
a table of name, title and company with one line on why each made the cut.while read -r ID; do
curl -s https://api.leadocean.io/v1/people/enrich \
-H "x-api-key: $LEADOCEAN_API_KEY" -H "content-type: application/json" \
-d "{\"person_id\": \"$ID\", \"reveal_email\": true}" | jq -c .
done < ids.txt > enriched.jsonl
wc -l enriched.jsonlSince October 2026 a person enrich returns career history in resume_data: past roles sit in experiences[] (title, company name, dates, job level). You also get educations[], certifications[] and awards[], each when we hold them. Past employers carry no company_id yet, so match them by name. The current role is in contact_data.contact_current_experiences[].
A person we hold nothing for returns a 404 and costs nothing. Revealing the email costs no extra record. A phone through GET /v2/people/phone bills one record per call, even when it finds nothing.
Step 5: Score fit and draft outreach
Keep the model out of the data work until now. Search and enrich are plain API calls that give the same answer every run. The model only scores and writes, and you can read what it wrote.
Read enriched.jsonl. Write shortlist.csv with these columns: name, current_title,
current_company, years_in_role, past_roles, email, email_status, fit_score (1-5),
reason, message. Build years_in_role and past_roles from resume_data and the current
role only. The message is two sentences under 50 words and names one concrete thing
from the career history. If the history is thin, leave message empty and write
"thin data" in reason. Drop anyone whose email_status is not verified,
catch_all_valid or catch_all. Do not send anything. Show me the first three rows.Read the CSV by hand before anything is sent. LeadOcean gives you records and a deliverability label for each address. It does not send mail, and bounced emails are not refunded, so read email_status first.
When the 10 rows look right, scale up with the same filters. POST /v1/exports takes up to 50,000 rows a request and 500,000 a day, and bulk CSV export is not on Free. The lead list recipe covers it, and the candidate sourcing guide goes deeper on the loop.
What you get
Step 2 gives a real number. On 21 October 2026 the free MCP count for this role returned this.
{
"total": 771,
"totalIsExact": true,
"totalAll": 1377,
"distinctCompanies": 302,
"credits": 0,
"defaults": "Counted only addresses worth sending to (emailStatus: verified or catch_all_valid or catch_all)",
"unsupportedFilters": []
}That is 771 contactable people across 302 employers, out of 1,377 matching the titles. The count used jobFunction Engineering and country DE. A first attempt with "Germany" was rejected: country takes ISO codes.
Here is how the records add up for one run of the workflow.
| Step | Call | Records |
|---|---|---|
| 2 | Count over MCP or count=true | 0 |
| 3 | People search, limit 25 | 25 |
| 4 | Enrich 10 people with reveal_email | 10 or fewer |
| 5 | Score and draft in Claude | 0 |
Rows that return a 404 on enrich cost nothing, so 35 is the ceiling for this run.
This is the response shape from the API reference and the enrichment docs for one enriched person, with placeholder values.
{
"success": true,
"data": {
"profile_data": { "...": "name, headline, current role, company, location" },
"contact_data": {
"contact_emails": [
{ "email": "jane.doe@acme.com", "type": "work", "status": "verified" }
],
"has_email": true,
"email_status": "verified"
},
"resume_data": {
"experiences": [
{ "job_title": "Backend Engineer", "company_name": "Acme" /* plus job_seniority, start and end dates */ }
],
"educations": [],
"certifications": []
},
"meta": {}
},
"meta": { "source": "leadocean", "count": 1, "credits": 1 }
}The docs name the experience fields (company_name, job_title, job_seniority, start and end dates) but not the date keys. Print one record with jq before you parse dates.
The CSV from Step 5 has one row per person. The row below uses placeholder values.
name,current_title,current_company,years_in_role,past_roles,email,email_status,fit_score,reason,message
Jane Doe,Staff Backend Engineer,Acme,3,"Backend Engineer at Beta Labs",jane.doe@acme.com,verified,4,"Distributed systems background","..."Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing, wrong or revoked key | Re-copy the key from app.leadocean.io and re-export $LEADOCEAN_API_KEY. |
| 429 | Above the plan's rate limit: 5 requests a second on Free, 200 on Agency | Wait the Retry-After seconds and send full pages of 100 instead of more calls. |
| 402 | Free plan: the 1,000 records are spent | Check GET /v1/account, then move to Solo at $97 a month. Free records do not reset. |
| 404 on enrich | We hold nothing for that person_id | Nothing to fix. It costs no record. Skip the row. |
| Empty results or a 400 | A filter value outside the list, such as country: "Germany" or jobFunction: "Engineer" | Resolve it with leadocean_list_enum_values or GET /v1/enums/<name> and pass the exact string. |
| 400 or 429 on an export | 400: limit is above 50,000 rows for one export. 429: the day's 500,000 rows are spent | Split the pool by country or employer size, or wait for resetsAt. |
FAQ
How many records does a recruiting run cost?
One record per person returned by a search or an enrich. Sizing is free. The example spends about 35: 25 for the pull and 10 for enrichment. Solo is $97 a month if you outgrow the free 1,000. See pricing.
Can Claude find candidates with no recruiter seat?
Yes, for people who have a public professional profile in the data. You filter by title, function, country and keywords, then read the record. There is no recruiter workspace or sequencer in LeadOcean, so outreach goes through your own tools.
Does Claude see my API key?
Not on 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.
Can I run the same workflow for sales or customer success?
Yes. Swap the brief in CLAUDE.md and keep the steps. See the B2B prospecting workflow, the customer success workflows and the guide to using Claude for recruiting.
More recipes live in the blog hub.
Build your first recruiting workflow in Claude today
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →