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":
importos,requestsdef 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)returnr.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)returnr.json().get("state") == "deliverable"def find_email(first,last,domain):forproviderin(try_hunter,try_pdl):
email = provider(first,last,domain)ifemail and verify(email):returnemailreturnNone
importos,requestsdef 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)returnr.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)returnr.json().get("state") == "deliverable"def find_email(first,last,domain):forproviderin(try_hunter,try_pdl):
email = provider(first,last,domain)ifemail and verify(email):returnemailreturnNone
importos,requestsdef 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)returnr.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)returnr.json().get("state") == "deliverable"def find_email(first,last,domain):forproviderin(try_hunter,try_pdl):
email = provider(first,last,domain)ifemail and verify(email):returnemailreturnNone
importos,requestsdef 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)returnr.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)returnr.json().get("state") == "deliverable"def find_email(first,last,domain):forproviderin(try_hunter,try_pdl):
email = provider(first,last,domain)ifemail and verify(email):returnemailreturnNone
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:
exportDATABAR_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 databaras
exportDATABAR_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 databaras
exportDATABAR_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 databaras
exportDATABAR_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 databaras
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.
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:
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. Runthe email_getter waterfall forfirst_name "Jane",last_name "Doe",company "acme.com".
Useproviders 833,613,403inthat order and verify with1220.Tell me which provider returned the email and what it cost
Use the Databar MCP. Runthe email_getter waterfall forfirst_name "Jane",last_name "Doe",company "acme.com".
Useproviders 833,613,403inthat order and verify with1220.Tell me which provider returned the email and what it cost
Use the Databar MCP. Runthe email_getter waterfall forfirst_name "Jane",last_name "Doe",company "acme.com".
Useproviders 833,613,403inthat order and verify with1220.Tell me which provider returned the email and what it cost
Use the Databar MCP. Runthe email_getter waterfall forfirst_name "Jane",last_name "Doe",company "acme.com".
Useproviders 833,613,403inthat order and verify with1220.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:
With the SDK, you control the providers explicitly:
from databar importDatabarClientclient = DatabarClient() # reads DATABAR_API_KEYpeople = [{"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 withinputs;None means no provider found dataforperson,resultinzip(people,results):print(person["last_name"],result)
from databar importDatabarClientclient = DatabarClient() # reads DATABAR_API_KEYpeople = [{"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 withinputs;None means no provider found dataforperson,resultinzip(people,results):print(person["last_name"],result)
from databar importDatabarClientclient = DatabarClient() # reads DATABAR_API_KEYpeople = [{"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 withinputs;None means no provider found dataforperson,resultinzip(people,results):print(person["last_name"],result)
from databar importDatabarClientclient = DatabarClient() # reads DATABAR_API_KEYpeople = [{"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 withinputs;None means no provider found dataforperson,resultinzip(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):
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 fora CSV of people using the Databar email_getter waterfall. Usewhen the user hands over a lead list withnames and company domains.
disable-model-invocation:true
---
1.Check the CSV has first_name,last_name,company columns. Renameifneeded.
2.Run`databar waterfall get email_getter --format json`and show current provider prices.
3.Estimateworst-casecost(rows x most expensive enabled provider + verifier)and ask before running more than 50rows.
4.Run`databar waterfall bulk email_getter --input leads.csv --out enriched.csv`.
5.Reporthow many rows got an email and how many came back empty.
6.Neveroverwrite the input file
---
name:enrich-emails
description:Find verified work emails fora CSV of people using the Databar email_getter waterfall. Usewhen the user hands over a lead list withnames and company domains.
disable-model-invocation:true
---
1.Check the CSV has first_name,last_name,company columns. Renameifneeded.
2.Run`databar waterfall get email_getter --format json`and show current provider prices.
3.Estimateworst-casecost(rows x most expensive enabled provider + verifier)and ask before running more than 50rows.
4.Run`databar waterfall bulk email_getter --input leads.csv --out enriched.csv`.
5.Reporthow many rows got an email and how many came back empty.
6.Neveroverwrite the input file
---
name:enrich-emails
description:Find verified work emails fora CSV of people using the Databar email_getter waterfall. Usewhen the user hands over a lead list withnames and company domains.
disable-model-invocation:true
---
1.Check the CSV has first_name,last_name,company columns. Renameifneeded.
2.Run`databar waterfall get email_getter --format json`and show current provider prices.
3.Estimateworst-casecost(rows x most expensive enabled provider + verifier)and ask before running more than 50rows.
4.Run`databar waterfall bulk email_getter --input leads.csv --out enriched.csv`.
5.Reporthow many rows got an email and how many came back empty.
6.Neveroverwrite the input file
---
name:enrich-emails
description:Find verified work emails fora CSV of people using the Databar email_getter waterfall. Usewhen the user hands over a lead list withnames and company domains.
disable-model-invocation:true
---
1.Check the CSV has first_name,last_name,company columns. Renameifneeded.
2.Run`databar waterfall get email_getter --format json`and show current provider prices.
3.Estimateworst-casecost(rows x most expensive enabled provider + verifier)and ask before running more than 50rows.
4.Run`databar waterfall bulk email_getter --input leads.csv --out enriched.csv`.
5.Reporthow many rows got an email and how many came back empty.
6.Neveroverwrite 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.