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

How-to guide

How to Build a LeadOcean, Claude Code and Postgres Outbound Stack (List, Verify, Send)

Size a list for free in Claude Code, export it from LeadOcean, gate it by email status in Postgres, and hand a clean send file to your sender.

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

This stack gives each tool one job. LeadOcean builds the list, Postgres holds it and gates it, and Claude Code is the interface you ask questions in. Sending happens in a tool you already run.

The example audience is VPs of sales in the US. Swap the filters for yours.

TL;DR

  • Time: about 45 minutes for a 200-row pilot on a local Postgres.
  • Records: 200 to 400 LeadOcean records for the pilot. Sizing is free. The free plan has 1,000 records, one-off, but bulk CSV export is not on Free, so the export in step 3 needs Solo.
  • Cost per 10,000 contacts: $97 for LeadOcean Solo plus $20 for Claude Pro, about $117 in a month at monthly list prices. A local Postgres is $0. Your sender is on top.

Prerequisites

  • A LeadOcean key in $LEADOCEAN_API_KEY. Sign up at app.leadocean.io/register. No card needed. Free covers steps 1 and 2. The step 3 export needs Solo ($97 a month) or Agency ($497 a month).
  • Claude Code on a Claude plan that includes it. Claude Pro lists Claude Code at $20 a month, or $17 a month billed yearly ($200 up front) (Claude pricing, September 2026).
  • Postgres 14 or newer with psql. A local install is enough for the pilot.
  • curl.

Step 1: Size the list before you spend anything

Count first. A search with count=true and limit=1 is free (LeadOcean API reference, September 2026).

bash
curl -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"],"jobFunction":["Sales & Business Development"],"country":["US"],"emailType":["work"],"emailStatus":["verified","catch_all_valid"],"limit":1}'

Read meta.total. It is capped at 100,000. Filters join with AND, and several values inside one filter join with OR.

Step 2: Connect the LeadOcean MCP server to Claude Code

LeadOcean runs a hosted MCP server at https://api.leadocean.io/mcp with 13 tools. Auth is OAuth 2.1, so no key is pasted. Claude Code adds remote HTTP servers with one command (Claude Code docs, September 2026).

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

Start claude, type /mcp, pick leadocean and finish the sign-in in the browser. Then ask for a count.

text
Using the leadocean tools, count VPs of Sales & Business Development in the US
with a work email whose status is verified or catch_all_valid. Use
leadocean_count_leads only. Do not search or export.

leadocean_count_leads is free. Search, get, find-phone and export tools each spend records, so keep the instruction explicit. Tweak the filters in chat until the number looks right, then copy them into step 3.

Step 3: Export the pilot list

Export 200 rows, not the whole audience. Bulk CSV export is included on Solo and Agency, not on Free. The export is asynchronous and reserves records up front (LeadOcean docs, September 2026).

bash
curl -X POST https://api.leadocean.io/v1/exports \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"vp-sales-us pilot","filters":{"jobLevel":["VP"],"jobFunction":["Sales & Business Development"],"country":["US"],"emailType":["work"],"emailStatus":["verified","catch_all_valid"]},"limit":200,"columns":["person_id","profile_first_name","profile_last_name","profile_url","current_job_title","current_company_name","current_company_domain","email_status","email_N_address"],"caps":{"emails":1}}'

The answer is a 202 with an export id. Poll GET /v1/exports/{id} until status is done, then download the CSV.

bash
curl -H "x-api-key: $LEADOCEAN_API_KEY" -o leads.csv \
  https://api.leadocean.io/v1/exports/EXPORT_ID/download

One export takes up to 50,000 rows, and an account takes 500,000 rows a day.

Step 4: Load the CSV into Postgres

Load into a staging table, then upsert into the real table keyed on person_id. A re-run then updates rows instead of duplicating them. Postgres supports this with INSERT ... ON CONFLICT DO UPDATE (PostgreSQL docs, September 2026).

sql
CREATE TABLE IF NOT EXISTS leads (
  person_id      text PRIMARY KEY,
  first_name     text,
  last_name      text,
  linkedin_url   text,
  title          text,
  company        text,
  domain         text,
  email          text,
  email_status   text,
  do_not_send    boolean NOT NULL DEFAULT false,
  loaded_at      timestamptz NOT NULL DEFAULT now()
);

CREATE TEMP TABLE stage (LIKE leads INCLUDING DEFAULTS);
ALTER TABLE stage DROP COLUMN do_not_send, DROP COLUMN loaded_at;

\copy stage (person_id, first_name, last_name, linkedin_url, title, company, domain, email_status, email) FROM 'leads.csv' WITH (FORMAT csv, HEADER true)

INSERT INTO leads (person_id, first_name, last_name, linkedin_url, title, company, domain, email_status, email)
SELECT person_id, first_name, last_name, linkedin_url, title, company, domain, email_status, email FROM stage
ON CONFLICT (person_id) DO UPDATE
  SET email = EXCLUDED.email,
      email_status = EXCLUDED.email_status,
      title = EXCLUDED.title,
      company = EXCLUDED.company,
      loaded_at = now();

The column list in \copy must match the order of columns in your CSV. Check the header row of leads.csv first. psql's \copy reads the file on your machine, not on the server (PostgreSQL docs, September 2026).

Step 5: Verify with SQL

LeadOcean tags every address with email_status. Gate on it in SQL. Your suppression list lives here too.

sql
-- Mark rows you must not send to
UPDATE leads SET do_not_send = true
WHERE email IS NULL
   OR email_status NOT IN ('verified', 'catch_all_valid')
   OR domain IN (SELECT domain FROM suppressed_domains);

-- What is ready
SELECT email_status, count(*) AS rows
FROM leads
WHERE NOT do_not_send
GROUP BY email_status;

Create suppressed_domains first with one domain column: customers, competitors and anyone who opted out. LeadOcean gives no refund for bounced emails, so this gate is yours to keep strict. If you want a second opinion on risky addresses, add a verifier before the send, as in the VerifyHQ and ReachHQ stack.

Step 6: Let Claude Code run the loop

Claude Code runs shell commands, so it can drive psql and write the send file. Keep the prompt narrow. Put it in a CLAUDE.md or paste it each time.

text
You maintain the outbound list in the local Postgres database `outbound`.
Rules:
- Read the CSV header before you run \copy and match the column order.
- Never run a LeadOcean tool that spends records without asking me first.
  leadocean_count_leads and leadocean_list_enum_values are free.
- Only rows with do_not_send = false may leave the database.
Task: export rows where do_not_send = false to send.csv with columns
first_name, last_name, email, company, title. Then print the row count.

The command it should run is plain SQL.

bash
psql outbound -c "\copy (SELECT first_name, last_name, email, company, title FROM leads WHERE NOT do_not_send) TO 'send.csv' WITH (FORMAT csv, HEADER true)"

Read the permission prompts. Claude Code asks before it runs shell commands, and you decide what to allow.

Step 7: Cost a 10,000-contact run and know where each tool stops

Price the run before you scale. This script is arithmetic from list prices, not a quote.

python
rows = 10_000
leadocean_solo = 97         # flat per month, fair-use pace, no per-record price
claude_pro_monthly = 20     # billed monthly
claude_pro_yearly = 17      # per month, $200 billed up front
postgres_local = 0          # your own machine

monthly = leadocean_solo + claude_pro_monthly + postgres_local
yearly = leadocean_solo + claude_pro_yearly + postgres_local
print("Monthly list:", monthly)
print("Yearly view:", yearly)
print("Per contact:", round(monthly / rows, 4))

It prints $117 a month at monthly list prices, $114 with Claude billed yearly, and about $0.0117 a contact. Hosted Postgres and your sender are extra, and we did not price them.

LeadOcean Solo is $97 a month with a fair-use pace instead of credits. Ten thousand contacts use 10,000 to 20,000 records, and the pace limits are on the LeadOcean pricing page (October 2026). Claude Code usage is subject to your plan's limits (Claude pricing, September 2026). Agency is $497 a month if a team shares the keys.

Where each tool stops:

  • LeadOcean stops at the list. It has no sequencer, dialer, CRM or Chrome extension. email_status is a signal, not a guarantee.
  • Postgres stops at storage. It sends nothing and finds nothing. Your gate is only as good as the SQL.
  • Claude Code stops at your instructions. It does not send email, and a vague prompt can spend records.

What you get

Step 1 is a free count. We ran the same audience through LeadOcean's MCP leadocean_count_leads tool on 2026-10-21, with a work email required and the status set to verified or catch_all_valid.

text
VPs, Sales & Business Development, US, work email
emailStatus verified + catch_all_valid
total 26988   totalIsExact true   distinctCompanies 16114   credits 0

That count is of people with a work email in those two statuses, not of everyone in the role. The export create call answers with the shape below. This is the response shape from the API reference (openapi.json), with placeholder values.

json
{
  "success": true,
  "data": {
    "id": "EXPORT_ID",
    "name": "vp-sales-us pilot",
    "status": "queued",
    "requested": 200,
    "estimatedTotal": 26988,
    "perRow": 1,
    "remaining": null,
    "pollAfter": 5
  }
}

requested is the records reserved and perRow is 1 or 2. remaining is what is left after the reservation, and it is null on a paid plan, which has a pace rather than a balance. Read perRow before you scale up.

Troubleshooting

ErrorCauseFix
401 "Missing or malformed x-api-key"The header is missing or wrong.Send x-api-key: $LEADOCEAN_API_KEY.
402 on export createFewer records are left than the export reserves.Lower limit. On Free the 1,000 records are one-off, and bulk CSV export is not on Free.
429More requests a second than your plan allows (5 on Free, 50 on Solo, 200 on Agency), or an export ceiling.Honour Retry-After. Exports allow 500,000 rows a day.
Zero rows backA filter value is outside the catalogue, such as United States for US.Read the valid values from GET /v1/enums.
400 on export createlimit is over 50,000, or a filter cannot be applied.Split into several exports with narrower filters.

FAQ

Do I need Claude Code at all?

No. Steps 1, 3, 4 and 5 are plain curl and SQL. Claude Code earns its place for sizing in chat and for running the same SQL on request.

Can I use Free?

Partly. Steps 1 and 2 work, and so does a search of up to 1,000 records. Bulk CSV export is not on Free, so the export in step 3 needs Solo. See pricing.

Does the MCP server cost records?

The free tools do not: leadocean_count_leads, leadocean_count_companies, leadocean_list_filters and leadocean_list_enum_values. Search, get, find-phone and export tools spend one record per person, company or row returned.

How do I send from this?

Point your sender at send.csv. Compare the Smartlead and Pipedrive stack if you want a sequencer and CRM, or the n8n and Attio stack to automate the load. Back to the blog hub.

Size your first Claude Code outbound list on LeadOcean. Start free.

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

Get your free API key →