TL;DR
- Automated prospecting on a REST API is a loop: size the list (free), search and page (spends records), enrich only the rows you keep.
- It takes about 20 minutes and about 120 LeadOcean records, so it fits inside the Free plan's 1,000 records.
- Every call goes to
https://api.leadocean.iowith anx-api-keyheader. Search bodies are flat JSON and filters are arrays.
Prerequisites
- A LeadOcean account. The Free plan is $0, needs no card and gives you 1,000 records for life.
- An API key from app.leadocean.io, exported as
$LEADOCEAN_API_KEY. curland Python 3 withrequests.- A target in mind. This recipe uses VPs of Sales in Canada.
Step 1: Size the list before you spend anything
Send count=true as a query parameter, not in the body, and limit 1 in the body. The body rejects unknown fields. A sizing call returns no rows and costs no records.
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 '{
"jobLevel": ["VP"],
"jobFunction": ["Sales & Business Development"],
"country": ["CA"],
"limit": 1
}'meta.total is capped at 100,000 and meta.countOnly is true. If the number is too big or too small, change the filters and call again. Sizing is free as often as you like.
Enum values are exact strings. Check them first with GET /v1/enums or the free leadocean_list_enum_values MCP tool.
Step 2: Search and page with a cursor
Now pull rows. limit runs from 1 to 100 and the default is 25. Each row you receive is one record.
curl -s -X POST "https://api.leadocean.io/v1/people/search" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jobLevel": ["VP"],
"jobFunction": ["Sales & Business Development"],
"country": ["CA"],
"limit": 100
}'The response carries meta.nextCursor. Send it back as cursor in the same body to get the next page. One search pages to 10,000 rows. The last page has meta.depthCapped true and no nextCursor. Narrow the filters instead of walking further.
Step 3: Enrich only the rows you keep
Search rows are cheap to filter and costly to over-pull, so score them first. Then enrich the keepers. 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_SEARCH", "reveal_email": true}'There is no name plus domain lookup. Search by domain first, then enrich by person_id. A person we hold nothing for is a 404 and costs nothing. Since 17 October 2026 the enrich response also returns career history in resume_data: past roles, education, certifications and awards.
Step 4: Put it in one script
This script pages one search, keeps rows that pass your own rule, enriches them and backs off on a 429.
import os, time, requests
BASE = "https://api.leadocean.io"
H = {"x-api-key": os.environ["LEADOCEAN_API_KEY"]}
def call(path, body):
while True:
r = requests.post(BASE + path, headers=H, json=body)
if r.status_code == 429:
time.sleep(int(r.headers.get("Retry-After", "1")))
continue
r.raise_for_status()
return r.json()
query = {
"jobLevel": ["VP"],
"jobFunction": ["Sales & Business Development"],
"country": ["CA"],
"limit": 100,
}
page = call("/v1/people/search", query)
for row in page["data"]:
if not keep(row): # your scoring rule
continue
person = call("/v1/people/enrich", {"person_id": row["meta"]["source_ids"]["person_id"], "reveal_email": True})
print(person)Swap print for a write to your CRM or queue. The API is the data layer: it has no sequencer, dialer or CRM of its own.
Step 5: Export when the list is big
Paging is for loops that run on a schedule or on a trigger. For a one-off list of thousands of rows, use POST /v1/exports instead. One request takes up to 50,000 rows and the daily ceiling is 500,000 rows.
The app at app.leadocean.io has an Exports page with a filter builder, a column picker, a price preview and a CSV download. It is the same export without code. Bulk CSV export is not on Free.
Design notes for a loop that runs unattended
Keep three habits and the loop stays cheap.
- Size first, always. A sizing call is free, so gate every run on
meta.totalbeing inside a range you expect. - Store the
person_idof every row you enrich. Re-enriching the same person spends another record. - Read
email_statusbefore you send. Bounced emails are not refunded, so do not send to risky or unknown addresses.
Pace the loop to your plan. Free allows 5 requests a second per account. Solo allows 50, Agency 200 and Platform 10,000. Above the limit you get a 429 with a Retry-After header, which the script above already honors.
Paid plans have a fair-use pace instead of credits. Reaching it slows the key until the window resets, and meta.notice says which window. Nothing stops and nothing needs a top-up.
What you get
The sizing call is free, so here is a real one. On 21 October 2026 the free leadocean_count_leads MCP tool returned this for the Step 1 filters.
{
"total": 5574,
"totalIsExact": true,
"totalAll": 18800,
"distinctCompanies": 3873,
"credits": 0
}The total field counts people with a verified, catch_all_valid or catch_all email, which is that tool's default.
The totalAll field is the same filters with no email condition. credits: 0 shows the call spent nothing.
This is the response shape from the API reference for an enriched person, with placeholder values.
{
"profile_data": { "profile_full_name": "Jane Doe" },
"contact_data": {
"email_status": "verified",
"contact_emails": [{ "email": "jane@acme.com" }],
"contact_phones": [{ "phone": "+1 415 555 0133" }],
"contact_current_experiences": [{ "title": "VP Sales", "company": "Acme" }]
},
"resume_data": { "experiences": [], "educations": [] },
"meta": { "source_ids": { "person_id": "<digits>" } }
}Fields appear only when we hold them. Past employers carry no company_id yet.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing or wrong x-api-key | Copy the key again from app.leadocean.io and check the env var is set |
| 429 | Above the plan's rate limit (5 requests a second on Free, 200 on Agency) | Wait for Retry-After, then retry; or move up a plan |
| 402 | Out of records on the Free plan | Check GET /v1/account; move to Solo at $97 a month |
| Empty results | A bad enum value, for example VP Sales as a jobLevel | Use exact strings: C-Team, VP, Director, Manager, Staff, Other |
| 404 on enrich | We hold nothing for that person | Nothing is charged; try another identifier |
Search stops with meta.depthCapped | One search pages to 10,000 rows | Add filters and split the list |
FAQ
How many records does a run like this spend?
One record is one person you receive. A 100-row search page is 100 records, and each enrichment that finds someone is one more. Sizing calls, counts and misses cost nothing.
Can I run this on the Free plan?
Yes. Free has every feature, the whole database and 5 requests a second, inside 1,000 records for life. Bulk CSV export is not on Free.
When should I export instead of paging?
Use POST /v1/exports for big lists. An export takes up to 50,000 rows a request and 500,000 a day. It needs Solo or above.
Can an AI agent run the same loop?
Yes. The hosted MCP server at https://api.leadocean.io/mcp has 13 tools, including free counts and filter lookups. It signs in with OAuth, so no key is pasted.
Related
- People data enrichment API for real-time B2B growth
- Social posts API for real-time B2B data
- Build internal sales tools with real-time signal APIs
- LeadOcean pricing
Run your first sizing call free
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →