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

How-to guide

How to Turn Intent Signals Into Contacts (With the LeadOcean API)

A signal tells you which accounts to work. Four API calls turn that list into named, mailable people. Real queries, real counts, no intent feed required.

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

Raw data becomes revenue when a signal list meets a contact list. The signal says which accounts to work this week. The contact data says who to write to.

This guide covers the second half. You bring the accounts, from your own site visits, your CRM or any intent vendor. LeadOcean returns the people. For what intent data is and where it breaks, read What Is Intent Data? first.

TL;DR

  • You end with a CSV of mailable decision makers at accounts that show a signal. It takes about 25 minutes.
  • It spends up to about 230 LeadOcean records. Sizing the audience is free, and the Free plan has 1,000 records, one-off, with no card.
  • LeadOcean has no intent feed. The signals here are your own domain list plus proxy filters: detected technology and funding year.

Prerequisites

  • A LeadOcean API key from app.leadocean.io/register. Export it as $LEADOCEAN_API_KEY.
  • curl and jq. Any HTTP client works the same way.
  • A signal list. Either 5 to 50 company domains from your own source, or the proxy filters in Step 2.
bash
export LEADOCEAN_API_KEY="paste-your-key-here"
curl -s https://api.leadocean.io/v1/account -H "x-api-key: $LEADOCEAN_API_KEY" | jq .

GET /v1/account is free. It shows your plan and how many records you have left.

Step 1: Write down the signal before you query

A signal is a reason to contact an account now. Pick one and write it as a rule you could test.

Three kinds work with this recipe:

  1. First-party. Domains that visited your pricing page twice this week, or that opened your last three emails. This is the strongest, and you own it.
  2. Third-party intent. A vendor's surging-account export. Take the domains and ignore the rest.
  3. Proxy. A fit trait that correlates with buying: a tool on the company website, or a recent funding round. Weaker, but free to filter on.

Save the first two as a plain list of domains. Use placeholders while you test.

Rank the list before you spend anything. Put accounts with two signals at the top, such as a pricing-page visit plus a funding round. Pull contacts for the top 20 first, and widen only if replies justify it.

Decide the buyer roles now too. A signal on an account does not tell you who owns the problem. Write down two or three titles or job functions, because Step 3 filters on them.

text
acme.com
example.org
globex.example

Step 2: Size the audience for free

Count before you spend. Send count=true as a query parameter with limit=1. It returns meta.total and no rows, and it costs no records.

bash
curl -s -X POST "https://api.leadocean.io/v1/people/search?count=true&limit=1" \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "jobFunction": ["Sales & Business Development"],
    "jobLevel": ["VP", "Director"],
    "country": ["US"],
    "employeeRange": ["51-200", "201-500"],
    "technologies": ["HubSpot"]
  }' | jq '.meta | {total, totalIsExact}'

The body is flat JSON and every filter is an array. Enum values are exact strings, so check them first with GET /v1/enums/job_level or the MCP tool leadocean_list_enum_values.

If the response carries meta.totalWithheld, a filter could not be applied to the count, and the total is null rather than a wrong number. Drop the company-side filter, here technologies, and count again.

Add a funding proxy at search time with one line: "minLastFundingYear": 2025 keeps companies funded in 2025 or later. It only matches companies that have a funding year on file. Do not trust a count that uses a funding filter. Size without it, then expect the funding filter to narrow the list further when you search.

Step 3: Pull people at your signal accounts

Now search by domain. The domain filter takes an array, so one call covers a short list. Each person returned costs 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", "example.org", "globex.example"],
    "jobFunction": ["Sales & Business Development"],
    "jobLevel": ["C-Team", "VP", "Director"],
    "emailStatus": ["verified", "catch_all_valid"],
    "limit": 25
  }' > people.json
jq '.meta | {count, credits, nextCursor}' people.json

Keep limit at 25 while you test. The maximum is 100. To page on, pass meta.nextCursor back unchanged as cursor.

For one account at a time, use GET /v1/companies/acme.com/people. It lists everyone we hold there, 25 rows by default, and stops at 10,000 rows.

Check meta.credits after the call. It shows the records that call used, so you can reconcile it against GET /v1/usage, which is free.

Search rows are thin. They say whether an email exists (has_email) and its email_status, not the address itself. That is deliberate: you pay for the address only when you ask for it.

Step 4: Enrich the people worth contacting

Take the person_id from meta.source_ids.person_id on a search row and enrich it. Send exactly one of linkedin_url, email, phone or person_id. Add reveal_email to get the address.

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": "1234567890", "reveal_email": true}' \
  | jq '.data.contact_data.contact_emails'

An enrich costs one record. A person we hold nothing for is a 404 and costs nothing. reveal_email adds no extra record.

Spot-check five people before you export. Read email_status on each. We do not refund bounced emails, so send only to verified unless you accept catch-all risk.

Step 5: Export the list as CSV

Export when the sample looks right. One call takes the same filters and writes up to 50,000 rows. The ceiling is 500,000 rows a day.

bash
curl -s -X POST "https://api.leadocean.io/v1/exports" \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "name": "signal-accounts-sales-leaders",
    "filters": {
      "domain": ["acme.com", "example.org", "globex.example"],
      "jobLevel": ["C-Team", "VP", "Director"],
      "emailStatus": ["verified", "catch_all_valid"]
    },
    "limit": 100,
    "preset": "full"
  }' | jq '.data | {id, status, requested, perRow, remaining}'

Start with 100 rows and open the file before you ask for 50,000. Check that the titles match the roles you wrote down in Step 1, and that email_1_status is what you filtered on.

The call reserves records up front and returns the unused ones when the export ends. Poll GET /v1/exports/{id} until status is done, then call GET /v1/exports/{id}/download. The full preset has 45 columns, and the emails come back as email_1_address, email_1_status and so on.

You can do the same on the Exports page at app.leadocean.io: filter builder, column picker, the record price before you start, and a Download button. The app has no sequencer or CRM, so load the CSV into the tool you already use.

What you get

The free sizing calls ran on 2026-10-01 through the leadocean_count_leads MCP tool. Each counts mailable people, meaning verified, catch_all_valid and catch_all addresses, which is the tool's default.

FiltersMailable peopleDate
jobFunction Sales & Business Development, jobLevel VP and Director, country US, employeeRange 51-200 and 201-50086,2732026-10-01
Same, plus technologies HubSpot23,0932026-10-01

Read the table as a funnel, with a count at each cut. A tool in the stack narrows 86,273 people to 23,093. A funding filter narrows it again at search time, but we do not quote a count for it. Those are fit signals, not behavior. Your own domain list narrows it further and is the only row here that shows real interest. Treat any count as a ceiling on the day you ran it, since the dataset refreshes monthly.

Each person row follows the response shape from the API reference. The values below are placeholders.

json
{
  "success": true,
  "data": [
    {
      "profile_data": { "...": "name, title, company, location" },
      "contact_data": {
        "has_email": true,
        "has_phone": false,
        "email_status": "verified",
        "email_type": "work"
      },
      "meta": { "source_ids": { "person_id": "1234567890" } }
    }
  ],
  "meta": { "count": 1, "credits": 1, "limit": 25, "nextCursor": "opaque-string" }
}

Troubleshooting

ErrorCauseFix
401Missing, wrong or revoked key.Send the key in the x-api-key header and re-copy it from the app.
429Faster than your plan's rate (5 a second on Free, 50 on Solo, 200 on Agency), or a paid plan's fair-use window reached, which paces calls to 1 a minute.Honour the Retry-After header. Do not retry in a tight loop.
402The Free plan's 1,000 records are spent. They are one-off and do not reset.Move to Solo ($97 a month) or Agency ($497 a month), or export fewer rows next time.
400 on a filterA wrong enum string such as VP Sales for jobLevel.Read details in the error body, then check the exact value with GET /v1/enums/job_level.
400 export_limit_rowsThe export asked for more than 50,000 rows.Split it into several exports with narrower filters.
429 export_limit_dailyYou passed 500,000 export rows in a day.Wait for resetsAt in the error body, or ask support for more headroom.

FAQ

Does LeadOcean sell intent data?

No. There is no surge score and no topic feed. It has fit filters such as technologies and minLastFundingYear, and it returns the people at accounts you already flagged. Compare vendors in best intent data providers.

Should I rank accounts by signal before I pull contacts?

Yes. Contacts cost records, so spend them on the top of the list. A short list of 20 accounts with a real signal beats 2,000 accounts with a weak proxy.

How fresh is the data?

The dataset is refreshed monthly. Each record also carries its own fetched_at date. Verify the status in email_status before a send.

How does this fit a wider data plan?

Signals pick the accounts, filters pick the roles, and enrichment picks the channel. The wider picture is in B2B data strategies for GTM.

What does the whole recipe cost?

The free plan covers it. For more, Solo is $97 a month for one person and Agency is $497 a month for teams. Both run at a fair-use pace with no per-record price. See pricing.

Turn your signal list into named contacts

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

Get your free API key →