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

Enrichment how-to

How to understand and test a waterfall enrichment (with real queries)

A waterfall asks sources in order and stops at the first answer you trust. Here is the loop with real requests, real output and the cost of each hop.

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

Waterfall enrichment asks data sources for the same field one at a time and stops at the first answer that passes your test. Later sources see only the rows earlier ones missed. That is the whole idea.

It matters because it changes what you pay for. One source leaves gaps. A chain fills them, and the order of the chain decides the bill.

TL;DR

  • A waterfall is three parts: an order of sources, a stop rule per row, and a hand-off of the leftovers.
  • The one-row test takes about 15 minutes and spends at most 1 LeadOcean record. A 404 miss spends none (LeadOcean docs, September 2026).
  • Put the source that charges nothing for a miss first. Put the costly source last and feed it only the gaps.

Prerequisites

  • A LeadOcean key from app.leadocean.io/register. Free is 1,000 records for life, no card.
  • The key in your shell: export LEADOCEAN_API_KEY=...
  • curl and jq.
  • One LinkedIn profile URL to test with.

Step 1: Size the audience for free

Count before you enrich anything. A count spends no records, however many times you run it. This is the MCP tool leadocean_count_leads:

json
{
  "jobLevel": ["Director"],
  "jobFunction": ["Sales & Business Development"],
  "country": ["DE"],
  "facets": false
}

Over REST, the same sizing call is a search with count=true and limit=1. Send count=true as a query parameter, not in the JSON body (API reference, October 2026).

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 '{"jobLevel":["Director"],"jobFunction":["Sales & Business Development"],"country":["DE"]}' \
  | jq '.meta | {total, totalIsExact, distinctCompanies}'

If the number is small, widen the filter before you build a chain around it.

Step 2: Pick one field and one identifier

A waterfall fills one field at a time. Here the field is a work email and the identifier is a LinkedIn URL.

POST /v1/people/enrich takes exactly one of linkedin_url, email, phone or person_id. There is no lookup by name plus company. If you hold only a domain, search it first, then enrich by person_id.

Step 3: Run hop 1 on one row

Hop 1 is the source with the cheapest miss. On LeadOcean a person we hold nothing for is a 404 and costs nothing. A hit costs one record.

bash
curl -s -o hop1.json -w '%{http_code}\n' -X POST "https://api.leadocean.io/v1/people/enrich" \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  -H "content-type: application/json" \
  -d '{"linkedin_url":"https://www.linkedin.com/in/jane-doe","reveal_email":true}'

The status code prints, and the body lands in hop1.json. reveal_email costs no extra record. Without it you still see the has_email flag, so you know what exists before you ask for the address.

Step 4: Test the answer against a stop rule

The stop rule says whether an answer ends the row. Without it you pass junk down the chain. For cold email, accept a work address that is verified or catch_all_valid, nothing else.

bash
jq -r '(.data.contact_data.contact_emails // [])
  | map(select(.type=="work" and (.status=="verified" or .status=="catch_all_valid")))
  | if length > 0 then "STOP" else "GAP" end' hop1.json

Every row ends in one of three states. STOP is a good email. GAP is a person we hold, with no qualifying email. MISS is the 404: a person we hold nothing for. Only GAP and MISS rows go on to hop 2.

The full loop over a file of URLs, with the leftovers written out for the next source, is built step by step in how waterfall enrichment works in B2B prospecting. In Clay, a column can run only when a condition is true, which is how a paid column fires on empty rows only (Clay docs, September 2026). See the LeadOcean and Clay recipe.

Step 5: Price the chain before you build it

Three costs add up: the price of each hit, what a source charges on a miss, and your own upkeep for every extra hop. Put your own numbers into this sketch. The figures below are placeholders, not measured results.

bash
rows=1000; hop1_fill=600; hop2_fill=150
hop2_seen=$((rows - hop1_fill))
echo "hop 2 is billed for $hop2_seen lookups, not $rows"
echo "rows still empty after hop 2: $((hop2_seen - hop2_fill))"

Hop 1 costs nothing on its 400 misses. Hop 2 sees only those 400 rows. Reverse the order and the costly source sees all 1,000. If hop 1 already fills most rows, a second hop adds little and a longer chain only adds upkeep.

Why the order of sources is the whole design

Order sources by cost on a miss first, then by coverage. A source that bills for an empty answer belongs late, where it sees few rows. A source that is free on a miss belongs early, where it sees all of them.

The same logic applies inside LeadOcean. The GET /v2/people/email/work and GET /v2/people/phone endpoints cost one record per call, even when they find nothing. Do not use them as extra hops after enrich. Use them when you need one field and no profile.

Where waterfalls go wrong

Three failure modes show up again and again. Each one is a rule you skipped.

  • No stop rule. Every answer counts, so bad addresses flow down the chain and into your sequencer.
  • No cap on hops. An agent or script keeps calling sources for one stubborn row. Set a maximum per row.
  • No log of the winner. You cannot retire a weak hop if you never recorded which source filled each field. Write the source name and the date next to every value.

When a waterfall is the wrong tool

If you can state the audience as filters, search it instead. A search returns people with email flags attached, and sizing it is free. POST /v1/exports returns up to 50,000 rows a request and 500,000 a day. Bulk CSV export is not on Free.

A waterfall also cannot fix a bad stop rule. If plain catch_all counts as a pass, a longer chain sends more unproven addresses through. Fix the rule before you add a source.

What you get

The Step 1 count, run on 2026-10-21 through leadocean_count_leads for Director level, Sales & Business Development, country DE:

json
{
  "total": 11351,
  "totalIsExact": true,
  "totalAll": 42084,
  "distinctCompanies": 6627,
  "defaults": "Counted only addresses worth sending to (emailStatus: verified or catch_all_valid or catch_all)"
}

That tool counts verified, catch_all_valid and catch_all addresses by default. So 11,351 is the mailable count, 42,084 is everyone matching, and the call cost 0 records.

Step 3 returns one person. This is the response shape from the API reference, with placeholder values:

json
{
  "success": true,
  "data": {
    "contact_data": {
      "has_email": true,
      "email_status": "verified",
      "email_type": "work",
      "contact_emails": [
        { "email": "jane.doe@acme.com", "type": "work", "status": "verified" }
      ],
      "contact_phones": [
        { "phone": "+14155550133", "type": "mobile" }
      ]
    }
  }
}

The stop rule reads contact_emails[].type and .status. A phone on a GAP row is already paid for, so keep it.

Troubleshooting

ErrorCauseFix
401Missing or wrong x-api-key headerRe-export $LEADOCEAN_API_KEY. The header is x-api-key, not Authorization.
429Requests above your plan's rate limit (5 a second on Free, 200 on Agency)Slow the loop and honor Retry-After. A 429 costs nothing.
402Records spent (1,000 for life on Free)Stop the run or move to a paid plan. A 402 costs nothing.
404 on a rowLeadOcean holds nothing for that personExpected. Mark the row MISS and send it on. No record is spent.
Empty search resultsEnum value spelled wrong, such as vp for jobLeveljobLevel takes exact strings (VP, C-Team, Director). seniority is lowercase. List values with GET /v1/enums.
Export refusedOver 50,000 rows a request or 500,000 a day, or a bulk export on FreeSplit the request. Bulk CSV export is not on Free (LeadOcean docs, October 2026).

FAQ

What is waterfall enrichment in one sentence?

It is a fallback chain: each row goes to the next data source only if the previous one returned nothing you trust for that field.

Why does it matter?

It raises fill rate over any single source and keeps cost down, because later sources see only the gaps. Both effects depend on the order of the chain and on the stop rule.

How many sources should a waterfall have?

Two or three. Each hop fills a smaller share of what is left, because the easy rows are already done. Measure every hop and drop one that adds little.

Does a waterfall guarantee a valid email?

No. It finds a value and does not prove the mailbox exists. Test each answer against email_status. LeadOcean gives no refund for bounced emails, so read the status before you send.

Does LeadOcean run the whole waterfall?

No. LeadOcean is a first hop: one identifier in, the record we hold out. Your script, a Clay table or an agent handles the later hops. For plans see /pricing, and for the longer build read how waterfall enrichment works in B2B prospecting, the definition and the tool ranking.

Test the first hop of your waterfall free

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

Get your free API key →