# Decision-Maker Contacts - Founders, CEOs, Execs from Sites (`tiger19935/decision-maker-contacts`) Actor

Find founders, CEOs and department heads on company websites: names, titles, seniority, public profile links and emails from team, about and imprint pages. Pattern-guessed emails are clearly flagged. No LinkedIn scraping. Pay per contact.

- **URL**: https://apify.com/tiger19935/decision-maker-contacts.md
- **Developed by:** [Andrew Boiko](https://apify.com/tiger19935) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.20 / 1,000 decision-maker contacts

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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Decision-Maker Contacts - Founders, CEOs and Executives from Company Websites

Paste a list of company websites and get the **founders, owners, C-level executives, VPs, heads and directors** that each company publishes on its own site: **name, job title, seniority, department, public profile links, and an email address** (found on the page, or a clearly flagged pattern-based guess). One row per person.

Only the company's own public pages are read (team, about, leadership, management, contact and imprint/Impressum pages). **No LinkedIn pages are visited, scraped or logged into.** If a company links to a person's LinkedIn profile on its own site, that link is returned as a profile URL.

### Who is it for

- Sales and outbound teams that need a named person to contact at a target company.
- Recruiters and investors mapping leadership teams.
- Agencies and freelancers building prospect lists from a list of domains.
- Analysts who want a structured view of who runs which company.

### What you get

| Field | Meaning |
|---|---|
| `personName`, `firstName`, `lastName` | Name as published on the page |
| `title` | Job title as published (any language) |
| `seniority` | `founder`, `owner`, `c-level`, `president`, `board`, `partner`, `vp`, `head`, `director`, `manager`, `staff` |
| `department` | `executive`, `technology`, `marketing`, `sales`, `finance`, `operations`, `hr`, `legal`, `product`, `design`, `support`, `other` |
| `email` | An address published on the site and matched to this person (profile card, structured data, or the name appears in the address) |
| `emailGuess`, `emailIsGuess`, `emailSource`, `emailBasis` | A guess built from the company's observed address pattern. **Always flagged**: `emailIsGuess: true`, `emailSource: "pattern-guess"`, with the evidence in `emailBasis`. Never presented as a verified mailbox |
| `emailStatus` | `verified_mx` (domain can receive mail), `unverified` (domain has no mail server), `none` |
| `companyEmails` | Generic company addresses seen on the site (info@, sales@ ...) |
| `linkedinUrl`, `profileLinks` | Personal LinkedIn/Xing/X/GitHub profile links that the site itself shows next to the person |
| `sourceUrl`, `sourceType` | The page and extraction method (`jsonld`, `microdata`, `card`, `line`, `imprint`) |
| `confidence` | 5-97 heuristic score (structured data scores highest; see below) |

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `domains` | array | required | Domains or any URL on the company site. Up to 2000. |
| `roles` | array | founder, ceo, cto, cfo, coo, cmo, owner, president, vp, head of, director, partner, managing | Keep only people whose title contains one of these words. Understands abbreviations and translations (ceo = chief executive officer = Geschäftsführer). |
| `maxPeoplePerDomain` | integer | 10 | 1-100. Most senior people first. |
| `findEmails` | boolean | true | Match published emails to people and guess the rest from the learned pattern. |
| `includeFailed` | boolean | false | Add free `rowType: "status"` rows for domains that were unreachable, blocked or empty. |

Role keywords are matched as whole words, so `president` also matches "Vice President". Use `vp` or `head of` for narrower lists.

### Output example

```json
{
  "rowType": "contact",
  "id": "acme.com|jane-doe",
  "domain": "acme.com",
  "companyName": "Acme Corp",
  "personName": "Jane Doe",
  "firstName": "Jane",
  "lastName": "Doe",
  "title": "Chief Executive Officer",
  "seniority": "c-level",
  "department": "executive",
  "email": "jane.doe@acme.com",
  "emailGuess": null,
  "emailIsGuess": false,
  "emailSource": "page",
  "emailStatus": "verified_mx",
  "emailBasis": null,
  "companyEmails": ["info@acme.com"],
  "linkedinUrl": "https://www.linkedin.com/in/janedoe/",
  "profileLinks": ["https://www.linkedin.com/in/janedoe/"],
  "sourceUrl": "https://acme.com/team",
  "sourceType": "card",
  "confidence": 87,
  "source": "Company website (public pages)",
  "scrapedAt": "2026-10-14T09:30:00.000Z"
}
```

Each run also writes `OUTPUT` (per-domain status, counts, skipped reasons, charged events, `stoppedReason`) and `RESULTS_MD` to the key-value store.

### Pricing

Pay only for results (pay per event). No monthly rental.

| Event | Price | When |
|---|---|---|
| `contact` | $0.004 | One person with a name and a job title found on the company's own site, after your role filter. |

1,000 contacts cost $4. Apify adds volume discounts on paid plan tiers (Bronze, Silver, Gold), and free-plan users can try it with the free monthly credit. You are never charged for unreachable or blocked domains, domains with no matching people, duplicates, failed rows, or anything beyond the maximum cost per run you set.

### How it works

1. For each domain the actor reads the homepage, then up to 5 more pages chosen by link text and path (team, our-team, leadership, management, people, founders, about, company, who we are, imprint/Impressum, contact, plus translations) and hints from `/sitemap.xml`. If the navigation gives little, common paths such as `/team` and `/about` are tried.
2. People are extracted from JSON-LD (`Person`, `founder`, `employee`), schema.org microdata, repeated team-card layouts (name next to a title), "Name, Title" lines and imprint lines such as "Geschäftsführer: ..." (German, English, French, Spanish, Italian and Ukrainian basics).
3. False positives are filtered hard: navigation labels, product names and sentences are rejected; a person needs a plausible name (two to four capitalised words) **and** a recognisable job title.
4. Titles are classified into seniority and department and matched against your role keywords.
5. Emails on the pages are matched to people. If the address format of the company can be learned (for example `first.last@`), other people get a flagged guess. Domains are MX-checked.
6. Progress is saved so a restarted run never charges or outputs the same person twice.

### Limits and honest caveats

- **Only what the company publishes.** Small companies often list nobody; many sites build team pages with JavaScript that a plain page fetch cannot see. Such domains return no rows and are not charged.
- Names and titles come from page text and can be outdated. `confidence` is a heuristic, not a probability.
- **Emails: found addresses are published ones; guesses are guesses.** A pattern guess has not been checked against the mail server, and we do not probe mailboxes (no SMTP). `verified_mx` only says that the domain can receive mail.
- Latin and Cyrillic names are supported. Names in Chinese, Japanese, Arabic and other scripts are not extracted yet.
- Titles are understood in English, German, French, Spanish, Italian and Ukrainian basics; other languages may be missed.
- No LinkedIn, Xing or social network scraping, no data from paid databases.

### Legal and ethical notes

- Only public pages are read, politely: robots.txt is respected, one request at a time per site with a pause, small page limits, honest user agent.
- Names, titles and business emails of identifiable people are personal data under GDPR and similar laws, even when public. You are responsible for a lawful basis (for example legitimate interest with a clear opt-out), purpose limitation, data minimisation and honouring removal requests. Follow anti-spam rules (CAN-SPAM, GDPR/PECR, CASL) when you write to people.
- Do not use guessed addresses for bulk mailing without verification. Bounces damage your sender reputation.
- Respect the terms of the websites you process.

### FAQ

**Does it scrape LinkedIn?** No. It never requests LinkedIn. A LinkedIn URL appears only when the company's own page links to it.

**Why did a domain return nothing?** The site may list no people, hide them behind JavaScript, require a login, or block robots. Check `OUTPUT.domains` or enable `includeFailed`.

**Are emails verified?** Found emails are published ones and are MX-checked at domain level. Guesses are flagged with `emailIsGuess` and are not verified mailboxes.

**Can I search by role only (no domains)?** No. Provide company domains; the actor is not a people search engine.

**Which titles are included by default?** Founder, CEO, CTO, CFO, COO, CMO, owner, president, VP, head of, director, partner, managing and equivalents such as Geschäftsführer or Gérant. Change `roles` to include engineers, managers or any other keyword.

**Why do I see the same person only once?** People are de-duplicated per domain by name; the most reliable source (structured data, then team cards, then lines, then imprint) provides the title.

**How are costs controlled?** Set a maximum cost per run in Apify. The actor stops softly when it is reached and records `stoppedReason: "budget"`.

### Use it via API

Replace `YOUR_USERNAME`; the actor ID is `YOUR_USERNAME~decision-maker-contacts`.

```bash
curl -X POST "https://api.apify.com/v2/acts/YOUR_USERNAME~decision-maker-contacts/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"domains":["acme.com","example.org"],"roles":["ceo","founder","head of marketing"],"maxPeoplePerDomain":5}'
```

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('YOUR_USERNAME/decision-maker-contacts').call({
    domains: ['acme.com', 'https://www.example.org/about'],
    maxPeoplePerDomain: 5,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.filter((p) => p.email));
```

```python
from apify_client import ApifyClient

client = ApifyClient("APIFY_TOKEN")
run = client.actor("YOUR_USERNAME/decision-maker-contacts").call(run_input={
    "domains": ["acme.com", "example.org"],
    "roles": ["founder", "ceo"],
})
for p in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(p["personName"], p["title"], p["email"] or p["emailGuess"])
```

### Integrations

Works with Make, n8n, Zapier, Google Sheets, Airtable, Slack and webhooks via the standard Apify integrations. A common flow: a company list in a sheet -> run this actor -> keep rows with `emailStatus = verified_mx` and `emailIsGuess = false` -> push to your CRM. Exports: JSON, CSV, Excel, XML.

### Support

If a site layout is not recognised, open an issue on the actor page with the domain and the people you expected to see.

### More tools by the same author

- [Website Contact Extractor](https://apify.com/tiger19935/website-contact-extractor): Emails, phones and social links from any list of websites
- [B2B Email Verifier](https://apify.com/tiger19935/b2b-email-verifier): Verify business emails and guess address patterns
- [Domain to Company Enrichment](https://apify.com/tiger19935/domain-company-enrichment): Turn domains into company profiles
- [Hiring Signals Monitor](https://apify.com/tiger19935/hiring-signals-monitor): Track which companies are hiring: new jobs and hiring surges

# Actor input Schema

## `domains` (type: `array`):

One company website per row: a domain (acme.com) or any URL on the site (https://www.acme.com/about). The actor reads the homepage and up to 5 more pages such as team, about, leadership, imprint (Impressum), contact. Up to 2000 domains per run.

## `roles` (type: `array`):

Only people whose job title contains at least one of these keywords are returned. Matching is case-insensitive on whole words and understands common abbreviations and translations (ceo = chief executive officer = Geschäftsführer). Examples: founder, cto, head of, vp, director, owner, partner, managing, marketing, sales, engineering. Leave empty to use the default decision-maker list.

## `maxPeoplePerDomain` (type: `integer`):

Maximum number of people returned (and charged) for each domain, 1-100. The most senior people are returned first (founders, owners, C-level, then presidents, VPs, heads and directors).

## `findEmails` (type: `boolean`):

When enabled, emails published on the pages are matched to people (inside their profile card, in structured data, or by name in the address). If the company's address format can be learned from at least one personal address, the remaining people get a pattern-based guess in the field emailGuess (emailIsGuess = true, emailSource = pattern-guess). Guesses are never verified mailboxes; the domain's MX record is checked only.

## `includeFailed` (type: `boolean`):

When enabled, a free row (rowType 'status', never charged) is added for each domain that could not be read or had no matching people. The OUTPUT record in the key-value store lists every domain's status either way.

## Actor input object example

```json
{
  "domains": [
    "basecamp.com"
  ],
  "roles": [
    "founder",
    "ceo",
    "cto",
    "cfo",
    "coo",
    "cmo",
    "owner",
    "president",
    "vp",
    "head of",
    "director",
    "partner",
    "managing"
  ],
  "maxPeoplePerDomain": 3,
  "findEmails": true,
  "includeFailed": false
}
```

# Actor output Schema

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

All result rows.

## `summary` (type: `string`):

Counts, charged events and warnings.

# 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 = {
    "domains": [
        "basecamp.com"
    ],
    "roles": [
        "founder",
        "ceo",
        "cto",
        "cfo",
        "coo",
        "cmo",
        "owner",
        "president",
        "vp",
        "head of",
        "director",
        "partner",
        "managing"
    ],
    "maxPeoplePerDomain": 3
};

// Run the Actor and wait for it to finish
const run = await client.actor("tiger19935/decision-maker-contacts").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 = {
    "domains": ["basecamp.com"],
    "roles": [
        "founder",
        "ceo",
        "cto",
        "cfo",
        "coo",
        "cmo",
        "owner",
        "president",
        "vp",
        "head of",
        "director",
        "partner",
        "managing",
    ],
    "maxPeoplePerDomain": 3,
}

# Run the Actor and wait for it to finish
run = client.actor("tiger19935/decision-maker-contacts").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 '{
  "domains": [
    "basecamp.com"
  ],
  "roles": [
    "founder",
    "ceo",
    "cto",
    "cfo",
    "coo",
    "cmo",
    "owner",
    "president",
    "vp",
    "head of",
    "director",
    "partner",
    "managing"
  ],
  "maxPeoplePerDomain": 3
}' |
apify call tiger19935/decision-maker-contacts --silent --output-dataset

```

## MCP server setup

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

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/vhyXGAzngQoj19f6l/builds/90aonLyldhF9ONkZ7/openapi.json
