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, VerifyHQ and ReachHQ Outbound Stack (List, Verify, Send)

Pull a list from LeadOcean, verify only the risky emails in VerifyHQ, and load a ReachHQ campaign. A 500-row pilot runs on the free plan.

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

This stack has three jobs and three tools. LeadOcean builds the list, VerifyHQ checks the emails LeadOcean could not confirm, and ReachHQ sends. Each tool stops where the next begins, and nothing in between needs a person.

TL;DR

  • Time: about 45 minutes for a 500-row pilot, then the same scripts run any size.
  • Records: about 500 LeadOcean records (one per exported row) on the Free plan's 1,000. Sizing the list costs nothing.
  • Cost per 10,000 contacts: about $406 a month in list prices (LeadOcean Solo, VerifyHQ Lite, ReachHQ Scale). ReachHQ's yearly billing takes off 2 months.

Why this split works

Most cold email waste comes from one status: catch_all. A catch-all domain accepts any address, so LeadOcean cannot prove the mailbox exists. Verifying every row again wastes credits on addresses LeadOcean already confirmed. Verifying only the open rows spends them where the answer is still unknown.

The sender is the last step because a bad list damages the sending domain, and a damaged domain is expensive to repair. Put the checks before the send, never after. LeadOcean has 13 email status values, and the split in Step 3 uses four of them. Everything else (risky, invalid, role, disposable, spam_trap) stays out of the export filter, so it never reaches the sender.

Prerequisites

  • A LeadOcean account and key at app.leadocean.io. Export it as $LEADOCEAN_API_KEY. Free needs no card.
  • A VerifyHQ account and API key, exported as $VERIFYHQ_API_KEY. New accounts get free credits (confirm the amount at signup, VerifyHQ homepage, October 2026).
  • A ReachHQ key with the scopes leads:write, lead_lists:write and recipients:write, exported as $REACHHQ_API_KEY (ReachHQ REST API guide, October 2026). ReachHQ's plan comparison marks API access "No" on Solo and the trial and "Yes" on Scale, so budget for Scale (ReachHQ pricing, October 2026).
  • Python 3 with requests, and curl.

Step 1: Size the list (free)

Check the audience before you spend a record. A count is free, however many times you ask.

bash
curl -G "https://api.leadocean.io/v1/people/search" \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  --data-urlencode "count=true" --data-urlencode "limit=1" \
  --data-urlencode 'jobLevel=VP' \
  --data-urlencode 'jobFunction=Sales & Business Development' \
  --data-urlencode 'country=CA'

The response has meta.total and an empty data array. meta.total is capped at 100,000. The same count works over MCP with leadocean_count_leads.

Step 2: Export the list from LeadOcean

Export with emailStatus set to the statuses you want to send, plus catch_all and untested for the rows VerifyHQ will check. Several values in one filter join with OR.

bash
curl -X POST https://api.leadocean.io/v1/exports \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"stack pilot","filters":{"jobLevel":["VP"],"jobFunction":["Sales & Business Development"],"country":["CA"],"emailStatus":["verified","catch_all_valid","catch_all","untested"],"emailType":["work"]},"limit":500,"columns":["person_id","profile_first_name","profile_last_name","current_job_title","current_company_name","current_company_domain","email_N_address","email_N_status"]}'

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

Poll until data.status is done, then download. One request takes up to 50,000 rows, and 500,000 rows a day. Bulk CSV export is not on Free. On a Free key, run the pilot with POST /v1/people/search (limit up to 100, cursor pagination) and write the rows to CSV yourself. Solo includes the export. Prefer clicking? The Exports page at app.leadocean.io has a filter builder, a column picker and the record price before you start.

Step 3: Split by email status

Rows LeadOcean marks verified or catch_all_valid skip the second check. Only catch_all and untested go to VerifyHQ.

python
import csv

send_to_vhq = ("catch_all", "untested")
keep, check = [], []
with open("leads.csv", newline="") as f:
    reader = csv.DictReader(f)
    fields = reader.fieldnames
    for row in reader:
        (check if row["email_1_status"] in send_to_vhq else keep).append(row)

for name, rows in (("keep.csv", keep), ("to_verify.csv", check)):
    with open(name, "w", newline="") as f:
        w = csv.DictWriter(f, fieldnames=fields)
        w.writeheader()
        w.writerows(rows)
print(len(keep), "keep,", len(check), "to verify")

Step 4: Verify the risky rows in VerifyHQ

Upload to_verify.csv as a bulk file, or call the single-email endpoint for a handful. VerifyHQ's homepage shows this request (VerifyHQ homepage, October 2026).

bash
curl -X POST https://api.verifyhq.io/api/public/verify \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $VERIFYHQ_API_KEY" \
  -d '{"email": "jane@acme.com"}'

The sample response carries email, result, score, tier, do_not_mail, reason, checks, hosting and domain. VerifyHQ does not publish a rate limit or the full list of result values. Keep rows with do_not_mail false and a score you trust. Its own page suggests sending above 80 and reviewing 50 to 80. Tune that on your bounce data, then append the survivors to keep.csv.

Step 5: Load ReachHQ and attach the list

ReachHQ imports rows with a dry run first, then commits them to a static list (ReachHQ API reference, October 2026).

python
import csv, os, requests

API = "https://api.reachhq.io/api/v1/leads/import"
HEAD = {"Authorization": f"Bearer {os.environ['REACHHQ_API_KEY']}"}
LIST_ID = os.environ["REACHHQ_LIST_ID"]

rows = [{
    "email": r["email_1_address"],
    "firstName": r["profile_first_name"],
    "lastName": r["profile_last_name"],
    "company": r["current_company_name"],
    "jobTitle": r["current_job_title"],
    "domain": r["current_company_domain"],
} for r in csv.DictReader(open("keep.csv"))]

for i in range(0, len(rows), 500):
    body = {"action": "dryRun", "source": "CSV", "fileName": "keep.csv",
            "rows": rows[i:i+500]}   # switch to commit, add listId, after reading the preview
    print(requests.post(API, headers=HEAD, json=body).json())

Then attach the list to a campaign. Large lists answer RUNNING, so poll audienceJobStatus.

bash
curl -X POST https://api.reachhq.io/api/v1/campaigns/CAMPAIGN_ID/recipients \
  -H "Authorization: Bearer $REACHHQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"action":"addFromList","leadListId":"LIST_ID"}'

ReachHQ allows 600 requests per 60 seconds per credential and answers 429 above it (ReachHQ REST API guide, October 2026). The 500-row chunk is our choice, not a ReachHQ limit. Launching needs a separate campaigns:launch scope, so keep it off this key.

What you get

A live free count on 2026-10-21 for jobLevel VP, jobFunction Sales & Business Development, country CA, using the default mailable filter (verified, catch_all_valid or catch_all emails):

json
{
  "total": 5574,
  "totalIsExact": true,
  "totalAll": 18800,
  "distinctCompanies": 3873,
  "credits": 0
}

The filter without any email condition matches 18,800 people. credits: 0 confirms the count spent nothing.

One exported row has this shape, with placeholder values (response shape from the API reference):

text
person_id,profile_first_name,profile_last_name,current_company_domain,email_1_address,email_1_status
p_000123,Jane,Doe,acme.com,jane.doe@acme.com,catch_all

Cost per 10,000 contacts

Monthly list prices, checked October 2026. Each plan is a flat monthly price, with no per-record charge.

StepTool and planMonthly list priceNote
ListLeadOcean Solo$97Bulk CSV export included. Agency is $497 for teams.
VerifyVerifyHQ Lite$9.9910K verification credits a month, for the catch_all and untested subset (VerifyHQ, October 2026)
SendReachHQ Scale$299API access, 750,000 emails a month, 10 seats (ReachHQ pricing, October 2026)
Total$405.99Monthly billing. ReachHQ yearly billing gives 2 months free. VerifyHQ shows a monthly plan and one-time credit packs, no yearly price.

A 3-step sequence to 10,000 people is 30,000 emails, well inside Scale's limit. That sum is ours. ReachHQ also lists email verification on every plan, so test its check on your catch_all rows before you add VerifyHQ. See pricing for every LeadOcean plan.

Scaling past the pilot

Run the same three scripts with a bigger limit. Check three things before you do.

  1. Re-run the free count with your final filters, so the export size is a number you chose.
  2. Confirm the VerifyHQ subset fits your credits. The subset is usually a fraction of the export, so read the to verify line the Step 3 script prints.
  3. Run one ReachHQ dryRun on the full file and read the preview before any commit.

One search pages to 10,000 rows (meta.depthCapped marks the end), so split a larger market into several filtered exports. A narrower filter also gives a better list than a wider one. Keep person_id in every file so you can join VerifyHQ results back to the LeadOcean rows later.

Where each tool stops

  • LeadOcean builds and sizes the list and reports an email status per row. It does not send, verify catch-all mailboxes, or run a sequencer.
  • VerifyHQ checks addresses. It does not know your audience and does not publish its rate limit.
  • ReachHQ sends and manages the campaign. Its pricing gates API access to Scale.

Troubleshooting

ErrorCauseFix
401 from LeadOceanMissing or wrong x-api-key.Send the key from app.leadocean.io in the x-api-key header.
429 from LeadOceanRequests above the plan's rate limit (5 a second on Free, 200 on Agency).Honour Retry-After and slow the loop.
402 on export or searchRecords spent.Lower limit, or move to a paid plan.
Empty resultsA bad enum value, such as vp for jobLevel.Check leadocean_list_enum_values. jobLevel is VP, seniority is lowercase.
Export refusedOver 50,000 rows a request or 500,000 a day.Split the export into smaller requests.
401 from ReachHQWrong header.Use Authorization: Bearer, not x-api-key.

FAQ

Why verify only some rows?

LeadOcean already marks those rows verified or catch_all_valid. A second check on them adds cost and little information. Spend VerifyHQ credits on catch_all and untested.

Does LeadOcean refund bounced emails?

No. Read email_status before you send.

Can an AI agent run the list step?

Yes. The hosted MCP server at https://api.leadocean.io/mcp has 13 tools. leadocean_count_leads sizes a list for free.

Can I use a different sender?

Yes. The list and verify steps do not change. See the Smartlead and Pipedrive stack, the n8n and Attio stack or the Lemlist and HubSpot stack.

Build your first list, verify, send stack with a free LeadOcean key

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

Get your free API key →