# Employee List by Company Domain (`b2bdata/people-at-company`) Actor

Employee list by company domain: give company domains, get one deduplicated person row per employee with title, seniority, location, profile URL and email flags. Filter by seniority. Free plan: 100 domains per run. Pay per person delivered, empty lookups free. No login or cookies.

- **URL**: https://apify.com/b2bdata/people-at-company.md
- **Developed by:** [Open Enrich](https://apify.com/b2bdata) (community)
- **Categories:** Lead generation, Marketing
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.40 / 1,000 person delivereds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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

## Employee List by Company Domain

Employee list by company domain: give a list of company domains, get one row per person found at each company, with title, seniority, location, profile URL and flags for whether an email is on file. A person with several positions is one row, not several. No login, no cookies, no start fee. You pay only for a person row you receive.

**No login | Domains, URLs or emails in | One person per row | Filter by seniority | Per-domain cap so one big company cannot run away | Unknown domains = free | JSON/CSV export**

> **Domains in, people out.** Many company-employee tools start from a company name or a LinkedIn company URL. This one starts from the domain you already hold in a CRM or a target list, and can keep only the seniority levels you choose. It does not return email addresses, only whether one is on file. For emails chain it with [email-finder](https://apify.com/b2bdata/email-finder).

[See all actors by b2bdata](https://apify.com/b2bdata)

***

### 💳 Pricing and plans

One event, `person`, charged once per person row delivered. Prices per 1,000 people (set on the Pricing tab):

| Event | When it is charged | Free plan | Gold plan |
|---|---|---|---|
| `person` | A person row is delivered to your dataset | $1.896 | $1.396 |

Bronze and Silver prices are on the Pricing tab. Higher Apify plans only lower the price. Empty runs, unknown domains, domains with nobody on file, failed lookups, unreadable entries and notices are free, and there is no start fee.

**Plan-credit math.** The free plan's $5 monthly credit covers about 2,630 people at the Free price.

#### Success-only billing

You are charged only for a person row you receive. A domain with no company on file, or with nobody matching your filter, gets one free notice row (`reason: "unknown_domain"` or `"no_people"`). A hosting or platform domain shared by too many companies gets a free notice (`domain_too_broad`). An entry that is not a domain, a repeat, or a lookup that fails is listed in `MISSES` with its position and a reason and costs nothing. A second position of a person you already received is skipped and never charged.

#### The free plan

Free Apify accounts get up to 100 domains looked up per run, and as many runs as their Apify credit covers. Nothing is stored on our side. When a list is longer, the first 100 valid domains are looked up and one free notice row (`reason: "free_plan_limit"`) says how many were not looked up; they are not charged. Run again with the rest, or use a paid Apify plan for no per-run limit.

#### Keep a run small while you test

Two caps protect you. `maxPeoplePerDomain` (default 100, maximum 1,000) stops any one company from using up the run, and a large employer can have tens of thousands of people on file. `limit` (default 100, maximum 10,000) caps the whole run. Try one domain with `limit: 5`, read the rows, then scale up.

***

### ⚡ Why this employee list actor?

- **Start from the domain.** `stripe.com`, `https://www.stripe.com/about` and `jane@stripe.com` all become `stripe.com`. No company-name matching and no LinkedIn URL needed.
- **Choose the seniority.** `titleRoles` keeps only cxo, vp, director, manager, founder or other, so a list of VPs and C-levels is one run.
- **One row per person.** The data holds one record per position. A person with two current positions is delivered once, under the first one found.
- **Caps you control.** Per domain and per run, so cost is known before you start.
- **Success-only, no start fee.** Unknown domains, bad entries, repeats and notices are free.

***

### 🎯 Use cases: list the people behind a target list

- **Account mapping.** For each account domain, get the people to research.
- **Decision-maker lists.** Run a list of domains with `titleRoles: ["cxo", "vp"]`.
- **CRM gap fill.** Find who works at accounts you only hold a domain for.
- **Agent workflows.** An AI agent holds company domains and needs the people at each one in a single call.

***

### 🚀 Quick start: employees for a list of domains

#### 1. One domain

```json
{ "items": ["stripe.com"], "limit": 5 }
```

#### 2. Leaders only, across a list

```json
{ "items": ["openai.com", "https://www.microsoft.com/en-us/about", "shopify.com"], "titleRoles": ["cxo", "vp"], "maxPeoplePerDomain": 50, "limit": 150 }
```

Each entry becomes its bare domain before the lookup.

***

### ⚙️ Inputs

| Key | Type | Default | What it does |
|---|---|---|---|
| `items` | array of strings | required | Company domains, website URLs or email addresses. A company name is not a domain and is listed in `MISSES` as `invalid_domain`. The same domain written twice is looked up once. Free plan: 100 domains per run. |
| `titleRoles` | array of strings | all | Keep only these seniority levels: `cxo`, `vp`, `director`, `manager`, `founder`, `other`. Any other value stops the run with a message. |
| `maxPeoplePerDomain` | integer | 100 | Most people delivered for one domain. Maximum 1,000. |
| `limit` | integer | 100 | Most people delivered in the whole run, which also caps what is charged. Maximum 10,000. Domains not reached are listed in `MISSES`. |
| `includeAudit` | boolean | false | Adds an `audit` object to each row. |

Any other field stops the run before a lookup with `remove unsupported fields: ...`. Nothing is charged.

***

### 📤 Output

One result row per person. Rows for one domain stay together, domains keep input order. Empty values are left out.

| Key | Type | Meaning, and when it is missing |
|---|---|---|
| `companyDomain` | string | The domain from your entry, cleaned |
| `fullName`, `firstName`, `lastName` | string | The person's name |
| `jobTitle` | string | Title of the position this row comes from |
| `seniority` | string | `cxo`, `vp`, `director`, `manager`, `founder` or `other`, grouped from the title |
| `headline` | string | The profile headline. Missing when the person has none |
| `location` | string | Place on the profile |
| `country` | string | Two-letter country code |
| `startYear` | integer | Year the person started the position. Missing when it is not on file |
| `connections` | integer | Connection count on the profile |
| `hasWorkEmail` | boolean | An address on another domain is on record. Missing when that could not be checked |
| `hasPersonalEmail` | boolean | A personal mailbox is on record. Missing when that could not be checked |
| `lowSignal` | boolean | `true` for a thin profile: under 10 connections and no start year. These sort last |
| `profileUrl` | string | Public profile URL |
| `slug` | string | Profile slug |
| `updatedAt` | string | When the profile was last refreshed in the data |
| `audit` | object | Only with `includeAudit`: `requestId`, `latencyMs`, `inputRaw` |

Example output (the shape is real, the values are illustrative):

```json
{
  "companyDomain": "stripe.com",
  "fullName": "Ann Example",
  "firstName": "Ann",
  "lastName": "Example",
  "jobTitle": "VP Engineering",
  "seniority": "vp",
  "headline": "Engineering leader",
  "location": "Dublin, Ireland",
  "country": "IE",
  "startYear": 2021,
  "connections": 1523,
  "hasWorkEmail": true,
  "hasPersonalEmail": false,
  "lowSignal": false,
  "profileUrl": "https://linkedin.com/in/ann-example",
  "slug": "ann-example",
  "updatedAt": "2025-01-02T03:04:05Z"
}
```

#### How to read the rows

- **Who is on the list.** People the data holds as currently working at the companies that declare the domain. A domain can declare more than one company, and all of them are included. It is not the company's own staff list.
- **Seniority is grouped from the title.** A title such as "President" can sit in `other`, and an executive assistant can sit in `cxo`. Check the `jobTitle` before you act on a level.
- **Order and caps.** Ordinary profiles come first, thin ones (`lowSignal`) last, grouped by seniority level inside each. With a cap you receive the first rows in that order. Use `titleRoles` to choose the levels you want.
- **Several positions.** A person is delivered once. If the same person is found at two domains in your list, the row stays with the first domain.
- **Email flags are not emails.** `hasWorkEmail: true` means an address is on record, not that we return it.

#### Other records in the run

- **`MISSES`** (key-value store): `[{index, input, reason}]` for every entry without a result row. Reasons: `unknown_domain`, `no_people`, `domain_too_broad`, `invalid_domain`, `duplicate_input` (the same domain as an earlier entry; `duplicateOf` names it), `not_looked_up_limit` (the run `limit` was reached first), or `Upstream HTTP <code>`.
- **`errors-people-at-company`** (key-value store): the lookups that failed for a technical reason, so you can re-run only those domains. A note `(partial: ...)` means some people were already delivered.
- **`RUN_SUMMARY`**: counts (requested, valid, looked up, people delivered, repeat positions skipped) and, per domain, how many people were delivered and how many rows the data holds for it.
- **Notice rows** (`type: "notice"`) appear for a domain with no data, a platform domain, a free-plan list longer than 100 (`free_plan_limit`), a reached `limit` (`limit_reached`), a reached spend limit (`charge_limit_reached`) or invalid input (`invalid-input`). Notices are never charged.

#### What is NOT returned

- Email addresses or phone numbers (use [email-finder](https://apify.com/b2bdata/email-finder))
- Work history, skills or education (use [linkedin-profile-by-url](https://apify.com/b2bdata/linkedin-profile-by-url))
- A search across companies by title or location (use [linkedin-people-search](https://apify.com/b2bdata/linkedin-people-search))
- Company details such as industry or size

***

### 🛡️ Reliable results

**Our data promise.** We work to keep the data current, and we stand behind it. If a person has moved on, a company is missing or a row looks wrong, open the Issues tab with your run link. We review every report and sort it out with you, case by case.

- **No data is free and visible.** Every domain without a row is in `MISSES` with its position and a reason and is never charged.
- **Repeats are charged once.** The same domain written twice, or the same person found twice, is one row.
- **Each row is saved as it is delivered.** A run that stops, or a lookup that fails halfway through a company, keeps what it already produced.
- **Bounded pace.** Lookups run at about 60 a minute, so a long list takes time. The run timeout is one hour.
- **Typos fail early.** An unsupported input field, a bad seniority value or a limit above its maximum stops the run before any charge.

***

### 🤖 For AI agents (MCP)

This actor runs through the Apify MCP server. Prompt to copy:

> "Use b2bdata/people-at-company with items=\["stripe.com"], titleRoles=\["cxo", "vp"] and limit=20. Return fullName, jobTitle, seniority and profileUrl."

**When to use it.** You hold company domains and need the people at each.
**When not to.** You need email addresses (use b2bdata/email-finder), a search across companies by title (use b2bdata/linkedin-people-search) or a full profile (use b2bdata/linkedin-profile-by-url).
**Input contract.** `items` array of domains, URLs or emails; optional `titleRoles`, `maxPeoplePerDomain` 1-1,000 (default 100), `limit` 1-10,000 (default 100).
**Output contract.** One row per person with `companyDomain`, `fullName`, `jobTitle`, `seniority`, `profileUrl`. Output is deterministic and schema-stable. Domains without data are a free notice row and listed in `MISSES`.
**Smoke test.** `limit: 1` on a well-known domain returns one row.
**Error cases.** `No valid domains` means no entry was a domain. `remove unsupported fields`, a bad `titleRoles` value or a bad number ends the run SUCCEEDED with one free `invalid-input` notice row and nothing charged.

***

### 🔌 Integrations: send results anywhere

1. **API.** Fetch rows from the run's default dataset: `GET /v2/datasets/{datasetId}/items?format=csv`.
2. **Webhook** on run success to push rows to your own endpoint.
3. **n8n, Make, Zapier.** Use the native Apify nodes: run the actor, then read the dataset.
4. **Google Sheets, Airtable, HubSpot, Salesforce, Clay.** Join on `companyDomain` and map `fullName`, `jobTitle` and `profileUrl` to contact fields.

***

### 🕐 Schedule examples

- **Weekly account refresh.** A schedule with a saved task that points at your target-account list.
- **New-account mapping.** A webhook from your CRM starts a run with the new domains.

***

### 🔗 Related actors

- `b2bdata/linkedin-employee-search`: the people at a company found by company name or LinkedIn URL. [Open](https://apify.com/b2bdata/linkedin-employee-search)
- `b2bdata/email-finder`: a work email for a person at a domain. [Open](https://apify.com/b2bdata/email-finder)
- `b2bdata/linkedin-people-search`: search people across companies by title and location. [Open](https://apify.com/b2bdata/linkedin-people-search)
- `b2bdata/headcount-by-department`: how many people a company has per team. [Open](https://apify.com/b2bdata/headcount-by-department)

[See all actors by b2bdata](https://apify.com/b2bdata)

***

### ❓ FAQ

#### Data and coverage

**How do I get a list of employees from a company domain?**
Put the domain in `items` and run. You get one row per person with name, title, seniority, location and profile URL.

**Can I get only the managers and executives?**
Yes. Set `titleRoles`, for example `["cxo", "vp", "director"]`. Seniority is grouped from the job title, so check `jobTitle` on the rows you act on.

**Why did I get fewer people than the company has?**
Rows stop at `maxPeoplePerDomain` (default 100) and at `limit` (default 100 for the whole run). Raise them. `RUN_SUMMARY` shows how many rows the data holds for each domain.

**Why is a domain missing from the results?**
It is in `MISSES` with a reason: no company on file, nobody matching your filter, a platform domain shared by many companies, or an entry that was not a domain.

**Do I get email addresses?**
No. `hasWorkEmail` and `hasPersonalEmail` say whether an address is on record. Use `b2bdata/email-finder` to find one.

**Do I need a LinkedIn account or cookies?**
No. It is a lookup without login, cookies or an account to ban.

#### Pricing

**What do I pay when a domain has no data?**
Nothing. There is no start fee, and unknown domains, bad entries, repeats and notices are free.

***

### 🛟 Support

Use the **Issues** tab on this actor's Apify page for bugs, questions and feature requests. We answer within 48 hours on working days. Please include the run ID and the input you used. Published on Apify by b2bdata. Not affiliated with, endorsed by, or sponsored by any company or person named in the examples above.

# Changelog

This Actor's version history is a separate document: https://apify.com/b2bdata/people-at-company/changelog.md

# Actor input Schema

## `items` (type: `array`):

Company domains to list people for. Example: \["stripe.com", "https://www.openai.com/about", "jane@microsoft.com"]. A website URL or an email address is reduced to its domain; a company name is not a domain and is listed in the MISSES record as invalid_domain. Each domain is looked up once; repeats are listed in MISSES as duplicate_input. A domain with no company or no people on file returns one free notice row. Free plan: 100 domains per run.

## `titleRoles` (type: `array`):

Keep only people at these seniority levels. Leave empty for everyone. Example: \["cxo", "vp"]. The levels are cxo, vp, director, manager, founder and other. 'other' is everyone not in a leadership level; a title such as President can sit there.

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

Most people delivered for any one domain, so a large employer cannot use up your run. Default 100, maximum 1000. A person with several positions counts once.

## `limit` (type: `integer`):

Most people delivered in the whole run, which also caps what is charged. Domains not reached are listed in MISSES and are not charged. Default 100, maximum 10000.

## `includeAudit` (type: `boolean`):

Adds requestId, latencyMs and inputRaw to each row under audit. Omit for clean rows.

## Actor input object example

```json
{
  "items": [
    "stripe.com"
  ],
  "maxPeoplePerDomain": 100,
  "limit": 100,
  "includeAudit": false
}
```

# Actor output Schema

## `people` (type: `string`):

One deduplicated row per person: name, title, seniority, company domain, location, profile URL and email flags

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

Counts, people per domain and stop reason for 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 = {
    "items": [
        "stripe.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("b2bdata/people-at-company").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 = { "items": ["stripe.com"] }

# Run the Actor and wait for it to finish
run = client.actor("b2bdata/people-at-company").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 '{
  "items": [
    "stripe.com"
  ]
}' |
apify call b2bdata/people-at-company --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,b2bdata/people-at-company"
        }
    }
}
```

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/sgi4DIcFF8jGb23cq/builds/h5osRZ52fzBIHxzfN/openapi.json
