Inbound lead generation is getting people to come to you. Most of the work after that is deciding who deserves a human reply, and a form with three fields cannot tell you.
This guide adds the missing fields. A signup posts to your server, you enrich the email, score the result and route it. Then you use the same filters to size more people like your best signups, for free.
TL;DR
- Enrich each form fill by email (
POST /v1/people/enrich), then the company by domain, then route on job level and headcount. - Setup takes about 45 minutes. It spends 2 records per signup and about 10 while you test. Free includes 1,000 records for life.
- A lookalike count with
leadocean_count_leadsspends nothing. Use it to see if your ICP is big enough before you buy ads for it.
Prerequisites
- A LeadOcean key from app.leadocean.io, stored as
$LEADOCEAN_API_KEY. Free works: both scopes, 1,000 records, 5 requests a second. - A form that can call a webhook (any form tool, or your own endpoint).
- Python 3.10+ and
requests, orcurl. - A place to send the result: a Slack webhook, your CRM API or a queue. This guide prints the decision and stops there.
Step 1: Enrich the email from the form
One call turns an email into a person. Send exactly one identifier. Here it is the email.
curl -s -X POST https://api.leadocean.io/v1/people/enrich \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email": "jane.doe@acme.com"}'A 404 means we hold nothing for that person. It costs nothing, so treat it as "unknown" and send the lead to your default queue.
Leave reveal_email off here. You already have the address, so you only need the profile.
Step 2: Enrich the company
The email domain is the company key. Enrich it to get headcount and industry.
curl -s -X POST https://api.leadocean.io/v1/companies/enrich \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"domain": "acme.com"}'Skip this call for free-mail domains (gmail.com, outlook.com). There is no company behind them, and you save a record.
Step 3: Score and route in one function
Put both calls behind your webhook. This handler enriches, scores on level and headcount, and picks a queue.
import os, requests
BASE = "https://api.leadocean.io"
H = {"x-api-key": os.environ["LEADOCEAN_API_KEY"]}
FREE_MAIL = {"gmail.com", "outlook.com", "yahoo.com", "icloud.com"}
def enrich(path, body):
r = requests.post(f"{BASE}{path}", headers=H, json=body, timeout=15)
if r.status_code == 404:
return None # nobody matched, no record spent
r.raise_for_status() # 401 bad key, 402 out of records, 429 too fast
return r.json()["data"]
def route(email):
domain = email.split("@")[1].lower()
person = enrich("/v1/people/enrich", {"email": email})
company = None if domain in FREE_MAIL else enrich(
"/v1/companies/enrich", {"domain": domain})
if not person:
return "default-queue"
role = (person.get("contact_data", {})
.get("contact_current_experiences") or [{}])[0]
level = (role.get("job_seniority") or {}).get("job_level")
senior = level in ("C-Team", "VP", "Director")
emp = ((company or {}).get("company_data") or {}).get("company_employees") or {}
size = emp.get("number_of_employees") or 0
if senior and size >= 50:
return "sales-now"
if senior or size >= 200:
return "sales-24h"
return "nurture"
print(route("jane.doe@acme.com"))The lookups follow the documented schemas: the role's job_seniority.job_level (MCP docs) and the company's company_data.company_employees.number_of_employees (company lookup). Print one real response before you ship and confirm the level strings match your rules.
Keep the rules this crude at first. After a month, compare the queue each lead landed in against what closed, then adjust the thresholds.
Step 4: Size the lookalikes before you spend on traffic
Your best inbound signups share a level, a function and a company size. Count how many people match that profile. The count is free.
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", "C-Team"],
"jobFunction": ["Sales & Business Development"],
"employeeRange": ["51-200", "201-500"],
"country": ["US"],
"limit": 1
}'The same query works as an MCP call from Claude or ChatGPT: ask for a count with those filters and the model calls leadocean_count_leads. For companies, GET /v1/companies/count and leadocean_count_companies do the same job. Enum values are exact strings, so look them up with leadocean_list_enum_values instead of guessing.
Step 5: Pull lookalikes to retarget or contact
If the count is large enough, search the same filters and page with cursor. Each person returned is one record, so set limit to what you need.
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"],
"employeeRange": ["51-200"], "country": ["US"], "limit": 25}'For a list above a few thousand rows, use POST /v1/exports instead (50,000 rows a request, 500,000 a day; not on Free). Upload the result as a custom audience to the ad platform you already run. Send mail only to rows whose email_status is verified.
Step 6: Log the decision so you can tune it
Write the queue, the level and the headcount next to the lead in your CRM. Without that, you cannot tell later whether the rules helped.
{
"email": "jane.doe@acme.com",
"queue": "sales-now",
"job_level": "VP",
"employee_count": 120,
"enriched_at": "2026-10-21"
}Three mistakes to avoid:
- Enriching bots. Filter obvious spam and disposable addresses before the call. Every call that finds a person costs a record.
- Routing on title text. Titles vary ("Head of Growth", "Growth Lead"). Route on job level and function, and keep title as a tiebreaker.
- Skipping the status. Check
email_statusbefore you reply by email. Bounces are not refunded.
After 30 days, group closed deals by queue. If sales-24h closes as often as sales-now, lower the headcount bar. If nurture produces deals, your rules are too strict.
Records also carry their own fetched_at date, and the dataset refreshes monthly. Re-enrich a lead only when it comes back after a long gap, such as a new form fill six months later. A person who changed jobs will show it.
What you get
A free count run on 2026-10-21 for VP and C-Team people in Sales & Business Development, at companies of 51-200 and 201-500 employees, in the US:
{
"total": 61084,
"totalIsExact": true,
"totalAll": 203746,
"distinctCompanies": 28019,
"credits": 0
}total counts only addresses worth sending to (verified, catch_all_valid or catch_all). totalAll is the same filters with no email condition. That is a mailable count, not a headcount of the market.
The enrich call returns this shape (response shape from the API reference, placeholder values):
{
"success": true,
"data": {
"profile_data": { "profile_full_name": "Jane Doe" },
"contact_data": {
"has_email": true,
"has_phone": false,
"email_status": "verified",
"email_type": "work",
"contact_current_experiences": [
{
"job_title": "VP Sales",
"job_seniority": { "job_level": "VP" },
"company_name": "Acme",
"company_domain": "acme.com",
"company_employees": { "number_of_employees": 120 }
}
]
},
"resume_data": {}
},
"meta": { "credits": 1 }
}Since 17 October 2026, resume_data also carries past roles, education and certifications when we hold them. Past employers have no company_id yet.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing or wrong x-api-key header | Send the key as x-api-key, read from $LEADOCEAN_API_KEY |
| 402 | The 1,000 Free records are spent | Add a paid plan, or enrich only leads that pass a cheap filter first |
| 429 | Above the plan rate limit (5 a second on Free, 200 on Agency) | Honour Retry-After; queue webhook calls instead of firing in parallel |
| 404 on enrich | We hold nothing for that email | Not an error. No record is spent; route to the default queue |
Empty search or total 0 | An enum value that is not an exact string, such as "VP of Sales" | Look it up with leadocean_list_enum_values; levels are C-Team, VP, Director, Manager, Staff, Other |
| Export refused | Over 50,000 rows a request or 500,000 a day, or you are on Free | Split the request, or move to a paid plan |
FAQ
What is inbound lead generation?
It is attracting people who contact you first: signups, demo requests, downloads. The channel is content, search, ads or referrals. Enrichment is what turns those names into a ranked queue.
Does enriching inbound leads break form conversion?
No. The call runs on your server after the form is submitted. The visitor sees nothing, and you keep the form short.
How many records will this use?
Two per signup with a work email: one person, one company. Free-mail signups use one. A 404 and a count cost nothing.
Can I do this without code?
Where does outbound fit?
Inbound brings the people who find you. Outbound reaches the ones who do not. Our B2B lead generation guide covers building that list, and Sales Navigator is one place to start. For scoring on intent signals, read predictive lead generation. Plans are on the pricing page.
Enrich every inbound lead before a human opens it
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →