Pre-Black Friday: 20% off, for as long as you stayEARLYBFon Solo and AgencyEnds Nov 28Start free

How-to guide

How to Search for Specific Job Titles Within Target Accounts (API Recipe)

Send a list of domains and a list of titles. Get back the people who hold them. Size it free first.

Get your free API key →Free to start. No credit card. 1,000 records to spend whenever you like.

Send the domains of your target accounts and the titles you want. LeadOcean returns the people who hold those titles at those companies. You can size the list for free before you spend a record.

TL;DR

  • One search request takes domain and title as arrays and returns matching people, up to 100 a page.
  • It takes about 15 minutes. A first run spends about 26 records: 25 for one search page, 1 for one enrichment. Counts cost nothing.
  • Title matching is keyword matching, so pair title with jobLevel or seniority to keep the list clean.

Prerequisites

  • A LeadOcean account. The Free plan is enough: 1,000 records for life, no card, 5 requests a second.
  • An API key from app.leadocean.io, exported as $LEADOCEAN_API_KEY.
  • curl and jq. Any HTTP client works.
  • A list of target account domains, such as acme.com. This page uses placeholders.

Step 1: Pick titles and a level, not only keywords

Titles are messy. "VP Sales", "Vice President, Sales" and "SVP of Revenue" are the same buyer. A bare keyword filter will miss some and catch noise.

Use three filters together:

  • title: an array of keywords. Each value is a phrase. Wrap a value in brackets for an exact match.
  • jobLevel: C-Team, VP, Director, Manager, Staff or Other. Exact strings, case matters.
  • jobFunction: the department, for example Sales & Business Development.
json
{
  "title": ["VP Sales", "Head of Sales", "Chief Revenue Officer"],
  "jobLevel": ["VP", "C-Team"],
  "jobFunction": ["Sales & Business Development"]
}

The level and function filters do the heavy lifting. The title keywords then narrow within them. Use excludeTitle to drop noise such as ["assistant", "intern"].

Two patterns work well. For a broad net, use jobLevel and jobFunction with no title. For one exact role, use a bracketed title such as ["[Chief Revenue Officer]"]. Run both as counts and compare the totals. The gap tells you how many people hold the role under a different name.

Step 2: Resolve enum values before you send them

Enum values are exact strings. A wrong one returns an empty result, not an error. Look them up first. It is free.

bash
curl -s "https://api.leadocean.io/v1/enums/job_level" \
  -H "x-api-key: $LEADOCEAN_API_KEY" | jq

GET /v1/enums lists every enum name, and GET /v1/enums/{name} returns its values. In an MCP client, call leadocean_list_enum_values with name: "job_level" or name: "job_function" instead. Both calls spend no records.

Step 3: Add your target accounts as domains

Domains are the cleanest way to say "inside these accounts". Names collide. Domains do not.

json
{
  "domain": ["acme.com", "globex.example", "initech.example"],
  "title": ["VP Sales", "Head of Sales", "Chief Revenue Officer"],
  "jobLevel": ["VP", "C-Team"]
}

For a single account, there is a shortcut: GET /v1/companies/{domain}/people lists the people at one company. It stops at 10,000 rows.

Have names but no domains? Resolve the companies first with POST /v1/companies/search, then pass the domains it returns. Check the list size with GET /v1/companies/count (free) before you build on it.

Step 4: Size the result for free

Never page blind. Send the same body with count=true as a query parameter and limit set to 1. The data comes back empty, meta.total holds the count, and nothing is spent.

bash
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 '{
    "domain": ["acme.com", "globex.example", "initech.example"],
    "title": ["VP Sales", "Head of Sales", "Chief Revenue Officer"],
    "jobLevel": ["VP", "C-Team"],
    "limit": 1
  }' | jq '.meta'

Put count=true in the URL. The JSON body rejects unknown fields. meta.total caps at 100,000.

If the total is zero, loosen one filter at a time. Drop jobLevel first, then widen title.

Step 5: Run the search and page through it

Now spend records. Set limit between 1 and 100 (default 25). Each person returned is one record.

bash
curl -s -X POST "https://api.leadocean.io/v1/people/search" \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "domain": ["acme.com", "globex.example", "initech.example"],
    "title": ["VP Sales", "Head of Sales", "Chief Revenue Officer"],
    "jobLevel": ["VP", "C-Team"],
    "limit": 25
  }' > page1.json

jq '.meta' page1.json

For the next page, send the cursor value from meta.nextCursor in the body. One search pages to 10,000 rows. If meta.depthCapped is true, there is no nextCursor: narrow the filters instead of walking the index.

Step 6: Enrich only the people you keep

Search gives you the person. Enrichment gives you the contact data. Pass exactly one of linkedin_url, email, phone or person_id.

bash
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_STEP_5", "reveal_email": true}'

There is no name plus domain lookup. Search by domain first, then enrich by person_id. An enrichment that finds nothing returns 404 and costs nothing. The current role is in contact_data.contact_current_experiences[]. Past roles are in resume_data.experiences[]. Each search row carries its id at meta.source_ids.person_id.

Step 7: Export a large list

Past a few hundred rows, export instead of paging. POST /v1/exports takes the same filters, up to 50,000 rows a request and 500,000 a day. Bulk CSV export is not on Free. It is included on Solo ($97 a month). Agency ($497 a month) gets 50,000 rows a file.

You can also build the same export in the app at app.leadocean.io: filter builder, column picker, a price preview, then CSV download.

Step 8: Check coverage per account

A search returns people, but you often want to know which target accounts came back empty. Group the first page by company and diff it against your list.

bash
jq -r '.data[] | .contact_data.contact_current_experiences[0].company_domain' page1.json \
  | sort -u > found.txt
sort -u targets.txt | comm -23 - found.txt > missing.txt

The current employer's domain sits on the first entry of contact_current_experiences. Run the check on a full export, not one page, or every account past page one looks missing.

For the missing accounts, loosen the filters on that account alone. Drop jobLevel, widen title to the department head, or switch to GET /v1/companies/{domain}/people and read the titles yourself. Accounts with no match at all are worth knowing about: they are the ones where the buyer is not in the data, and a different channel is needed.

Do it in an AI client instead

The hosted MCP server at https://api.leadocean.io/mcp signs in with OAuth, no key pasted. Ask for what you want:

text
Count VPs and C-level people in Sales & Business Development at
acme.com, globex.example and initech.example. Use leadocean_count_leads
first. Do not search until I confirm.

The client calls leadocean_count_leads, which is free, and reports the total before anything is spent.

What you get

A count run on 2026-10-21 with the MCP tool leadocean_count_leads, filters title: ["VP Sales", "Head of Sales"], country: ["US"], employeeRange: ["51-200"], default mailable email filter:

json
{
  "total": 2639,
  "totalIsExact": true,
  "totalAll": 8926,
  "distinctCompanies": 2253,
  "credits": 0
}

Read it like this: 2,639 mailable people (verified, catch-all valid or catch-all email) across 2,253 companies. Without the email condition, 8,926 people match. The count spent nothing.

Response shape from the API reference, with placeholder values:

json
{
  "success": true,
  "data": [
    {
      "profile_data": { "profile_full_name": "Jane Doe", "profile_headline": "VP Sales at Acme" },
      "contact_data": {
        "has_email": true,
        "email_status": "verified",
        "contact_current_experiences": [
          { "job_title": "VP Sales", "company_name": "Acme", "company_domain": "acme.com" }
        ]
      },
      "meta": { "source_ids": { "person_id": "1234567" } }
    }
  ],
  "meta": { "count": 25, "limit": 25, "credits": 25, "nextCursor": "CURSOR_STRING" }
}

Troubleshooting

ErrorCauseFix
401Missing or wrong x-api-key headerRe-copy the key from app.leadocean.io and send it as x-api-key.
429Requests above the plan's rate limit: 5 a second on Free, 200 on AgencyWait for the Retry-After header, then retry. Batch domains into one request.
402The 1,000 free records are spentUpgrade to Solo ($97 a month) or Agency ($497 a month). Counts and enrichments that find nothing stay free.
Empty resultsA wrong enum value, such as vp instead of VP for jobLevelCheck exact strings with /v1/enums or leadocean_list_enum_values. Loosen one filter at a time.
404 on enrichNobody matched the inputFree. Try linkedin_url or email instead of person_id.
Export refusedOver 50,000 rows a request or 500,000 a day, or Free planSplit the filters into slices. Bulk CSV export needs Solo or above.

FAQ

Can I search titles inside a list of company names, not domains?

Yes, with companyName. Each value is a phrase, and brackets make it exact. Domains are safer, since names collide.

How do I avoid fake matches like "Assistant to the VP Sales"?

Use excludeTitle with ["assistant", "intern"] and set jobLevel. The level filter reads the seniority of the current role, not the words in the title.

How many records does a list of 500 accounts cost?

It depends on how many people match, and each person returned is one record. Count first. The count is free and shows meta.total and the number of distinct companies.

Does search return emails?

Search finds the people. Enrich with reveal_email to get the address, or export with email columns. Check contact_data.email_status before you send.

Can I search several buying roles in one call?

Not cleanly. Run one search per role, for example one for sales leaders and one for RevOps. Each body stays small, each count is free, and you can compare totals before you spend.

Related: target account list, find target accounts with a prompt, find people by job title and pricing.

Search any job title inside your target accounts, free

Free to start. No credit card. 1,000 records to spend whenever you like.

Get your free API key →