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

How-to guide

How to Feed an AI SDR With Live Signals (LeadOcean API and MCP)

Define the signal as a filter, watch its free count, pull only the new people and give your agent the tools to act on them.

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

A live signal is a filter you can re-run, plus a trigger when its result changes. The free count is the trigger, and the search and enrich calls are the payload.

TL;DR

  • Time: about 25 minutes. Five steps, one shell script, one MCP command.
  • Records: about 30 on the first run (25 search rows, 5 enrichments). Counts, filter lookups and empty enrichments cost nothing.
  • Freshness: LeadOcean refreshes monthly and each record carries a fetched_at date. You poll for change. You do not get a news feed.

Crustdata's page for this topic describes webhooks that update lists when prospects match new criteria (source: crustdata.com/solutions/ai-sdr, October 2026). This recipe polls instead, which needs no webhook receiver and costs no records.

Prerequisites

  • A LeadOcean key in $LEADOCEAN_API_KEY. The Free plan (1,000 records for life, 5 requests a second) covers everything here.
  • curl and jq.
  • Optional: Claude Code, for the agent in Step 5.
  • A list of signals you care about. Below, the signal is "VP and C-level sales leaders at 51 to 500 person US companies that run HubSpot".

Two limits to accept up front. The data refreshes monthly, so a signal can be up to a month old. Intent and hiring events are not in the dataset, so a recipe that needs them has to bring its own feed.

Step 1: Turn the signal into a filter

Pick signals LeadOcean can filter on. This table shows which ones work as a trigger and which do not.

Signal you wantLeadOcean filterWorks as a trigger?
Uses a given tooltechnologies, technologyCategoriesYes, for the about 7.8M sites crawled
Company size bandemployeeRange, minEmployees, maxEmployeesYes
Recent funding roundfundingType, minLastFundingYearYes at search time. Not in the free count
New decision makerjobLevel, seniority, jobFunctionOnly by diffing the list
Hiring, intent, newsNoneNo. Bring your own feed

Check the exact enum strings before you write the body, because they are case sensitive. VP and vp are different values, and one of them returns nothing.

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

The search body is flat JSON and every filter is an array. Save this as signal.json:

json
{
  "jobLevel": ["VP", "C-Team"],
  "jobFunction": ["Sales & Business Development"],
  "employeeRange": ["51-200", "201-500"],
  "hqCountry": ["US"],
  "technologies": ["HubSpot"],
  "limit": 1
}

Add minLastFundingYear at search time for a funding signal. Do not trust a count that carries a funding filter: it can come back unsupported or as a fake round total.

Step 2: Size it with the free count

Send count=true as a query parameter, not in the body. The body rejects unknown fields, and limit: 1 keeps the call free.

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 @signal.json | jq '{total: .meta.total, companies: .meta.distinctCompanies}'

meta.total is capped at 100,000, so keep the signal narrow enough to stay under it. meta.distinctCompanies tells you how many employers the people span, which is the number an account-based team cares about. Run the count by hand a few times and adjust the filters until the segment is one your team could work in a quarter.

Step 3: Poll the count and fire on change

A cron job that stores yesterday's total turns the filter into a trigger. A changed total means people entered or left the segment.

bash
#!/usr/bin/env bash
set -euo pipefail
API="https://api.leadocean.io/v1/people/search"
H=(-H "x-api-key: $LEADOCEAN_API_KEY" -H "content-type: application/json")

NOW=$(curl -s -X POST "$API?count=true" "${H[@]}" -d @signal.json | jq '.meta.total')
PREV=$(cat last_total.txt 2>/dev/null || echo 0)
echo "$NOW" > last_total.txt

if [ "$NOW" -eq "$PREV" ]; then echo "no change ($NOW)"; exit 0; fi

jq '.limit = 25' signal.json | curl -s -X POST "$API" "${H[@]}" -d @- \
  | jq -r '.data[].meta.source_ids.person_id' | sort > page.txt
touch seen.txt
comm -13 <(sort seen.txt) page.txt > new_ids.txt
cat new_ids.txt >> seen.txt
wc -l < new_ids.txt

Each run that fires spends up to 25 records, including rows you saw before. Run it on change only, and tighten the filter if the page keeps returning the same people. Count totals can also drift without new people if data is rebuilt, so treat a change as "look", not "act".

The seen.txt file is the memory of the agent. Without it, every fired run hands the same top 25 people back, and the agent writes to them twice. Keep it in version control or a small table, and prune it when someone replies or opts out.

Step 4: Enrich the new people by person_id

Search gives you meta.source_ids.person_id. Send it to enrich, with reveal_email on, to get the address and career history. Enrich takes exactly one of linkedin_url, email, phone or person_id. An enrichment that finds nothing costs nothing.

bash
head -5 new_ids.txt | while read -r PID; do
  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\": \"$PID\", \"reveal_email\": true}"
done | jq -s '.' > enriched.json

Read data.contact_data.email_status and each contact_emails[].status before any send. Bounced emails are not refunded. The current role is in contact_current_experiences[]. Past roles, education and certifications are in resume_data, which is where an agent finds an opener (a past employer, a certification).

Write the opener from facts the record holds, such as the current title or a certification, and nothing else. An agent that invents a reason to reach out burns the list faster than a human does.

Step 5: Give the agent the tools

Scripts get you a trigger. An agent gets you judgement about what to do with it: which person to lead with, what to say, when to skip. LeadOcean does not send email or run sequences, so the agent hands its output to whatever does.

An AI SDR needs the same filters and calls without you writing glue. LeadOcean's hosted MCP server exposes 13 tools, and leadocean_count_leads is free. In Claude Code, per its MCP docs (September 2026):

bash
claude mcp add --transport http leadocean https://api.leadocean.io/mcp

Then authorise it with the OAuth sign-in, with no key pasted. A standing instruction keeps the agent inside the budget:

text
Every morning, run leadocean_count_leads with the saved signal filters.
If the total differs from yesterday's, call leadocean_search_leads with limit 25.
Skip any person_id already in seen.txt. Draft one opener per new person from
their current role and latest past employer. Never call leadocean_find_phone
or leadocean_export_leads without asking me.

Two guardrails matter more than the prompt. Cap the daily search at one page of 25 so a rule change cannot drain the Free plan. And keep a human review step before the first send, because the agent reads email_status but does not know your domain reputation.

What you get

The free count for the Step 1 signal, run through the LeadOcean MCP tool leadocean_count_leads on 2026-10-21. It carries a mailable default (verified, catch_all_valid or catch_all addresses):

text
filters: jobLevel [VP, C-Team], jobFunction [Sales & Business Development],
         employeeRange [51-200, 201-500], hqCountry [US], technologies [HubSpot]
total (mailable):   15,543
totalAll:           46,649   (no email filter)
distinctCompanies:  6,526
records spent:      0

The 15,543 is a mailable count, not a count of people who will reply. An enriched record looks like this (response shape from the API reference, placeholder values):

json
{
  "success": true,
  "data": {
    "profile_data": { "profile_full_name": "Jane Doe" },
    "contact_data": {
      "has_email": true,
      "email_status": "verified",
      "contact_emails": [{ "email": "jane@acme.com", "type": "work", "status": "verified" }],
      "contact_current_experiences": [{ "job_title": "VP of Sales", "company_name": "Acme", "company_domain": "acme.com" }]
    },
    "resume_data": { "experiences": [], "educations": [], "certifications": [] },
    "meta": { "source_ids": { "person_id": "1234567890" }, "fetched_at": "2026-10-01" }
  },
  "meta": { "credits": 1 }
}

Troubleshooting

ErrorCauseFix
401Missing or wrong x-api-keyCheck $LEADOCEAN_API_KEY is exported in the cron environment
429Requests above the plan's rate limit (5 a second on Free, 200 on Agency)Honour the Retry-After header and add a short sleep in the loop
402The 1,000 Free records are spentMove to Solo ($97 a month) or Agency ($497 a month); see pricing
Empty resultsA filter value that is not an exact enum string, such as "VP" written as "vp"Read /v1/enums/job_level and copy the string as printed
.meta.total is nullA filter could not be applied, so the total is withheldRemove the unsupported filter and read unsupportedFilters
Cursor stops at 10,000 rowsSearch depth cap (meta.depthCapped)Split the signal into narrower filters, such as one per country

FAQ

Are these signals real time?

No. The dataset is refreshed monthly, and fetched_at on each record tells you when it was fetched. The recipe detects change at that cadence. For news-grade triggers, pair it with a source that publishes events.

What does a daily run cost?

Nothing when the count has not changed. When it fires, up to 25 records for the page plus one per enrichment. On the Free plan that is about 30 of your 1,000.

Can I use job changes as a signal?

Not as a filter. Person enrich returns career history, so you can read the start date of a current role and decide in code, but there is no "changed jobs" search parameter.

How narrow should a signal be?

Narrow enough that the count stays under 100,000 and the page of 25 is a meaningful slice. A segment of 500 to 20,000 people works well. If the total is in the millions, split it by country or headcount band and run one watch per slice.

Which AI SDR tool does this plug into?

Anything that can call HTTP or MCP: Claude Code, n8n, Clay or your own agent. See what an AI SDR is, the SDR tools compared and the sales MCPs compared, or browse the blog hub.

Point your AI SDR at a signal you can count for free

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

Get your free API key →