# Waterfall Contact Enrichment API - Email and Mobile Finder (`nabeelbaghoor/waterfall-contact-enrichment-api`) Actor

Find work emails, personal emails and mobile numbers for B2B contacts from a profile URL or a name and company, with every address verified. Also reverse email lookup, and people and company search across job title, seniority, industry, headcount and tenure. Pay per result.

- **URL**: https://apify.com/nabeelbaghoor/waterfall-contact-enrichment-api.md
- **Developed by:** [Nabeel Hassan](https://apify.com/nabeelbaghoor) (community)
- **Categories:** Business, Developer tools, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$14.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Waterfall Contact Enrichment API - Email and Mobile Finder

Give it a profile URL, or a name and a company, and get back a verified work email, a personal email and a mobile number. One flat row per contact.

### What this actor does

- **Runs a waterfall, not a single source.** The provider queries a series of upstream contact databases in turn and stops at the first one that answers, which is why the hit rate is higher than any one source on its own. That is also why an enrichment takes minutes rather than milliseconds, and why the run is built around batches rather than around one request per contact.
- **Verifies every address before returning it.** Each email comes back with a status: deliverable at roughly a two percent bounce rate, high probability at roughly nine, catch-all higher again, invalid excluded from the best-address column entirely. You can filter on it.
- **Returns the alternatives, not only the winner.** The best work email, the best personal email and the best mobile number are their own columns, and every other address and number the waterfall found is kept alongside them. The second-best address is often the one that matches a record you already hold.
- **Prices the decision up front.** A work email is one credit on your own plan, a personal email three, a mobile number ten. Asking for all three costs fourteen times what asking for the first costs, so the default is the work email alone and the run logs what it is about to spend before it starts.
- **Turns an address back into a person.** Reverse email lookup takes an address you already have and returns the person and the company behind it, which is how you fill in a sign-up list that has nothing but email addresses in it.
- **Searches for people you do not know yet.** Twenty filters over job title, seniority, function, skills, languages, universities, current and past employers, industry, location, headcount, founding year, tenure and time since the last job change. Any line beginning with a minus sign excludes that value instead of requiring it.
- **Searches for companies the same way.** Name, domain, description keywords, specialties, industry, type, headquarters, headcount and founding year. Description keywords are the filter that finds companies doing a thing rather than companies filed under a category.
- **Finds people who just moved.** Filter on days since the last job change to reach someone in their first quarter somewhere new, while they are still choosing their own tools.
- **Never submits more than you asked for.** Enrichment batches go one at a time, so a run that stops at the result cap or at the spending limit has spent your provider credits only on the contacts it actually returned.
- **Knows the difference between out of credit and finished.** This provider reports an exhausted balance as a job status rather than as an error, which is exactly how an abandoned batch gets written up as a successful empty run elsewhere. Here it stops and says so.
- **Never charges for a miss.** Only rows carrying a resolved record are billed.

### Input

| Field | What it does |
| --- | --- |
| `mode` | Enrich, reverse lookup, people search, company search, or a single lookup. |
| `linkedinUrls` | Profile URLs to enrich, one per line. |
| `contacts` | Contacts as objects, when you have CRM rows rather than links. |
| `emails` | Addresses to reverse look up. |
| `enrichFields` | Work email, personal email, mobile, and what each costs. |
| `currentJobTitles` | Titles at the current employer. |
| `seniorityLevels` | Owner through to Senior. |
| `jobFunctions` | The broad function the person works in. |
| `subFunctions` | The narrower function under it. |
| `pastJobTitles` | Titles the person held before. |
| `personNames` | First, last or full name. |
| `personLinkedinUrls` | Specific people by profile URL. |
| `personLocations` | Continent, country, region or city. |
| `personLanguages` | Languages spoken. |
| `personSkills` | Listed skills. |
| `personUniversities` | Universities attended. |
| `currentCompanyNames` | Where they work now. |
| `currentCompanyDomains` | The same, by domain. |
| `currentCompanyLinkedinUrls` | The same, by company profile URL. |
| `currentCompanyIndustries` | Employer industry. |
| `currentCompanySpecialties` | Employer specialties. |
| `currentCompanyTypes` | Public, private, nonprofit and the rest. |
| `currentCompanyHeadquarters` | Where the employer is based. |
| `pastCompanyNames` | Employers they worked at before. |
| `pastCompanyDomains` | The same, by domain. |
| `companyNames` | Company search: names. |
| `companyDomains` | Company search: domains. |
| `companyLinkedinUrls` | Company search: profile URLs. |
| `companyKeywords` | Company search: words in the description. |
| `companySpecialties` | Company search: specialties. |
| `companyIndustries` | Company search: industries. |
| `companyTypes` | Company search: organisation type. |
| `companyHeadquarters` | Company search: headquarters. |
| `exactMatch` | Match text filters exactly rather than approximately. |
| `headcountMin` / `headcountMax` | Employee count range. |
| `foundedYearMin` / `foundedYearMax` | Founding year range. |
| `yearsInPositionMin` / `Max` | Time in the current role. |
| `yearsAtCompanyMin` / `Max` | Tenure at the current employer. |
| `daysSinceJobChangeMin` / `Max` | Time since the last move. |
| `requireWorkEmail` | Keep only rows with a work email. |
| `requirePhone` | Keep only rows with a mobile number. |
| `requireAnyContact` | Keep only rows reachable by some route. |
| `workEmailStatuses` | Keep only these verification statuses. |
| `minCompanyHeadcount` | Drop rows whose company is smaller than this. |
| `jobName` | A label for the batch on your provider dashboard. |
| `reportCredits` | Log your remaining credits first. |
| `requestsPerMinute` | Pace the run under the provider rate limit. |
| `jobTimeoutMinutes` | Give up waiting for a batch after this long. |
| `skipNotFound` | Leave misses and refusals out of the dataset. |
| `maxResults` | Cap on rows, and on provider credits spent. |
| `apiKey` | Your own API key. Stored as a secret. |

### Example output

```json
{
  "found": true,
  "mode": "enrich",
  "query": "https://www.linkedin.com/in/example",
  "workEmail": "j.snow@example.com",
  "workEmailStatus": "DELIVERABLE",
  "personalEmail": "jsnow@gmail.com",
  "personalEmailStatus": "DELIVERABLE",
  "phone": "+1 555-123-4567",
  "phoneRegion": "US",
  "workEmails": ["j.snow@example.com", "john.snow@example.com"],
  "workEmailStatuses": ["DELIVERABLE", "CATCH_All"],
  "personalEmails": ["jsnow@gmail.com"],
  "phones": ["+1 555-123-4567"],
  "phoneRegions": ["US"],
  "firstName": "John",
  "lastName": "Snow",
  "fullName": "John Snow",
  "headline": "Head of Sales Operations at Example Inc",
  "jobTitle": "Head of Sales Operations",
  "jobStartedAt": "2022-03-15T00:00:00Z",
  "companyName": "Example Inc",
  "companyDomain": "example.com",
  "companyWebsite": "https://www.example.com",
  "companyHeadcount": 250,
  "companyFoundedYear": 2010,
  "companyType": "Privately Held",
  "companyIndustry": "Software Development",
  "companyLinkedinUrl": "https://www.linkedin.com/company/example-inc",
  "country": "United States",
  "countryCode": "US",
  "region": "California",
  "city": "San Francisco",
  "linkedinUrl": "https://www.linkedin.com/in/example",
  "skills": ["Sales Operations", "CRM Management"],
  "languages": ["English", "French"],
  "universities": ["Stanford University"],
  "custom": { "user_id": "12584" },
  "error": null,
  "raw": { }
}
```

### Frequently asked questions

#### What is waterfall enrichment and why does it find more?

Any single contact database has gaps, and they are not the same gaps. A waterfall asks one source, and if it comes back empty asks the next, and the next, until something answers or the list runs out. The hit rate is therefore the union of every source rather than the coverage of the best one. The cost of that is time: the run waits on a chain of upstream lookups instead of a single query, which is why enrichment here is a batch job you wait a few minutes for rather than an instant response.

#### What does an enrichment actually cost me?

On your own provider plan, one credit for a work email, three for a personal email and ten for a mobile number, charged when something is found. Asking for all three is fourteen credits a contact. That ratio is the thing worth knowing before you start, so the `enrichFields` input defaults to the work email alone and the run logs what it is about to look for. Separately, this actor charges per row written to the dataset.

#### What do I need to supply for an enrichment to work?

Either a professional network profile URL, or a first name and a last name together with a company domain or company name. A profile URL is markedly better: it raises the email hit rate by up to a fifth and the mobile number hit rate by up to three fifths, and it is what makes the full profile, job title, location, employer headcount and the rest come back with the result. Contacts carrying neither are refused before they are sent, so a half-filled CRM row costs you nothing.

#### What do the email statuses mean?

Deliverable means the address was verified and bounces about two percent of the time. High probability means the domain is catch-all so no server will confirm the individual mailbox, but the provider's own checks say it is likely real, at about nine percent. Catch-all is the same situation with less supporting evidence and a higher rate again. Invalid is very likely to bounce, and is already excluded from the best-address column. Filter on the status rather than assuming every returned address is equal.

#### What is reverse email lookup for?

Turning an address into a person. Sign-up forms, webinar lists and inbound demo requests often give you nothing but an email address and a first name, which is not enough to route, score or personalise anything. Reverse lookup returns the person and the company behind the address, so a list of raw addresses becomes a list of people with titles and employers.

#### How do the search filters combine?

Different filters are combined with each other: a job title filter and a headcount range both have to hold. Several values inside one filter are alternatives: three job titles means any of the three. A line beginning with a minus sign excludes that value instead of requiring it, which is how you say "at these two companies but not that subsidiary" in a single field. Numeric filters are ranges, and a range with neither end set is not sent at all.

#### When should I turn exact matching on?

For domains and profile URLs, always: an approximate match on a domain is meaningless. For company names and job titles, usually not. With it off the provider tolerates missing words, extra words and reordering, so "VP Sales" also finds "Vice President of Sales, EMEA". Matching is never case sensitive either way.

#### Why would I search rather than enrich?

They answer different questions and cost different amounts. Search finds people and companies you did not know about, from filters, and returns profiles without contact details. Enrichment takes people you have already identified and finds how to reach them. The normal pattern is to search first, look at what comes back, and enrich only the rows worth the credits.

#### How do I find people who recently changed jobs?

Set the maximum days since job change to ninety. Someone in their first quarter at a new company is still choosing tools, still has budget that has not been committed, and has not yet formed a preference. That filter is the reason the field exists, and it pairs well with a past employer filter when you want people who left a particular company.

#### What happens when a batch runs out of credits halfway?

This provider reports that as a job status rather than as an error, which is exactly how an abandoned batch gets recorded as a successful empty run in an integration that only reads HTTP status codes. This actor checks the job status, stops, and tells you the balance ran out, so you see the real reason rather than an empty dataset.

#### Do I need my own API key?

Yes. This actor does not include data access. You use your own key from FullEnrich, the provider whose contact enrichment API this actor calls, taken from the API page of your provider dashboard and pasted into the `apiKey` field, where it is stored as an Apify secret. Your own plan, credits and terms apply.

#### How much does a run cost?

Pricing is pay per result: you are charged for each record resolved into the dataset, and never for contacts the provider found nothing for, for inputs refused before they were sent, for rows dropped by the filters, or for duplicates. Apify platform usage is included in the per-result price. Your own provider credits are separate and billed by them.

### Keyword map

contact enrichment API, waterfall enrichment, email finder API, work email finder, personal email finder, mobile phone number finder, cell phone enrichment, B2B contact data API, lead enrichment API, reverse email lookup, email to person lookup, people search API, prospect search API, company search API, firmographic search, job title search, seniority filter, job change alerts, job change data, technographic prospecting, CRM enrichment, lead list building, sales prospecting API, email verification status, bounce rate reduction, GDPR compliant enrichment

# Actor input Schema

## `mode` (type: `string`):

Enrich finds contact details for people you already know of. Reverse lookup turns an email address back into the person and company behind it. The two searches find new people or companies from filters, and return profiles without contact details. The two lookups return the single best match for one identifier.

## `linkedinUrls` (type: `array`):

In enrich mode, the profiles to find contact details for, one per line. This is the highest-yielding input this provider takes: a profile URL raises the email hit rate by up to a fifth and the mobile number hit rate by up to three fifths against a name and company alone, and it is also what makes the full profile come back with the result.

## `contacts` (type: `array`):

In enrich mode, contacts given as fields rather than as a URL. Each entry is an object with first\_name, last\_name and either domain or company\_name, and optionally linkedin\_url and a custom object of your own reference values. Use this when you have CRM rows rather than profile links. Entries missing a name or a company are refused here rather than charged for.

## `emails` (type: `array`):

In reverse lookup mode, the addresses to identify, one per line. Each one comes back with the person and the company behind it, where the provider can resolve them.

## `enrichFields` (type: `array`):

In enrich mode, which contact details to look for. This is what a run costs on your own plan: a work email is 1 credit, a personal email 3, and a mobile number 10. Leaving this empty looks for the work email alone, because asking for all three costs fourteen times as much and that should be a decision rather than a default.

## `currentJobTitles` (type: `array`):

In people search mode, filter on current job titles. One value per line. Put a minus sign in front of a line to exclude it instead of requiring it. Job titles to match at the person current employer, for example Head of Sales or VP Engineering.

## `seniorityLevels` (type: `array`):

In people search mode, filter on how senior the person is in their current role. Several levels are combined as alternatives.

## `jobFunctions` (type: `array`):

In people search mode, filter on the broad function the person works in. Pair it with the sub function field for something narrower.

## `subFunctions` (type: `array`):

In people search mode, filter on job sub functions. One value per line. Put a minus sign in front of a line to exclude it instead of requiring it. The narrower function under the one above, for example Data Entry, Field Sales or Backend Engineering.

## `pastJobTitles` (type: `array`):

In people search mode, filter on past job titles. One value per line. Put a minus sign in front of a line to exclude it instead of requiring it. Titles the person held before their current role. This is how you find people who have done a job before, rather than people doing it now.

## `personNames` (type: `array`):

In people search mode, filter on person names. One value per line. Put a minus sign in front of a line to exclude it instead of requiring it. A first name, last name or full name.

## `personLinkedinUrls` (type: `array`):

In people search mode, filter on person profile urls. One value per line. Put a minus sign in front of a line to exclude it instead of requiring it. Professional network profile URLs for specific people.

## `personLocations` (type: `array`):

In people search mode, filter on person locations. One value per line. Put a minus sign in front of a line to exclude it instead of requiring it. A continent, country, region or city. The country goes in English and the region and city in the local language, which is how this provider stores them.

## `personLanguages` (type: `array`):

In people search mode, filter on languages spoken. One value per line. Put a minus sign in front of a line to exclude it instead of requiring it.

## `personSkills` (type: `array`):

In people search mode, filter on skills. One value per line. Put a minus sign in front of a line to exclude it instead of requiring it. Listed skills, for example Python, Kubernetes or Contract Negotiation.

## `personUniversities` (type: `array`):

In people search mode, filter on universities attended. One value per line. Put a minus sign in front of a line to exclude it instead of requiring it.

## `currentCompanyNames` (type: `array`):

In people search mode, filter on current company names. One value per line. Put a minus sign in front of a line to exclude it instead of requiring it.

## `currentCompanyDomains` (type: `array`):

In people search mode, filter on current company domains. One value per line. Put a minus sign in front of a line to exclude it instead of requiring it. Company website domains, for example example.com. Turn on exact matching for these.

## `currentCompanyLinkedinUrls` (type: `array`):

In people search mode, filter on current company profile urls. One value per line. Put a minus sign in front of a line to exclude it instead of requiring it.

## `currentCompanyIndustries` (type: `array`):

In people search mode, filter on current company industries. One value per line. Put a minus sign in front of a line to exclude it instead of requiring it. One of the provider industry names, for example Software Development or Hospitals and Health Care.

## `currentCompanySpecialties` (type: `array`):

In people search mode, filter on current company specialties. One value per line. Put a minus sign in front of a line to exclude it instead of requiring it.

## `currentCompanyTypes` (type: `array`):

In people search mode, filter on what kind of organisation the employer is.

## `currentCompanyHeadquarters` (type: `array`):

In people search mode, filter on current company headquarters. One value per line. Put a minus sign in front of a line to exclude it instead of requiring it. Where the employer is based, as a city, region or country.

## `pastCompanyNames` (type: `array`):

In people search mode, filter on past company names. One value per line. Put a minus sign in front of a line to exclude it instead of requiring it. Employers the person worked at before. Useful for alumni targeting.

## `pastCompanyDomains` (type: `array`):

In people search mode, filter on past company domains. One value per line. Put a minus sign in front of a line to exclude it instead of requiring it.

## `companyNames` (type: `array`):

In company search mode, filter on company names. One value per line. Put a minus sign in front of a line to exclude it instead of requiring it.

## `companyDomains` (type: `array`):

In company search mode, filter on company domains. One value per line. Put a minus sign in front of a line to exclude it instead of requiring it. Website domains, for example example.com. Turn on exact matching for these.

## `companyLinkedinUrls` (type: `array`):

In company search mode, filter on company profile urls. One value per line. Put a minus sign in front of a line to exclude it instead of requiring it.

## `companyKeywords` (type: `array`):

In company search mode, filter on description keywords. One value per line. Put a minus sign in front of a line to exclude it instead of requiring it. Words that appear in the company description. This is the filter that finds companies doing a thing rather than companies filed under a category.

## `companySpecialties` (type: `array`):

In company search mode, filter on company specialties. One value per line. Put a minus sign in front of a line to exclude it instead of requiring it.

## `companyIndustries` (type: `array`):

In company search mode, filter on company industries. One value per line. Put a minus sign in front of a line to exclude it instead of requiring it. One of the provider industry names, for example Software Development or Hospitals and Health Care.

## `companyHeadquarters` (type: `array`):

In company search mode, filter on headquarters locations. One value per line. Put a minus sign in front of a line to exclude it instead of requiring it. A city, region or country.

## `companyTypes` (type: `array`):

In company search mode, filter on what kind of organisation it is.

## `exactMatch` (type: `boolean`):

Require every text filter to match exactly rather than approximately. Matching is never case sensitive either way. Off is right for company and job titles, where the provider tolerates missing words, extra words and reordering. On is right for domains and profile URLs, where an approximate match is meaningless.

## `headcountMin` (type: `integer`):

Smallest employee count to include, on the current employer in people search and on the company itself in company search. Leave at zero for no lower bound.

## `headcountMax` (type: `integer`):

Largest employee count to include. Leave at zero for no upper bound.

## `foundedYearMin` (type: `integer`):

Earliest founding year to include. Set this alone to find younger companies.

## `foundedYearMax` (type: `integer`):

Latest founding year to include.

## `yearsInPositionMin` (type: `integer`):

People search only. Filter on how long the person has held their current title. A low maximum here finds people newly promoted into a buying role.

## `yearsInPositionMax` (type: `integer`):

People search only. Leave at zero for no upper bound.

## `yearsAtCompanyMin` (type: `integer`):

People search only. Tenure at the employer rather than in the role, which are different numbers for anyone promoted internally.

## `yearsAtCompanyMax` (type: `integer`):

People search only. Leave at zero for no upper bound.

## `daysSinceJobChangeMin` (type: `integer`):

People search only. Leave at zero for no lower bound.

## `daysSinceJobChangeMax` (type: `integer`):

People search only. Set this to ninety to find people who started somewhere new in the last quarter, which is the window in which they are still choosing their own tools.

## `requireWorkEmail` (type: `boolean`):

Drop enriched contacts where no work email was found. Note that a contact with a mobile number and no email is still a usable contact, so this is a narrower filter than it looks.

## `requirePhone` (type: `boolean`):

Drop enriched contacts where no mobile number was found. Only does anything when mobile numbers were among the details asked for.

## `requireAnyContact` (type: `boolean`):

Drop enriched contacts where nothing at all was found, keeping anyone reachable by any route. Usually the right switch rather than the two above.

## `workEmailStatuses` (type: `array`):

The provider verifies every address it returns and reports how confident it is. Deliverable bounces about two percent of the time and high probability about nine. Catch-all is higher again, and invalid is excluded from the best-address column already. Leave empty to keep every status.

## `minCompanyHeadcount` (type: `integer`):

Drop rows whose company is smaller than this, after the fact. Different from the headcount range above, which is asked of the provider: this one also applies to enrichment results, where there is no search filter to use.

## `jobName` (type: `string`):

A label for this batch on your provider dashboard, so you can find it later. A person name works well when enriching one contact.

## `reportCredits` (type: `boolean`):

Log how many credits your provider workspace has left before the run starts. Informational only, and a failure to read it does not stop the run.

## `requestsPerMinute` (type: `integer`):

Pace the run under the provider limit of 60 requests a minute across all endpoints. The default sits just under it rather than on it, because a burst that lands on the boundary is refused rather than queued.

## `jobTimeoutMinutes` (type: `integer`):

Give up waiting for one enrichment batch after this long. The batch keeps running at the provider and its results stay available there, so nothing is lost. Waterfall enrichment queries a series of upstream sources in turn, so a full batch genuinely takes minutes rather than seconds.

## `skipNotFound` (type: `boolean`):

Leave contacts the provider found nothing for, and inputs refused before they were sent, out of the dataset. Off by default, because knowing which of your contacts could not be resolved is usually the point. Misses are never charged for either way.

## `maxResults` (type: `integer`):

Stop after this many rows. Enrichment batches are submitted one at a time, so this caps what you spend on your own provider plan as well as what you spend here.

## `apiKey` (type: `string`):

Your own API key for the contact enrichment API, taken from the API page of your provider dashboard. Stored as a secret and never logged.

## Actor input object example

```json
{
  "mode": "enrich",
  "linkedinUrls": [],
  "contacts": [],
  "emails": [],
  "enrichFields": [
    "contact.work_emails"
  ],
  "currentJobTitles": [],
  "seniorityLevels": [],
  "jobFunctions": [],
  "subFunctions": [],
  "pastJobTitles": [],
  "personNames": [],
  "personLinkedinUrls": [],
  "personLocations": [],
  "personLanguages": [],
  "personSkills": [],
  "personUniversities": [],
  "currentCompanyNames": [],
  "currentCompanyDomains": [],
  "currentCompanyLinkedinUrls": [],
  "currentCompanyIndustries": [],
  "currentCompanySpecialties": [],
  "currentCompanyTypes": [],
  "currentCompanyHeadquarters": [],
  "pastCompanyNames": [],
  "pastCompanyDomains": [],
  "companyNames": [],
  "companyDomains": [],
  "companyLinkedinUrls": [],
  "companyKeywords": [],
  "companySpecialties": [],
  "companyIndustries": [],
  "companyHeadquarters": [],
  "companyTypes": [],
  "exactMatch": false,
  "headcountMin": 0,
  "headcountMax": 0,
  "foundedYearMin": 0,
  "foundedYearMax": 0,
  "yearsInPositionMin": 0,
  "yearsInPositionMax": 0,
  "yearsAtCompanyMin": 0,
  "yearsAtCompanyMax": 0,
  "daysSinceJobChangeMin": 0,
  "daysSinceJobChangeMax": 0,
  "requireWorkEmail": false,
  "requirePhone": false,
  "requireAnyContact": false,
  "workEmailStatuses": [],
  "minCompanyHeadcount": 0,
  "reportCredits": false,
  "requestsPerMinute": 55,
  "jobTimeoutMinutes": 20,
  "skipNotFound": false,
  "maxResults": 100
}
```

# Actor output Schema

## `results` (type: `string`):

One row per record, carrying the best work email with its verification status, the best personal email and mobile number, every alternative the waterfall found, the person name, headline, title and tenure, and the employer name, domain, headcount, founding year, type, industry and headquarters.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "linkedinUrls": [],
    "contacts": [],
    "emails": [],
    "currentJobTitles": [],
    "subFunctions": [],
    "pastJobTitles": [],
    "personNames": [],
    "personLinkedinUrls": [],
    "personLocations": [],
    "personLanguages": [],
    "personSkills": [],
    "personUniversities": [],
    "currentCompanyNames": [],
    "currentCompanyDomains": [],
    "currentCompanyLinkedinUrls": [],
    "currentCompanyIndustries": [],
    "currentCompanySpecialties": [],
    "currentCompanyHeadquarters": [],
    "pastCompanyNames": [],
    "pastCompanyDomains": [],
    "companyNames": [],
    "companyDomains": [],
    "companyLinkedinUrls": [],
    "companyKeywords": [],
    "companySpecialties": [],
    "companyIndustries": [],
    "companyHeadquarters": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("nabeelbaghoor/waterfall-contact-enrichment-api").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "linkedinUrls": [],
    "contacts": [],
    "emails": [],
    "currentJobTitles": [],
    "subFunctions": [],
    "pastJobTitles": [],
    "personNames": [],
    "personLinkedinUrls": [],
    "personLocations": [],
    "personLanguages": [],
    "personSkills": [],
    "personUniversities": [],
    "currentCompanyNames": [],
    "currentCompanyDomains": [],
    "currentCompanyLinkedinUrls": [],
    "currentCompanyIndustries": [],
    "currentCompanySpecialties": [],
    "currentCompanyHeadquarters": [],
    "pastCompanyNames": [],
    "pastCompanyDomains": [],
    "companyNames": [],
    "companyDomains": [],
    "companyLinkedinUrls": [],
    "companyKeywords": [],
    "companySpecialties": [],
    "companyIndustries": [],
    "companyHeadquarters": [],
}

# Run the Actor and wait for it to finish
run = client.actor("nabeelbaghoor/waterfall-contact-enrichment-api").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "linkedinUrls": [],
  "contacts": [],
  "emails": [],
  "currentJobTitles": [],
  "subFunctions": [],
  "pastJobTitles": [],
  "personNames": [],
  "personLinkedinUrls": [],
  "personLocations": [],
  "personLanguages": [],
  "personSkills": [],
  "personUniversities": [],
  "currentCompanyNames": [],
  "currentCompanyDomains": [],
  "currentCompanyLinkedinUrls": [],
  "currentCompanyIndustries": [],
  "currentCompanySpecialties": [],
  "currentCompanyHeadquarters": [],
  "pastCompanyNames": [],
  "pastCompanyDomains": [],
  "companyNames": [],
  "companyDomains": [],
  "companyLinkedinUrls": [],
  "companyKeywords": [],
  "companySpecialties": [],
  "companyIndustries": [],
  "companyHeadquarters": []
}' |
apify call nabeelbaghoor/waterfall-contact-enrichment-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,nabeelbaghoor/waterfall-contact-enrichment-api"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/FQr0XOT6dS60iuhxK/builds/XCjf8qHHnJ2eNga60/openapi.json
