TL;DR
- LeadOcean gives you the same data three ways: REST with an
x-api-keyheader, a shell script aroundcurlandjq, and a hosted MCP server for AI clients. - It takes about 20 minutes and about 8 of your 1,000 free records. Sizing, filter lookups and enum lookups cost nothing.
- LeadOcean has no CLI of its own. The terminal route is
curlandjq. If you want a packaged command, see how lm-tui works.
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 scopes,
searchandenrich. - The key in your shell as
$LEADOCEAN_API_KEY. curlandjq. Claude Code only for Step 6.
export LEADOCEAN_API_KEY="paste-your-key-here"New to the terms? Read what data enrichment is and what a B2B data API is first. This guide is for people who already know and want the calls.
Developer tools in this space tend to ship three interfaces on one account balance. LeadMagic's developer page lists REST, the LeadMagic CLI (the lm command, known as lm-tui) and a hosted MCP server, all on one credit balance (LeadMagic, October 2026). LeadOcean covers two of the three natively and the third with a ten-line script. This page shows all three side by side, with the calls and the records each one spends.
Step 1: Read your account before you spend anything
GET /v1/account is free. It returns your plan, records used and remaining (or your fair-use pace on a paid plan), your rate and your export ceilings. Every call after this one is measured against it.
curl -s https://api.leadocean.io/v1/account \
-H "x-api-key: $LEADOCEAN_API_KEY" | jqRun it first in any new environment. A 401 here means the key is wrong, and you have spent nothing finding out.
The rate limit is per account and set by plan: 5 requests a second on Free, 50 on Solo, 200 on Agency. A 429 or a paced answer carries X-RateLimit-Limit, and X-Quota-State appears while a paid plan is paced.
Step 2: Size the audience for free
Write the audience as filters. Filters are arrays of strings, and the body is flat JSON. This example is VP Engineering and Head of Engineering at US software companies with 51 to 200 staff.
Send count=true as a query parameter with limit at 1. You get 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": ["VP Engineering", "Head of Engineering"],
"country": ["US"],
"industry": ["Software Development"],
"employeeRange": ["51-200"],
"emailStatus": ["verified", "catch_all_valid", "catch_all"],
"limit": 1
}' | jq '.meta | {total, totalIsExact, countOnly}'Enum values are exact strings. "Software" is not an industry. Resolve it first with the free enum endpoint.
curl -s "https://api.leadocean.io/v1/enums/industry?q=software" \
-H "x-api-key: $LEADOCEAN_API_KEY" | jqmeta.total is capped at 100,000. totalIsExact tells you when the number is a floor.
Filters join with AND. Several values inside one filter join with OR. Add excludeTitle (for example ["assistant","intern"]) when the title match is too loose. If the count is too big or too small, change one filter and count again. Counting is free, however many times you do it.
Step 3: Pull a three-row sample
Look at real rows before you pull hundreds. A search returns thin records, and each person returned counts one record. 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": ["VP Engineering", "Head of Engineering"],
"country": ["US"],
"industry": ["Software Development"],
"employeeRange": ["51-200"],
"emailStatus": ["verified", "catch_all_valid", "catch_all"],
"limit": 3
}' | tee sample.json | jq '.meta | {count, nextCursor}'Each row carries an id at meta.source_ids.person_id. Keep it. Search publishes email flags, never addresses, and about a fifth of findable people have no LinkedIn URL. The id is how you enrich them anyway. Rows come back as profile_data, contact_data, resume_data and meta.
jq -r '.data[].meta.source_ids.person_id' sample.jsonTo page on, pass meta.nextCursor back as cursor, unchanged. Limit is 1 to 100 a page. One search walks at most 10,000 rows, so narrow the filters rather than paging harder.
Step 4: Enrich one person by person_id
Enrich takes exactly one of linkedin_url, email, phone or person_id. There is no name plus domain lookup. Search by domain first, then enrich by id.
reveal_email defaults to false. Set it to true to get the emails and phones we hold. It costs no extra record. A person we hold nothing for is a 404 and costs nothing.
PID=$(jq -r '.data[0].meta.source_ids.person_id' sample.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\": \"$PID\", \"reveal_email\": true}" \
| jq '.data.contact_data | {has_email, email_status, email_type, contact_emails, contact_phones}'The one record covers the person, the revealed emails and the phones. If you only need a number, GET /v2/people/phone is the narrow endpoint. The v2 contact endpoints cost one record per call, including a call that finds nothing.
For a company, POST /v1/companies/enrich takes the same style of request. To list the people at a domain, use GET /v1/companies/{domain}/people, which stops at 10,000 rows.
Step 5: Loop it from the shell
This is the job a terminal tool does: take a CSV, enrich every row, write a new file. With LeadOcean it is a short script. LeadMagic's developer page describes its own lm command for bulk CSV enrichment from the shell (LeadMagic, October 2026). LeadOcean authenticates with a key.
Put one email per line in emails.txt. Use your own list.
#!/usr/bin/env bash
: "enrich.sh: one record per hit, a 404 costs nothing"
echo "email,email_status,email_type" > out.csv
while read -r EMAIL; do
RES=$(curl -s -w "\n%{http_code}" https://api.leadocean.io/v1/people/enrich \
-H "x-api-key: $LEADOCEAN_API_KEY" -H "content-type: application/json" \
-d "{\"email\": \"$EMAIL\", \"reveal_email\": true}")
CODE=$(tail -n1 <<<"$RES")
[ "$CODE" = "404" ] && { echo "$EMAIL,not_found," >> out.csv; continue; }
sed '$d' <<<"$RES" | jq -r --arg e "$EMAIL" \
'[$e, .data.contact_data.email_status, .data.contact_data.email_type] | @csv' >> out.csv
done < emails.txtRun it with bash enrich.sh. Four addresses spend at most four records. The script is sequential. On Free, add sleep 0.2 inside the loop for a long file to stay under 5 requests a second.
For more than a few thousand rows, stop looping. Use POST /v1/exports, which takes the same filters and writes a file. Bulk CSV export is included on Solo and above, not on Free. One export holds up to 50,000 rows a request and 500,000 a day. The app has the same export on its Exports page, with a column picker and the record price before you start.
Step 6: Hand the same data to an AI client
The hosted MCP server is https://api.leadocean.io/mcp. It signs in with OAuth 2.1, so no key gets pasted into a config file. It exposes 13 tools, seven of which never spend records.
claude mcp add --transport http leadocean https://api.leadocean.io/mcp
claude mcp listInside a Claude Code session, run /mcp, pick leadocean and approve the sign-in. Claude Code's docs describe this flow for remote servers (Claude Code MCP docs, September 2026). Then ask in plain language and keep the spend off.
Use leadocean_list_enum_values to resolve the industry value for "software". Then use
leadocean_count_leads with title ["VP Engineering","Head of Engineering"], country ["US"],
industry ["Software Development"], employeeRange ["51-200"]. Say which email statuses it
counted. Do not call any tool that spends records.Clients that support dynamic registration can register themselves at https://api.leadocean.io/oauth/register (scope mcp).
What you get
On 1 October 2026 the free MCP count for the Step 2 audience returned this. It ran with no records spent.
{
"total": 213,
"totalIsExact": true,
"credits": 0,
"defaults": "Counted only addresses worth sending to (emailStatus: verified or catch_all_valid or catch_all)",
"unsupportedFilters": []
}That is 213 mailable people, counted by what we filtered: email status verified, catch_all_valid or catch_all. The number moves as the data is refreshed monthly.
This is the enrich response shape from the API reference, with placeholder values. Contact points appear only because reveal_email was true.
{
"success": true,
"data": {
"profile_data": { "profile_full_name": "Jane Doe" },
"contact_data": {
"has_email": true,
"has_phone": true,
"email_status": "verified",
"email_type": "work",
"contact_emails": [
{ "email": "jane.doe@acme.com", "type": "work", "status": "verified" }
],
"contact_phones": [
{ "phone": "+14155550133", "type": "mobile", "priority": 1 }
]
},
"resume_data": { "experiences": [ { "...": "past roles: title, company name, start and end dates" } ] },
"meta": { "schema": "leadocean.person.v1", "fetched_at": "<date>" }
}
}Read email_status before you send. There is no refund for bounced emails. Filter on email_status at search time, and re-check it after enrichment, because the search flag describes the best address and the revealed list can hold several.
The record cost of this whole walkthrough is small by design. Steps 1 and 2 and the MCP count spend nothing. Step 3 spends 3, Step 4 spends 1, and the four-row loop in Step 5 spends at most 4.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing, wrong or revoked key | Re-copy the key from app.leadocean.io and export $LEADOCEAN_API_KEY again. |
| 403 | The key lacks the scope the endpoint needs | Use a key with search and enrich. A permissions mistake returns 403, not an empty result. |
| 429 | Faster than your plan's rate per account (5 a second on Free, 200 on Agency) | Wait the Retry-After seconds, then retry. |
| 402 | Free plan: the 1,000 records are spent | Check GET /v1/account, or move to Solo ($97) or Agency ($497). Free records do not reset. |
| Empty results or 400 | A filter value outside the catalogue, such as industry: "Software" | Resolve it with GET /v1/enums/industry?q=... and pass the exact string. |
| 400 on an export | An export limit above the 50,000-row ceiling | Split the audience by country or headcount band and run several exports. |
FAQ
Does LeadOcean have a CLI like lm-tui?
No. LeadOcean has the REST API, a hosted MCP server and an app with an Exports page. From a terminal you use curl and jq, as in Step 5. The lm-tui write-up covers the packaged-command route.
What does this cost?
The free plan is 1,000 records, one-off, with no card. Solo is $97 a month and Agency $497 a month, with a fair-use pace and no per-record price. Each person returned counts one record on every plan. See pricing.
Which route should I pick?
Use REST inside an application. Use the shell loop for one-off files under a few thousand rows. Use exports above that. Use MCP when a person drives the work from an AI client.
Do I need a key for MCP?
No. The MCP server signs in with OAuth. The REST API and the shell script use the x-api-key header.
Where do I go next?
The blog hub lists the related guides. Start with what a B2B data API is if you are still choosing a provider.
Enrich your first records from code. Free key, one curl.
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →