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

How-to guide

How to Build Custom AI SDR Sales Agents (With Three Data Tools)

A custom sales agent is a loop, a short rule list and a few tools. Here are the three data tools, the queries behind them and what comes back.

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

TL;DR

  • A custom sales agent is three data tools (size, find, enrich), a rule list that caps spend, and a human who sends. You can build it in about 90 minutes.
  • A test run spends about 20 of your 1,000 free LeadOcean records: 15 search rows and 5 enrichments. Sizing an audience costs nothing.
  • Build instead of buy when your prospecting rules are the asset. Crustdata's internal sales tools page, September 2026, makes the same case for prospecting and pipeline agents. This page shows the data layer it skips.

Prerequisites

  • A LeadOcean account from app.leadocean.io. Free is $0, no card, 1,000 records for life, 5 requests a second.
  • Python 3.10+ with requests, and any LLM that can call tools.
  • Your key in the shell. Never paste it into code.
bash
export LEADOCEAN_API_KEY="paste-your-key-here"
pip install requests

Which agent to build first

Build the prospecting agent first. It has one input (an ICP), one output (a short list) and the cheapest failure mode: a bad list, not a bad email. Crustdata's page, September 2026, names prospecting, pipeline prioritization and auto-updating prospect lists as agent types. Of those, only prospecting needs nothing but a people database.

Pipeline prioritization needs buying signals from your CRM and other sources. Keep that agent on those sources and feed it people from here.

Step 1: Write the rules first

Rules decide whether the agent is useful or expensive. Write them before any code and load them as the system prompt.

text
You are a prospecting assistant for one seller. You draft. A human sends.
1. Call size_audience before find_people. Report the total and what it counts.
2. Stop at 25 records per run. Report records spent at the end.
3. Use only enum values returned by the API. Never guess a job level or industry.
4. Use only fields present in the row. Never invent a company fact.
5. Never send, schedule or post anything. You have no tool for it.

The default count covers mailable people, not everyone. Rule 1 makes the agent say so.

Step 2: Build the sizing tool

The first tool is a free count. It is a people search with count=true as a query parameter and limit at 1. It returns meta.total and no rows.

python
import os, requests

BASE = "https://api.leadocean.io"
H = {"x-api-key": os.environ["LEADOCEAN_API_KEY"], "content-type": "application/json"}

def size_audience(filters: dict) -> dict:
    r = requests.post(f"{BASE}/v1/people/search?count=true",
                      headers=H, json={**filters, "limit": 1})
    r.raise_for_status()
    m = r.json()["meta"]
    return {"total": m["total"], "exact": m["totalIsExact"],
            "employers": m.get("distinctCompanies")}

Totals are capped at 100,000 in meta.total. If the agent gets a capped number, tell it to narrow the filters.

Step 3: Build the find tool, with a spend cap

The second tool is a capped search. The body is flat JSON, every filter is an array of strings, limit runs 1 to 100, and you page with cursor. The cap lives in code, not only in the prompt.

python
SPENT = 0
CAP = 25

def find_people(filters: dict, limit: int = 10) -> dict:
    global SPENT
    limit = min(limit, CAP - SPENT)
    if limit <= 0:
        return {"error": "record cap reached"}
    r = requests.post(f"{BASE}/v1/people/search", headers=H,
                      json={**filters, "limit": limit})
    r.raise_for_status()
    body = r.json()
    SPENT += body["meta"]["count"]
    return body

A prompt can be talked around. A counter in the tool cannot. The same filters work in both tools, so the count you showed the seller is the list the agent pulls.

Example filters for the whole run:

json
{
  "jobLevel": ["VP"],
  "jobFunction": ["Sales & Business Development"],
  "country": ["US"],
  "industry": ["Software Development"],
  "employeeRange": ["51-200"],
  "emailStatus": ["verified", "catch_all_valid", "catch_all"]
}

Step 4: Build the enrich tool

The third tool deepens one person. Enrich takes exactly one of linkedin_url, email, phone or person_id. There is no name plus domain lookup, so search first and enrich by person_id. Since October 2026 the response carries career history in resume_data.

python
def enrich_person(person_id: str) -> dict:
    global SPENT
    if SPENT >= CAP:
        return {"error": "record cap reached"}
    r = requests.post(f"{BASE}/v1/people/enrich", headers=H,
                      json={"person_id": person_id})
    if r.status_code == 404:
        return {"found": False}   # nobody matched, costs nothing
    r.raise_for_status()
    SPENT += 1
    return r.json()

A 404 costs nothing. Past roles, education and certifications come back when held, which gives the agent real material for a first line instead of a guess.

Step 5: Wire the tools to a loop

Any model that supports tool calling works. Give it the three functions, the rules from step 1, and the user's ICP. The loop is the same everywhere: call the model, run the tool it asks for, append the result, repeat until it answers.

python
TOOLS = {"size_audience": size_audience,
         "find_people": find_people,
         "enrich_person": enrich_person}

def run(model_call, icp: str):
    messages = [{"role": "user", "content": icp}]
    while True:
        step = model_call(messages)          # returns text or a tool request
        if step["type"] == "text":
            return step["text"], SPENT
        result = TOOLS[step["name"]](**step["args"])
        messages.append({"role": "tool", "name": step["name"], "content": result})

model_call is whatever SDK you use. Check that provider's own tool-calling docs for the message format. The agent logic above does not change.

To skip the REST code, point a client at the hosted MCP server at https://api.leadocean.io/mcp. It exposes 13 tools with OAuth sign-in, no key pasted. Seven never spend records. The Claude Code sales agent guide shows that route.

Step 6: Check the count against the API

Do not trust a number you cannot reproduce. Run step 2's call by hand and compare it with what the agent reported.

bash
curl -s "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":["US"],"industry":["Software Development"],
       "employeeRange":["51-200"],"limit":1}' | jq '.meta'

If the totals differ, the agent changed a filter. Read its tool calls. GET /v1/account is free and shows what you have left.

Step 7: Add a review queue

An agent that drafts needs somewhere to put drafts. Write each run to a file the seller reads before anything is sent. Keep the count, the filters and the records spent at the top so the run is auditable.

python
import datetime, json

def save_brief(icp, count, rows, spent):
    name = f"briefs/brief-{datetime.date.today()}.json"
    os.makedirs("briefs", exist_ok=True)
    with open(name, "w") as f:
        json.dump({"icp": icp, "count": count, "rows": rows,
                   "records_spent": spent}, f, indent=2)
    return name

Review the first five briefs by hand. Look for rows where the first line claims something the row does not show. Fix the rules, not the row, and rerun. Add a sequencer only after the briefs hold up.

What you get

The sizing tool answers first. This is the free MCP count tool run on 21 October 2026 with the filters above.

json
{
  "total": 1649,
  "totalIsExact": true,
  "totalAll": 4141,
  "distinctCompanies": 1125,
  "credits": 0
}

That is 1,649 mailable VPs of sales at 1,125 US software companies with 51-200 staff, for 0 records. Without an email filter the same filters match 4,141 people. Your numbers will differ as the data refreshes monthly.

A search row has a fixed shape. This is the response shape from the API reference, with placeholder values.

json
{
  "data": [
    {
      "profile_data": { "profile_full_name": "Jane Doe" },
      "contact_data": { "has_email": true, "email_status": "verified" },
      "resume_data": { "<current role and company>": "<values>" },
      "meta": { "source_ids": { "person_id": "<digits>" } }
    }
  ],
  "meta": { "count": 15, "limit": 15, "credits": 15, "total": "<total>", "nextCursor": "<cursor>" }
}

Troubleshooting

ErrorCauseFix
401Missing or wrong x-api-keyCopy the key from app.leadocean.io and re-export $LEADOCEAN_API_KEY.
429Above the plan's rate limit: 5 a second on Free, 200 on AgencyWait the Retry-After seconds. Run tool calls one at a time.
402The free plan's 1,000 one-off records are spentCheck GET /v1/account. Move to Solo at $97 a month or Agency at $497.
Total of 0, or 400A filter value outside the list, such as jobLevel: "Vice President"Pass an exact string. Use GET /v1/enums/job_level or leadocean_list_enum_values.
meta.depthCapped is true, no nextCursorOne search pages to 10,000 rowsNarrow the filters and run several searches.
Export refusedOver 50,000 rows a request or 500,000 a daySplit the job. Bulk CSV export is not on Free.

FAQ

Should I build a custom sales agent or buy one?

Build when your targeting rules and review step are the edge. Buy when you need a sequencer, inbox and CRM sync this quarter. LeadOcean is the data layer only: it has no sequencer, dialer, CRM or Chrome extension. For the bought options, read what an AI sales agent is, B2B sales agents and the best AI agent tools for GTM teams.

Why cap spend in code and not in the prompt?

Models follow a rule most of the time. A counter in the tool follows it every time. On Free the 1,000 records are one-off, so a loose agent can spend them in an afternoon.

How many records does a run cost?

About 20 here: 15 search rows and 5 enrichments. One record is one person you receive. Counts, an enrichment that finds nothing and a 404 cost nothing.

Can the agent send the emails?

Not as built. It has no send tool. Read email_status before anything goes out, because bounced emails are not refunded.

What does it cost at team scale?

Paid plans have a fair-use pace, not credits. Solo is $97 a month for one person. Agency is $497 a month for teams, with 200 requests a second and 5 keys. See pricing. More recipes are in the blog hub.

Give your custom AI SDR 693M people to work from

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

Get your free API key →