How to Run Waterfall Enrichment in Claude Code

Set up an email or company waterfall in Claude Code with one Databar key instead of wiring separate provider APIs, with real commands and costs.

var(--variable-yLy1gAThf)

Databar team

Written by the Databar team

Blog

— min read

How to Run Waterfall Enrichment in Claude Code

How to Run Waterfall Enrichment in Claude Code

Set up an email or company waterfall in Claude Code with one Databar key instead of wiring separate provider APIs, with real commands and costs.

var(--variable-yLy1gAThf)

Databar team

Written by the Databar team

Blog

— min read

How to Run Waterfall Enrichment in Claude Code

Build your dream workflow with Databar today.

To run waterfall enrichment in Claude Code, connect one data layer instead of wiring every provider yourself. Add the Databar MCP server with claude mcp add, or install the Databar CLI, then ask Claude to run a waterfall such as email_getter. Databar tries providers in the order you set, stops at the first result, and bills only the provider that returned data.

This guide shows both paths side by side: what the do-it-yourself version with separate provider keys actually involves, and the working setup with one Databar key, including real commands, provider IDs and per-row cost math.

What does a waterfall need to do inside Claude Code?

A waterfall is a fallback chain. You have a record with a missing field, say a work email, and you query providers one at a time until one returns a usable value. The mechanics are covered in how waterfall enrichment works, but inside Claude Code the job breaks into five parts:

  • Order. Decide which provider goes first, usually the cheapest one with decent coverage for your market.

  • Stop rule. End the chain as soon as one provider returns data, so you don't pay for lookups you don't need.

  • Verification. Check a found email for deliverability before accepting it, and move on if it fails.

  • Normalization. Every provider returns a different JSON shape. Something has to map them to one column.

  • Bulk handling. Rate limits, retries and async jobs once you go past a handful of rows.

Claude can write all of that for you. The question is whether you want to own and maintain it.

What does the DIY version with separate API keys involve?

Here is a trimmed two-provider email waterfall with a verification step, the kind of script Claude Code will happily generate when you ask it to "find emails using Hunter, then People Data Labs":

import os, requests

def try_hunter(first, last, domain):
    r = requests.get("https://api.hunter.io/v2/email-finder", params={
        "domain": domain, "first_name": first, "last_name": last,
        "api_key": os.environ["HUNTER_API_KEY"]}, timeout=10)
    return (r.json().get("data") or {}).get("email")

def try_pdl(first, last, domain):
    r = requests.get("https://api.peopledatalabs.com/v5/person/enrich", params={
        "first_name": first, "last_name": last, "company": domain},
        headers={"X-Api-Key": os.environ["PDL_API_KEY"]}, timeout=10)
    return r.json().get("work_email")

def verify(email):
    r = requests.get("https://api.emailable.com/v1/verify", params={
        "email": email, "api_key": os.environ["EMAILABLE_API_KEY"]}, timeout=15)
    return r.json().get("state") == "deliverable"

def find_email(first, last, domain):
    for provider in (try_hunter, try_pdl):
        email = provider(first, last, domain)
        if email and verify(email):
            return email
    return None
import os, requests

def try_hunter(first, last, domain):
    r = requests.get("https://api.hunter.io/v2/email-finder", params={
        "domain": domain, "first_name": first, "last_name": last,
        "api_key": os.environ["HUNTER_API_KEY"]}, timeout=10)
    return (r.json().get("data") or {}).get("email")

def try_pdl(first, last, domain):
    r = requests.get("https://api.peopledatalabs.com/v5/person/enrich", params={
        "first_name": first, "last_name": last, "company": domain},
        headers={"X-Api-Key": os.environ["PDL_API_KEY"]}, timeout=10)
    return r.json().get("work_email")

def verify(email):
    r = requests.get("https://api.emailable.com/v1/verify", params={
        "email": email, "api_key": os.environ["EMAILABLE_API_KEY"]}, timeout=15)
    return r.json().get("state") == "deliverable"

def find_email(first, last, domain):
    for provider in (try_hunter, try_pdl):
        email = provider(first, last, domain)
        if email and verify(email):
            return email
    return None
import os, requests

def try_hunter(first, last, domain):
    r = requests.get("https://api.hunter.io/v2/email-finder", params={
        "domain": domain, "first_name": first, "last_name": last,
        "api_key": os.environ["HUNTER_API_KEY"]}, timeout=10)
    return (r.json().get("data") or {}).get("email")

def try_pdl(first, last, domain):
    r = requests.get("https://api.peopledatalabs.com/v5/person/enrich", params={
        "first_name": first, "last_name": last, "company": domain},
        headers={"X-Api-Key": os.environ["PDL_API_KEY"]}, timeout=10)
    return r.json().get("work_email")

def verify(email):
    r = requests.get("https://api.emailable.com/v1/verify", params={
        "email": email, "api_key": os.environ["EMAILABLE_API_KEY"]}, timeout=15)
    return r.json().get("state") == "deliverable"

def find_email(first, last, domain):
    for provider in (try_hunter, try_pdl):
        email = provider(first, last, domain)
        if email and verify(email):
            return email
    return None
import os, requests

def try_hunter(first, last, domain):
    r = requests.get("https://api.hunter.io/v2/email-finder", params={
        "domain": domain, "first_name": first, "last_name": last,
        "api_key": os.environ["HUNTER_API_KEY"]}, timeout=10)
    return (r.json().get("data") or {}).get("email")

def try_pdl(first, last, domain):
    r = requests.get("https://api.peopledatalabs.com/v5/person/enrich", params={
        "first_name": first, "last_name": last, "company": domain},
        headers={"X-Api-Key": os.environ["PDL_API_KEY"]}, timeout=10)
    return r.json().get("work_email")

def verify(email):
    r = requests.get("https://api.emailable.com/v1/verify", params={
        "email": email, "api_key": os.environ["EMAILABLE_API_KEY"]}, timeout=15)
    return r.json().get("state") == "deliverable"

def find_email(first, last, domain):
    for provider in (try_hunter, try_pdl):
        email = provider(first, last, domain)
        if email and verify(email):
            return email
    return None

It works for a demo. For a real list, the script still needs:

  • Three accounts and three keys, each with its own plan, billing and rotation.

  • Three auth styles. Hunter takes a query parameter, PDL a custom header, and every new provider adds another variation.

  • Three response parsers. data.email, work_email, state. A provider changes a field name and the waterfall silently returns nothing.

  • Rate limit and error handling per provider: 429s, timeouts, retries, backoff.

  • Cost tracking. Nothing in the script tells you what a run cost until the invoices arrive.

Multiply that by phone numbers, company data and job postings and you are maintaining an integration layer, not running campaigns.

How do you connect Databar to Claude Code?

Pick one of three surfaces. All use the same API key, found in your Databar workspace under Integrations.

Option 1: the MCP server (best for interactive work)

Databar hosts an MCP server at https://mcp.databar.ai/mcp. Add it to Claude Code from your terminal:

export DATABAR_API_KEY="your-api-key"
claude mcp add --transport http databar https://mcp.databar.ai/mcp \
  --header "Authorization: Bearer $DATABAR_API_KEY"

claude mcp list   # should show databar as

export DATABAR_API_KEY="your-api-key"
claude mcp add --transport http databar https://mcp.databar.ai/mcp \
  --header "Authorization: Bearer $DATABAR_API_KEY"

claude mcp list   # should show databar as

export DATABAR_API_KEY="your-api-key"
claude mcp add --transport http databar https://mcp.databar.ai/mcp \
  --header "Authorization: Bearer $DATABAR_API_KEY"

claude mcp list   # should show databar as

export DATABAR_API_KEY="your-api-key"
claude mcp add --transport http databar https://mcp.databar.ai/mcp \
  --header "Authorization: Bearer $DATABAR_API_KEY"

claude mcp list   # should show databar as

Inside a session, run /mcp to check the server status, then ask "How many Databar credits do I have left?" to confirm the tools respond. The Databar MCP overview covers the Claude desktop and Cursor setups.

Option 2: the CLI (best for files and scripts)

pip install databar
databar login --api-key your-api-key
databar whoami
databar waterfall list --format json
pip install databar
databar login --api-key your-api-key
databar whoami
databar waterfall list --format json
pip install databar
databar login --api-key your-api-key
databar whoami
databar waterfall list --format json
pip install databar
databar login --api-key your-api-key
databar whoami
databar waterfall list --format json

Claude Code can call the CLI through its Bash tool. --format json returns one object with ok and data (or error), which is easy for an agent to parse.

Option 3: the Python SDK (best for recurring jobs)

The same pip install databar package ships DatabarClient, which reads DATABAR_API_KEY from the environment. CLI and SDK access depend on your plan, so check pricing before you build a pipeline on them. The MCP vs SDK vs API guide goes deeper on when each fits.

How do you run an email waterfall from a Claude Code prompt?

With the MCP connected, the agent can discover waterfalls itself. search_waterfalls returns each waterfall's inputs, providers and per-provider credit price. For the email-by-name waterfall (email_getter), the catalog listed these providers as of September 2026:

Provider

Provider ID

Credits per lookup

Icypeas

833

3

Snov.io

612

4

Findymail

613

5

Prospeo

403

5

Leadmagic

380

5

Hunter.io

87

6

Datagma

966

6

RocketReach

611

9

People Data Labs

1108

22

Verifiers available on the same waterfall: Emailable (ID 10), Bouncer (136) and ZeroBounce (1220), each 1 credit. Prices change, so have the agent re-check with search_waterfalls before a big run.

A prompt that gives Claude enough to act on:

Use the Databar MCP. Run the email_getter waterfall for
first_name "Jane", last_name "Doe", company "acme.com".
Use providers 833, 613, 403 in that order and verify with 1220.
Tell me which provider returned the email and what it cost

Use the Databar MCP. Run the email_getter waterfall for
first_name "Jane", last_name "Doe", company "acme.com".
Use providers 833, 613, 403 in that order and verify with 1220.
Tell me which provider returned the email and what it cost

Use the Databar MCP. Run the email_getter waterfall for
first_name "Jane", last_name "Doe", company "acme.com".
Use providers 833, 613, 403 in that order and verify with 1220.
Tell me which provider returned the email and what it cost

Use the Databar MCP. Run the email_getter waterfall for
first_name "Jane", last_name "Doe", company "acme.com".
Use providers 833, 613, 403 in that order and verify with 1220.
Tell me which provider returned the email and what it cost

Claude calls run_waterfall with params, provider_ids and email_verifier, polls the task and reports back. If you leave out provider_ids, the tool uses all available providers in cost-optimized order.

How do you run a waterfall on a whole CSV?

For a list, skip the chat loop and use bulk. With the CLI, the CSV headers must match the waterfall's input names:

# leads.csv columns: first_name,last_name,company
databar waterfall bulk email_getter --input leads.csv --out results.csv
# leads.csv columns: first_name,last_name,company
databar waterfall bulk email_getter --input leads.csv --out results.csv
# leads.csv columns: first_name,last_name,company
databar waterfall bulk email_getter --input leads.csv --out results.csv
# leads.csv columns: first_name,last_name,company
databar waterfall bulk email_getter --input leads.csv --out results.csv

With the SDK, you control the providers explicitly:

from databar import DatabarClient

client = DatabarClient()  # reads DATABAR_API_KEY
people = [
    {"first_name": "Jane", "last_name": "Doe", "company": "acme.com"},
    {"first_name": "Sam", "last_name": "Lee", "company": "example.com"},
]
results = client.run_waterfall_bulk_sync(
    "email_getter", people, enrichments=[833, 613, 403]
)
# results line up with inputs; None means no provider found data
for person, result in zip(people, results):
    print(person["last_name"], result)
from databar import DatabarClient

client = DatabarClient()  # reads DATABAR_API_KEY
people = [
    {"first_name": "Jane", "last_name": "Doe", "company": "acme.com"},
    {"first_name": "Sam", "last_name": "Lee", "company": "example.com"},
]
results = client.run_waterfall_bulk_sync(
    "email_getter", people, enrichments=[833, 613, 403]
)
# results line up with inputs; None means no provider found data
for person, result in zip(people, results):
    print(person["last_name"], result)
from databar import DatabarClient

client = DatabarClient()  # reads DATABAR_API_KEY
people = [
    {"first_name": "Jane", "last_name": "Doe", "company": "acme.com"},
    {"first_name": "Sam", "last_name": "Lee", "company": "example.com"},
]
results = client.run_waterfall_bulk_sync(
    "email_getter", people, enrichments=[833, 613, 403]
)
# results line up with inputs; None means no provider found data
for person, result in zip(people, results):
    print(person["last_name"], result)
from databar import DatabarClient

client = DatabarClient()  # reads DATABAR_API_KEY
people = [
    {"first_name": "Jane", "last_name": "Doe", "company": "acme.com"},
    {"first_name": "Sam", "last_name": "Lee", "company": "example.com"},
]
results = client.run_waterfall_bulk_sync(
    "email_getter", people, enrichments=[833, 613, 403]
)
# results line up with inputs; None means no provider found data
for person, result in zip(people, results):
    print(person["last_name"], result)

And the raw REST call, if you want Claude to use curl:

curl -X POST "https://api.databar.ai/v1/waterfalls/email_getter/run" \
  -H "x-apikey: $DATABAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"params": {"first_name": "Jane", "last_name": "Doe", "company": "acme.com"},
       "enrichments": [833, 613, 403], "email_verifier": 1220}'
# returns a task_id; poll GET https://api.databar.ai/v1/tasks/{task_id}
curl -X POST "https://api.databar.ai/v1/waterfalls/email_getter/run" \
  -H "x-apikey: $DATABAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"params": {"first_name": "Jane", "last_name": "Doe", "company": "acme.com"},
       "enrichments": [833, 613, 403], "email_verifier": 1220}'
# returns a task_id; poll GET https://api.databar.ai/v1/tasks/{task_id}
curl -X POST "https://api.databar.ai/v1/waterfalls/email_getter/run" \
  -H "x-apikey: $DATABAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"params": {"first_name": "Jane", "last_name": "Doe", "company": "acme.com"},
       "enrichments": [833, 613, 403], "email_verifier": 1220}'
# returns a task_id; poll GET https://api.databar.ai/v1/tasks/{task_id}
curl -X POST "https://api.databar.ai/v1/waterfalls/email_getter/run" \
  -H "x-apikey: $DATABAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"params": {"first_name": "Jane", "last_name": "Doe", "company": "acme.com"},
       "enrichments": [833, 613, 403], "email_verifier": 1220}'
# returns a task_id; poll GET https://api.databar.ai/v1/tasks/{task_id}

One detail that trips people up: task results are stored for 24 hours. Save the output to a file or a Databar table as soon as the task completes.

How much does a waterfall cost per row?

Only the provider that returns data is charged. Lookups that come back empty cost nothing. Using the order above as an illustration (Icypeas at 3, Findymail at 5, Prospeo at 5, ZeroBounce at 1):

  • Icypeas finds a deliverable email: 3 + 1 = 4 credits.

  • Icypeas misses, Findymail finds one: 0 + 5 + 1 = 6 credits.

  • All three miss: 0 credits.

Two caveats. First, if a provider returns an email that fails verification, that provider is still charged because it did return a result, and the waterfall moves on to the next one. Second, a partial record counts as a result: the cascade stops and bills even if some fields are empty. For context, the Build plan is $99/month for 5,000 credits, so a 4-credit row works out to about 8 cents at that rate.

Should you order providers by price or by coverage?

Start cheap, then widen. Databar's docs give the logic: if Provider A costs 1 credit and covers 40% of your records while Provider B costs 3 credits and covers 70%, putting A first means you only pay B's price on the rows A missed. Coverage varies by region and segment, so test on 50 to 100 of your own rows before committing to an order. The waterfall enrichments page shows the table view where you can drag providers into order and watch the per-row cost.

How do you turn this into a reusable Claude Code skill?

Once the flow works, save it as a skill so you can run it with one command. Create .claude/skills/enrich-emails/SKILL.md in your project:

---
name: enrich-emails
description: Find verified work emails for a CSV of people using the Databar email_getter waterfall. Use when the user hands over a lead list with names and company domains.
disable-model-invocation: true
---

1. Check the CSV has first_name, last_name, company columns. Rename if needed.
2. Run `databar waterfall get email_getter --format json` and show current provider prices.
3. Estimate worst-case cost (rows x most expensive enabled provider + verifier) and ask before running more than 50 rows.
4. Run `databar waterfall bulk email_getter --input leads.csv --out enriched.csv`.
5. Report how many rows got an email and how many came back empty.
6. Never overwrite the input file

---
name: enrich-emails
description: Find verified work emails for a CSV of people using the Databar email_getter waterfall. Use when the user hands over a lead list with names and company domains.
disable-model-invocation: true
---

1. Check the CSV has first_name, last_name, company columns. Rename if needed.
2. Run `databar waterfall get email_getter --format json` and show current provider prices.
3. Estimate worst-case cost (rows x most expensive enabled provider + verifier) and ask before running more than 50 rows.
4. Run `databar waterfall bulk email_getter --input leads.csv --out enriched.csv`.
5. Report how many rows got an email and how many came back empty.
6. Never overwrite the input file

---
name: enrich-emails
description: Find verified work emails for a CSV of people using the Databar email_getter waterfall. Use when the user hands over a lead list with names and company domains.
disable-model-invocation: true
---

1. Check the CSV has first_name, last_name, company columns. Rename if needed.
2. Run `databar waterfall get email_getter --format json` and show current provider prices.
3. Estimate worst-case cost (rows x most expensive enabled provider + verifier) and ask before running more than 50 rows.
4. Run `databar waterfall bulk email_getter --input leads.csv --out enriched.csv`.
5. Report how many rows got an email and how many came back empty.
6. Never overwrite the input file

---
name: enrich-emails
description: Find verified work emails for a CSV of people using the Databar email_getter waterfall. Use when the user hands over a lead list with names and company domains.
disable-model-invocation: true
---

1. Check the CSV has first_name, last_name, company columns. Rename if needed.
2. Run `databar waterfall get email_getter --format json` and show current provider prices.
3. Estimate worst-case cost (rows x most expensive enabled provider + verifier) and ask before running more than 50 rows.
4. Run `databar waterfall bulk email_getter --input leads.csv --out enriched.csv`.
5. Report how many rows got an email and how many came back empty.
6. Never overwrite the input file

disable-model-invocation: true means Claude only runs it when you type /enrich-emails, which is what you want for anything that spends credits. The best Claude Code skills for GTM post has more recipes to build on. Databar also publishes ready-made skills for single, bulk, table and waterfall enrichment in its MCP server repo.

DIY provider keys vs Databar: which approach fits?

Approach

Pricing model

Best for

Data coverage focus

API/MCP access

Separate provider APIs wired in scripts

One subscription or credit pack per provider

Teams committed to one or two providers with negotiated contracts

Whatever providers you integrate and maintain

Each provider's own API; MCP only where the vendor offers one

Databar MCP in Claude Code

Shared credits; only the provider that returns data is charged

Interactive research, testing provider order, small batches

100+ data sources behind one key

Hosted MCP at mcp.databar.ai

Databar CLI or Python SDK

Same credits as above

Bulk CSV runs, scheduled scripts, production pipelines

Same catalog

REST API v1, CLI and SDK (plan-dependent)

When does wiring providers yourself still make sense?

If you already hold a large contract with one provider and only need that source, calling it directly avoids a second layer. The same goes for teams that need custom caching or retry policies across millions of records. You can also mix the two: Databar lets you bring your own API key for supported providers, so a contract you already pay for can sit inside a Databar waterfall next to providers billed in credits. For everyone else, the maintenance cost of the DIY script grows with every provider you add. For a broader look at data sources, see the best data APIs for Claude Code, and once emails are in hand, build the outbound campaign in Claude Code.

FAQ

Does Databar charge when no provider in the waterfall finds data?

No. Lookups that return nothing are not charged. You pay for the provider that returned a result, plus the verifier if you enabled one. A provider that returns a partial record is charged, because it did return data.

Can I pick which providers run and in what order?

Yes. Pass provider IDs in order: provider_ids in the MCP tool, enrichments in the SDK and REST API, or --providers in the CLI. In a Databar table you drag providers into order and toggle them on or off.

Should I use the MCP or the CLI for 2,000 rows?

Use the CLI or SDK bulk commands. The MCP suits exploration and small batches (its bulk tools take around 100 records per call), but a bulk job from a CSV is faster, cheaper in context tokens and easier to rerun.

How long are waterfall results kept?

Headless task results are stored for 24 hours, after which the task returns a "gone" status. Write results to a file or a Databar table when the task completes.

Try it on your own list

Connect the MCP, run email_getter on ten of your own leads and look at which provider answered each row. That tells you more about the right order than any benchmark. Start free with the 14-day trial and 100 credits, or book a founder demo to walk through your provider order with us.

Build your dream workflow today

Start for free today · no credit card required

Build your dream workflow today

Start for free today · no credit card required

Build your dream workflow today

Start for free today · no credit card required