A Clay and BlitzAPI Enrichment Waterfall, Step by Step

Three independent agency guides cover Clay waterfalls as drag-and-drop columns. None show the request, the response, or an empty result. Here is the scripted version.

Anshul
Anshul Bhatia
Founder
September 2, 2026 · 10 min read

Finding the person, then the email, then the phone number, all without opening a Clay table. That is a Clay waterfall enrichment workflow run from scripts and HTTP calls instead of drag-and-drop columns, and it is how Lead Line Partners runs it for scored, per-task lookups that do not justify standing up a table. Find the person first, through the Clay CLI or BlitzAPI's employee finder. Enrich the email next, through BlitzAPI's waterfall endpoint keyed on a LinkedIn URL. And enrich the phone number the same way. But log a miss instead of guessing one. Five moves. That's the walkthrough below.

Why script it instead of building a Clay table

A Clay table earns its keep when a waterfall runs the same way every week: same providers, same order, same list shape, and someone who is not an engineer needs to watch it run and glance at rows as they fill in. That is most of Clay's audience, and our own Clay review covers that use case in more depth.

But a one-off lookup for a scored account, mid-research, does not need any of that.

Opening Clay's UI, building a table, and wiring three enrichment steps together takes longer than writing forty lines of Python that call an endpoint directly. So we script it. We run these lookups from Claude Code more than from a spreadsheet now, and this walkthrough is that path: find the person, hit the email endpoint, hit the phone endpoint, and log what came back.

Find the person first

Clay CLI, query mode

Clay's CLI supports Clay CLI people search in query mode: pass filters like title, seniority, and company domain, and it returns matches straight from Clay's own database, the same data a table-based people search would surface. Just callable from a script instead of a UI.

The example below is illustrative pseudo-code, not literal current syntax. Clay's CLI syntax moves, and a copy-pasted snippet here would go stale faster than the mechanism it demonstrates.

# illustrative only, not literal Clay CLI syntax
clay search people \
  --title "VP RevOps" \
  --company-domain "example.com" \
  --mode query \
  --token "$CLAY_API_KEY"

The output is a list of candidate matches, one LinkedIn URL per person. And that URL is the one field the rest of this waterfall keys on.

BlitzAPI employee finder, by company LinkedIn URL

When the starting point is a company rather than a person, an employee finder API takes it from there. POST /v2/search/employee-finder takes a company LinkedIn URL and returns employee LinkedIn profiles at that company, paginated up to 200 pages and 10,000 results, per BlitzAPI's documentation (accessed September 2026).

It returns no email, and no phone number either. Just who works there, and their LinkedIn profile.

curl -X POST https://api.blitz-api.ai/v2/search/employee-finder \
  -H "Authorization: Bearer $BLITZ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "company_linkedin_url": "https://www.linkedin.com/company/example-co",
    "page": 1
  }'

So filter the results by title before sending anyone into the email step below. The employee finder does not know your ICP. It just lists names.

Email enrichment through the waterfall endpoint

Once a candidate has a LinkedIn URL, whether it came from Clay's people search or BlitzAPI's employee finder, the email enrichment API step is one call, repeated for each candidate. BlitzAPI's waterfall endpoint for this is POST /v2/enrichment/email, keyed on person_linkedin_url. That single field is the whole waterfall's connective tissue: Clay does not need to know how BlitzAPI found the person, and BlitzAPI does not need to know Clay exists. And the LinkedIn URL is the interface between them.

curl -X POST https://api.blitz-api.ai/v2/enrichment/email \
  -H "Authorization: Bearer $BLITZ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "person_linkedin_url": "https://www.linkedin.com/in/example-person"
  }'

A found match comes back shaped like this, per BlitzAPI's own documentation (accessed September 2026):

{
  "found": true,
  "email": "person@example.com",
  "all_emails": [
    {
      "email": "person@example.com",
      "job_order_in_profile": 1,
      "company_linkedin_url": "https://www.linkedin.com/company/example-co",
      "email_domain": "example.com"
    }
  ],
  "fair_usage": {
    "records_used": 179,
    "records_remaining": 4821,
    "next_reset_at": "2026-10-01T00:00:00Z",
    "rate_limit": {
      "requests_per_second": 10,
      "remaining_this_second": 4
    },
    "request_id": "a1b2c3d4-0000-0000-0000-000000000000"
  }
}

found is the field the rest of your script hinges on. email is the single best-match address, null on a miss. all_emails is not a flat list of strings: each candidate comes back as its own object, carrying email, job_order_in_profile, company_linkedin_url, and email_domain, which matters when a company runs more than one address format at once. fair_usage tracks account-level consumption, and it nests the per-second rate limit inside it (rate_limit.requests_per_second) rather than keeping the two separate, alongside a request_id for tracing a specific call.

A script wrapping this is short:

resp = post_json(f"{BASE}/v2/enrichment/email", {"person_linkedin_url": url})
if resp["found"]:
    save_email(person_id, resp["email"])
else:
    log_miss(person_id, "email")

Our own BlitzAPI review covers where this endpoint sits inside the broader enrichment stack, beyond this one call.

Phone enrichment, same shape

The phone endpoint follows the same request and response family: POST /v2/enrichment/phone, keyed on the same person_linkedin_url, returning the same found boolean.

We have not pulled BlitzAPI's phone-enrichment documentation as thoroughly as its email endpoint's. So treat the fields below as structurally consistent rather than as a fully verified schema, and check BlitzAPI's own docs before building against it.

curl -X POST https://api.blitz-api.ai/v2/enrichment/phone \
  -H "Authorization: Bearer $BLITZ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "person_linkedin_url": "https://www.linkedin.com/in/example-person"
  }'

What we can say with confidence: the shape mirrors the email endpoint, a found boolean gates everything downstream, and a miss deserves the same discipline as an empty email lookup. Coverage specifics, which countries, which line types, are not something we have confirmed against BlitzAPI's own phone documentation as of this writing. So none of that is asserted here. Check current docs before planning volume around geographic coverage.

Rate limits: ten requests a second on the entry plan

BlitzAPI limits requests per second, applied independently to each endpoint. As of October 27, 2026, the Start plan ($99 a month) allows 10 requests per second per endpoint and the Scale plan ($499 a month) allows 25. The legacy plan we run allows 50. That is a real constraint the moment enrichment leaves a tool's internal queue and becomes a script making raw HTTP calls. Clay throttles its own waterfall internally. But a script calling BlitzAPI directly does not get that for free.

None of the UI-only Clay waterfall guides we have come across mention this, because a Clay table never exposes it. So the limit only shows up once you are the one making the calls.

import time

RATE_LIMIT = 10  # requests per second per endpoint on the Start plan; set to your plan's limit
last_call = 0

def call_endpoint(fn, *args):
    global last_call
    elapsed = time.time() - last_call
    if elapsed < 1 / RATE_LIMIT:
        time.sleep(1 / RATE_LIMIT - elapsed)
    last_call = time.time()
    return fn(*args)

Pull the employee finder first. Queue every LinkedIn URL it returns. Then pace the email and phone calls against that same limiter. Skip that step and a burst of calls in the first second gets you a stack of rejected requests, not a stack of enriched rows. And on the entry plan, calls pause once you reach the monthly record cap.

Honest handling of found: false

A found: false response means what it says: BlitzAPI's waterfall checked its sources and came up empty on that person, at that moment. It is not a signal to try harder by guessing.

The temptation is obvious. You know the company's domain. You know most of their colleagues use firstname.lastname@company.com. Pattern-guessing the address and moving on feels like finishing the job.

But it is not.

A guessed email is not enrichment. It is an unverified string that produces a bounce the moment someone sends to it, and a high bounce rate hurts deliverability more than a smaller, honest list ever does.

So log the miss. Move to the next candidate. Or accept that this row stays empty, and mean it. That is the same no-fabricated-claims discipline Lead Line Partners applies to prospect research, aimed here at the data itself instead of at the words in an email. A missing row costs nothing beyond an empty cell. A wrong one costs a bounce, hurts deliverability, and hides that cost behind a field that looks filled in.

Where a Clay table waterfall with FullEnrich still wins

None of this makes the scripted path right in every case. Four situations still point back to a Clay table, FullEnrich included in the waterfall.

Bulk lists are the first case. A one-off script makes sense for a scored subset of accounts. It stops making sense at ten thousand rows a client hands over at once, where Clay's own queueing, retry logic, and credit tracking already do the coordination work you would otherwise build yourself.

Non-engineering operators are the second. Not every operator running enrichment can write a script, or wants to maintain one when BlitzAPI or Clay ships a breaking change. A table with configured steps is the right interface for a team whose job is running campaigns, not maintaining code.

Visual QA per row is the third. Some enrichment tasks need a person glancing at each row as it fills in, catching a mismatched company before it goes further into a sequence. A spreadsheet view does that naturally. A JSON response in a terminal does not.

And there's a fourth case most teams underrate: standing, recurring enrichment that runs the same way against a growing list, week after week. That kind of work sits closer to infrastructure than to a one-off lookup, and infrastructure benefits from a table's built-in monitoring and audit trail.

FullEnrich fits into that kind of waterfall on its own merits, for a given task. It is never the fallback source by default, in Clay or anywhere else. What earns it a spot is the same question that decides every step in a waterfall: does it produce enriched rows the earlier steps missed, at a cost the task can carry. So the choice comes down to the task in front of you, script or table. Not a blanket rule.

Credit cost is a tuning parameter, not a fixed number

Every provider in a waterfall costs something per row, whether it is a BlitzAPI call or a Clay-native step, and the instinct is to ask which one is cheapest. But that is the wrong question on its own. The right one is which order, which providers, and which fallback depth get you the enriched rows you need, at a cost the task can carry. That is a design choice made per waterfall, not a number printed on a pricing page.

BlitzAPI's plans have changed since this guide first ran. The legacy plan we are on runs $399 a month for 15,000,000 records and includes no email or phone enrichment. The new public Start plan is $99 a month for 200,000 records and covers every endpoint, email and US phone included, while Scale is $499 a month for 5,000,000 records. But those figures tell you almost nothing about what a given waterfall will cost to run, because a plan price and the enrichment steps in this walkthrough are two different things.

Whether the Clay API lowers waterfall cost goes deeper on the cost question specifically. Here, the point is narrower: credit cost is one input you tune per task, alongside provider order and fallback depth. It is not a fixed line item you accept as given.

If the command-line and API side of Clay is new territory before any of this, the API and CLI side of Clay, not just the table is worth reading first.

Frequently asked questions

What is a waterfall enrichment workflow in Clay?

A waterfall enrichment workflow runs a list of prospects through more than one data provider in sequence, using each provider's output only when the one before it came back empty. In Clay's table UI, that means chaining enrichment columns with conditional logic. The mechanism here is the same idea, run through direct API calls instead of table columns.

Can you run a Clay-style enrichment waterfall without Clay's table UI?

Yes. A waterfall is just an ordering of provider calls with fallback logic, and that logic can live in a script as easily as in a table. This walkthrough runs the person-finding, email, and phone steps through the Clay CLI and BlitzAPI's HTTP endpoints directly, applying the same found-or-not-found logic a Clay table waterfall would apply.

What does found: false mean in an enrichment API response?

It means the provider checked its own data sources and did not turn up a verified match for that person. In BlitzAPI's email endpoint, found: false returns a null email rather than a guess. So the honest response is to log the miss, not to pattern-guess an address from a known company domain.

How many requests per second can you send to an enrichment API like BlitzAPI?

BlitzAPI limits each endpoint independently: 10 requests per second on its Start plan, 25 on Scale, and 50 on the legacy plan we run. Those numbers are specific to BlitzAPI. Other enrichment vendors set their own limits, so check current documentation before a script assumes any particular ceiling holds.

When should you use a Clay table with FullEnrich instead of scripting the waterfall yourself?

Reach for the table when the list is large, the operator running it is not writing scripts, the task needs a person glancing at each row, or the enrichment runs on a standing weekly cadence rather than as a one-off lookup. So those four situations favor Clay's queueing and monitoring over a script you would otherwise maintain yourself.

Supporting

  1. BlitzAPI docs: Employee Finder, accessed September 2026
  2. BlitzAPI docs: Find Work Email, accessed September 2026
  3. BlitzAPI docs: pricing and rate limits, accessed October 27, 2026
Written by
Anshul

Anshul Bhatia

Founder
IIT Kharagpur. Builds GTM systems for B2B SaaS.

Anshul builds the outbound systems behind Lead Line Partners. Clay workflows, AI enrichment, and research-first sequencing for teams that want more with less.

More posts
GTM ToolkitGuide · 11 min read

Clay in the Era of MoltSets and BlitzAPI: Retain or Retire?

Clay pricing as of October 2026, a 2,443-contact credit ledger, and a job-by-job call on where Clay still earns a place in a waterfall that starts with BlitzAPI and MoltSets.

By Anshul Bhatia
GTM ToolkitGuide · 13 min read

Price per 1,000 Contact Records Under Each Data API Billing Model

A B2B data API quotes one price and counts three different things. Here are the prices we could fetch in one table, converted to a price per 1,000 records at the cap, with what five billing models do to a waterfall budget.

By Anshul Bhatia
GTM ToolkitThought leadership · 10 min read

How Adam's MoltSets, With a $27/mo Unlimited Plan, Is Changing the Industry

A $27 a month contact data API was listed on MoltSets' pricing page by early August 2026, while the product was still invite-only. Another vendor has since cut its entry price to $99. Others held, and four could not be checked.

By Anshul Bhatia

Ready to engineer your GTM motion?

Tell us how your motion runs today. We'll show you what we'd engineer.

Contact us