# LinkedIn Decision Maker Email Finder (`thenetaji/linkedin-decision-maker-scraper`) Actor

Find a company's C-level, VP, Head, and Director staff and a work email for each. Everyone is bound to a current role there — ex-employees and name-drops are dropped, not shipped. Email formats are derived from addresses the company itself publishes, and that evidence ships with every row.

- **URL**: https://apify.com/thenetaji/linkedin-decision-maker-scraper.md
- **Developed by:** [The Netaji](https://apify.com/thenetaji) (community)
- **Categories:** Lead generation, Automation
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 decision makers

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## LinkedIn Decision Maker Email Finder

The Actor finds a company's C-level, VP, Head, and Director staff and proposes a work email address for each. A person is included only when their headline binds them to a *current* role at the target company; ex-employees, unrelated name-drops, and similarly named companies are excluded rather than returned and left for the buyer to filter. No LinkedIn account or session cookie is required.

Email addresses are not looked up from a directory. The Actor learns the company's address format from addresses the company has already published — commit metadata on its public GitHub repositories, or its own site — and proposes each person's address by applying that format to their name. The real addresses the format was learned from ship with the result, so the format can be checked rather than trusted.

### Accepted input

| Field | Type | Default | Description |
|---|---|---|---|
| `target_company` | array of strings | `["Stripe"]` | Required. One line per company. A name, a LinkedIn company URL, or a website — all work. |
| `verify_emails` | boolean | `false` | Confirm each address against the company's mail server. |
| `search_depth` | integer | `3` | How many batches of seniority titles to sweep, `1`–`5`. Each batch adds meaningful run time per company, so a higher value trades speed for a fuller roster. |
| `max_candidates` | integer | `3` | How many ranked candidate addresses to return per person, `1`–`5`. |
| `maxItems` | integer | `100` | Maximum decision-makers saved across all companies. `0` removes the limit. |
| `email_domain` | array of strings | `[]` | Override. Leave empty unless the company mails from a domain other than its website. |
| `github_org` | array of strings | `[]` | Override. Leave empty unless the company publishes code under an unrelated name. |

```json
{
  "target_company": ["Northwind Robotics"],
  "search_depth": 3,
  "verify_emails": true
}
```

That is the whole input. The company's email domain and its GitHub organisation are worked out from the line you typed, and every row reports the domain that was used, so a wrong one is visible rather than silent. The two override fields exist for the rare company those lookups get wrong; supply one against several companies and it applies to all of them, or match them by position.

### Response fields

One record per decision-maker.

| Field | Contents |
|---|---|
| `target_company` | Company this person was found at |
| `company_domain` | Email domain used to resolve addresses for this company |
| `name` | Name shown on the related LinkedIn profile |
| `headline` | Current headline or organization text on the profile |
| `seniority` | `c_suite`, `vp`, `head`, or `director`, read from the headline |
| `department` | Function read from the headline, e.g. `sales`, `engineering`, `finance` |
| `profile_url` | Public LinkedIn profile URL |
| `matched_title` | The seniority title whose search matched this person |
| `email` | Best-match work email, or `null` when no address could be proposed |
| `email_confidence` | `0`–`1`, how much to trust this specific address |
| `email_status` | `best_match`, `guess`, or `not_found` |
| `email_candidates` | Ranked alternate addresses, each with its own format rule and confidence |
| `email_pattern` | The company's email format, e.g. `first.last` |
| `email_pattern_rule` | The full rule, including how surnames, hyphens, and accented characters are handled |
| `email_pattern_evidence` | Real addresses at this company the format was derived from |
| `discovery_source` | Which search index surfaced this profile |

```json
{
  "target_company": "Northwind Robotics",
  "company_domain": "northwindrobotics.com",
  "name": "Priya Shah",
  "headline": "VP of Engineering at Northwind Robotics",
  "seniority": "vp",
  "department": "engineering",
  "profile_url": "https://www.linkedin.com/in/priyashah",
  "matched_title": "VP",
  "email": "priya.shah@northwindrobotics.com",
  "email_confidence": 0.94,
  "email_status": "best_match",
  "email_pattern": "first.last",
  "email_pattern_rule": "first.last (surnames=all, given_hyphen=collapse, family_hyphen=collapse, diacritics=ascii)",
  "email_pattern_evidence": ["alex.chen@northwindrobotics.com", "morgan.lee@northwindrobotics.com"],
  "discovery_source": "brave"
}
```

### Confirming a current role

A search snippet naming the target company is not proof of employment there. Checked against a naive version of that same check on a live run, 255 rows carried the company's name, and 230 of them were not that company's staff: 213 were unrelated mentions with no employment relationship, 11 named a former employer, and 6 belonged to a different company that happened to share the same leading word. All 230 are excluded here rather than shipped and left for the buyer to sort out; only headlines that bind the person to a current role at the named company are kept.

`search_depth` widens the search by seniority band rather than by page depth. At the default of `3`, the Actor sweeps chief-officer titles, then revenue-adjacent leadership, then VP-level titles. `4` adds Head and Director titles; `5` adds Founder, President, and General Manager. A run returning strong C-level and VP coverage but no Head or Director rows has not exhausted the company — it has not yet swept the batch those titles are in.

### How the email address is proposed

The Actor never assumes a format such as `first.last`. It first collects confirmed `(name, email)` pairs the company has already published — public commit authorship on its GitHub org, and addresses listed on its own site — then learns which format explains the largest share of that confirmed set. Coverage of that free ground truth is uneven and sector-dependent: it is common for software companies and for firms that publish staff directories, and it can be entirely absent for others. Where it is absent, the Actor still ranks candidate formats by how companies in general tend to render names and returns those, at lower confidence and `email_status: "guess"`.

Confidence is computed per person, not per company. A domain-wide "this company uses `first.last`" says nothing about how that company renders a compound surname, a hyphenated given name, or an accented character — those are learned separately, so two people at the same company with the same format can carry different confidence. Where the confirmed sample is small or splits across more than one format, the Actor reports that as low confidence and a `guess` status rather than defaulting to a single assumed pattern; the alternative — reporting one address with unearned certainty — is the failure mode this field exists to avoid.

### Verified addresses

With `verify_emails` enabled, an address is returned as `email_status: "verified"` only when the mailbox is confirmed to exist.

Some company domains accept mail addressed to anything, so no service can confirm a mailbox there. The Actor detects this before spending anything, reports it on each row as `domain_verifiable`, and does not charge the verified add-on for those companies. About four domains in ten behave this way.

Without `verify_emails`, addresses are best matches derived from the company's format and are not checked against the mail server.

The pattern add-on that resolves a company's format is charged once per company, and only when a format is actually derived. A company where no confirmed address could be found, or where GitHub and site harvesting turn up nothing, is not charged for pattern derivation — the decision-maker rows it does yield are billed on their own.

### Frequently asked questions

**Are the returned email addresses verified?**
No. Each is the best-match address for a format learned from the company's own published addresses, applied to the person's name. `email_pattern_evidence` carries the real addresses that format was derived from, so the rule behind a proposal can be checked directly instead of taken on trust.

**Why does `email_confidence` differ between two people at the same company?**
The company-level format is one thing; rendering a specific name into that format is another. Compound surnames, particles (`van`, `de`, `von`), hyphenated names, and accented characters are each handled by a rule learned separately, so a straightforward name can score higher than an unusual one even though both use the same base pattern.

**What does `email_status: "guess"` mean?**
The proposed address came from ranking candidate formats generally, not from a format confirmed against this company's own published addresses. This happens when the company has too little free ground truth — no useful GitHub history and nothing published on its own site — to learn a company-specific rule from.

**What happens when a company yields no decision-makers?**
The run completes, logs the outcome for that company, and moves to the next one. Nothing is charged for a company that returns no rows.

**Do I need to supply `email_domain` and `github_org` for every company?**
Only `target_company` is required. A single `email_domain` or `github_org` is applied to every company in the run; supply one per company to pair them positionally instead. Leaving `email_domain` empty for a company returns its people without attempting to resolve email addresses at all.

**Is a LinkedIn account or session cookie required?**
No. Only public profile headlines and search results are read.

### Related Actors

| Actor | Purpose |
|---|---|
| [LinkedIn Company Scraper](https://apify.com/thenetaji/linkedin-company-scraper) | Company page details — industry, size, headquarters — by handle or URL |
| [LinkedIn Profile Scraper](https://apify.com/thenetaji/linkedin-profile-scraper) | Full profile details for a known username or URL |
| [LinkedIn Related User Scraper](https://apify.com/thenetaji/linkedin-related-user-scraper) | Profiles LinkedIn shows alongside a seed profile |
| [LinkedIn Jobs Scraper](https://apify.com/thenetaji/linkedin-jobs-scraper) | Job search by keyword and location |

# Actor input Schema

## `target_company` (type: `array`):

One line per company. A name (Stripe), a LinkedIn company URL, or a website (stripe.com) — all work. The email domain and everything else is worked out from this.

## `seniority` (type: `array`):

Which levels to look for. Each one adds its own titles to the search, so picking fewer is faster and cheaper. Partner and General Counsel sit under C-suite -- that is where a law firm, an agency or a fund keeps the person who signs.

## `verify_emails` (type: `boolean`):

Confirm each address against the company's mail server before returning it. Around 40% of company domains accept every address (catch-all), where no mailbox can be confirmed by anyone — those are reported as unverifiable rather than guessed at, and are never charged for.

## `alternate_emails` (type: `boolean`):

Return the runner-up addresses alongside the best one, each with the format rule and confidence behind it. Off by default: one address per person is the answer, and a column of near-identical alternates next to it reads as uncertainty rather than as options. Turn it on when you intend to try the alternates after a bounce.

## `maxItems` (type: `integer`):

Maximum number of decision-makers to save across all companies. Set 0 for no limit.

## Actor input object example

```json
{
  "target_company": [
    "https://www.linkedin.com/company/datadog/"
  ],
  "seniority": [
    "c_suite",
    "vp"
  ],
  "verify_emails": false,
  "alternate_emails": false,
  "maxItems": 20
}
```

# Actor output Schema

## `dataset` (type: `string`):

All records scraped by this run

# 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 = {
    "target_company": [
        "https://www.linkedin.com/company/datadog/"
    ],
    "seniority": [
        "c_suite",
        "vp"
    ],
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("thenetaji/linkedin-decision-maker-scraper").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 = {
    "target_company": ["https://www.linkedin.com/company/datadog/"],
    "seniority": [
        "c_suite",
        "vp",
    ],
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("thenetaji/linkedin-decision-maker-scraper").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 '{
  "target_company": [
    "https://www.linkedin.com/company/datadog/"
  ],
  "seniority": [
    "c_suite",
    "vp"
  ],
  "maxItems": 20
}' |
apify call thenetaji/linkedin-decision-maker-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,thenetaji/linkedin-decision-maker-scraper"
        }
    }
}

```

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/ZMaqOvy8NNevofH7X/builds/aOcw00F8csnFwqaed/openapi.json
