# LinkedIn Company Employees Scraper (`coregent/linkedin-company-employees-scraper`) Actor

Find people who currently work at LinkedIn companies: names, profile URLs, current titles, headlines, locations, seniority and functions, with optional profile and verified work-email enrichment. Filter by title, location, seniority and function. No login or cookies.

- **URL**: https://apify.com/coregent/linkedin-company-employees-scraper.md
- **Developed by:** [Delowar Munna](https://apify.com/coregent) (community)
- **Stats:** 12 total users, 7 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.50 / 1,000 employee (basic)s

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

## LinkedIn Company Employees Scraper

![LinkedIn Company Employees Scraper](https://raw.githubusercontent.com/coregentdevspace/linkedin-company-employees-scraper-assets/main/thumbnail-linkedin-company-employees-scraper.png)

Give it target companies — get the people who **currently** work there, filtered by title, seniority, function and location.

For each LinkedIn company you provide (URL, slug or name), the actor returns **one clean row per current employee**: name, LinkedIn profile URL, headline, current job title, location, normalized seniority and function, and the company's context. Two optional depth modes add public profile details and work emails, each labelled with how far it was verified.

**No LinkedIn login, cookies or session required.**

### What you get

| Field                                            | Example                                                                                                         |
| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------- |
| `fullName`, `firstName`, `lastName`              | Jane Smith                                                                                                      |
| `profileUrl`, `publicIdentifier`                 | https://www.linkedin.com/in/jane-smith/                                                                         |
| `headline`                                       | Sales Director at Example Company                                                                               |
| `currentJobTitle`                                | Sales Director                                                                                                  |
| `location`                                       | Sydney, New South Wales, Australia                                                                              |
| `seniority`                                      | `DIRECTOR`                                                                                                      |
| `function`                                       | `SALES`                                                                                                         |
| `currentCompany`                                 | `{ id, name, slug, url, website, domain, industry, companySize, employeesOnLinkedIn, headquarters, followers }` |
| `isCurrentEmployee`, `employmentMatchConfidence` | `true`, `HIGH`                                                                                                  |
| `employmentEvidence`                             | `["title_company", "snippet_experience"]`                                                                       |
| `coverageStatus`                                 | `PARTIAL_PUBLIC`                                                                                                |
| `inputCompany`, `profileMode`, `scrapedAt`       | what you asked for, and when                                                                                    |

**PROFILE mode adds:** `profileSource`, `about`, `followersCount`, `connectionsCount`, `profilePictureUrl`, `currentPositions[]`, `experience[]`, `education[]`, `skills[]`, `languages[]`, `certifications[]`.

What to expect from PROFILE fields, measured on 80 rows: followers and connections almost always; photo and education on about 70% of rows; about and languages on about 55%; certifications on about 20%. **Past jobs are rarely available.** `experience[]` and `currentPositions[]` usually hold only the current employer, with no title or dates, and `skills[]` is empty. The current job title comes from search results, in `currentJobTitle`.

**PROFILE_EMAIL mode adds:** `workEmail`, `emailStatus`, `emailSource`, `emailConfidence`, `emailProvider`, `emailPattern`.

### How it works

1. **Resolve each company** from LinkedIn's public company page: its ID, name, slug, website and domain. Company names are matched exactly. If a name fits more than one LinkedIn company, the actor tells you instead of guessing.
2. **Discover employees** from public search results for that company's LinkedIn profiles. Your title, location, seniority and function filters are used to **target** the search, so they find more of the right people instead of only filtering a generic list.
3. **Validate current employment.** Each person is checked against how LinkedIn itself summarises their **current** employer. Former employees, "ex-Company" profiles and people who only mention the company are dropped.
4. **Filter, normalise and deduplicate**, then deliver.
5. Optional: **PROFILE** reads the person's public profile, which re-checks current employment. **PROFILE_EMAIL** then looks up a work email.

### Input examples

**Minimal**

```json
{
    "companies": ["https://www.linkedin.com/company/stripe/"],
    "profileMode": "BASIC",
    "maxEmployeesPerCompany": 100
}
```

**Recruiters at two companies, in Australia**

```json
{
    "companies": ["canva", "atlassian"],
    "profileMode": "BASIC",
    "maxEmployeesPerCompany": 500,
    "jobTitles": ["Recruiter", "Talent Acquisition", "Talent Partner"],
    "locations": ["Australia"]
}
```

**Decision makers, with profile details**

```json
{
    "companies": ["stripe"],
    "profileMode": "PROFILE",
    "maxEmployeesPerCompany": 100,
    "seniorityLevels": ["DIRECTOR", "VP", "CXO"],
    "functions": ["SALES", "MARKETING", "ENGINEERING"]
}
```

**Sales team, with work emails**

```json
{
    "companies": ["hubspot"],
    "profileMode": "PROFILE_EMAIL",
    "maxEmployeesPerCompany": 25,
    "jobTitles": ["Account Executive", "Sales Director"]
}
```

### Output

#### Employees: table view

The default **Employees** tab, from a real run on the minimal input above (Stripe, BASIC, 100 employees):

![Employees — table view](https://raw.githubusercontent.com/coregentdevspace/linkedin-company-employees-scraper-assets/main/linkedin-company-employees-scraper-output-employees-table-view.png)

#### Employees: sample record (JSON)

One record from that run, with all fields:

```json
{
    "rowType": "employee",
    "fullName": "Jackson Thea",
    "firstName": "Jackson",
    "lastName": "Thea",
    "profileUrl": "https://www.linkedin.com/in/jackson-thea-92468959/",
    "publicIdentifier": "jackson-thea-92468959",
    "profileId": null,
    "profilePictureUrl": null,
    "headline": "Software Engineer at Stripe",
    "currentJobTitle": "Senior Software Engineer",
    "location": "Brooklyn",
    "seniority": "SENIOR",
    "function": "ENGINEERING",
    "currentCompany": {
        "id": "2135371",
        "name": "Stripe",
        "slug": "stripe",
        "url": "https://www.linkedin.com/company/stripe/",
        "website": "https://stripe.com",
        "domain": "stripe.com",
        "industry": "Technology, Information and Internet",
        "companySize": "5,001-10,000 employees",
        "employeesOnLinkedIn": 16609,
        "headquarters": "South San Francisco, California",
        "followers": 1758930
    },
    "isCurrentEmployee": true,
    "employmentMatchConfidence": "HIGH",
    "employmentEvidence": ["title_company", "subtitle_company", "snippet_experience", "headline_at_company"],
    "coverageStatus": "LIMIT_REACHED",
    "sourceMode": "PUBLIC_SEARCH",
    "inputCompany": "https://www.linkedin.com/company/stripe/",
    "profileMode": "BASIC",
    "scrapedAt": "2026-10-09T17:32:51.406Z"
}
```

`employmentEvidence` shows why the row counts as a current employee. Here four independent signals all name Stripe as the current employer, so confidence is `HIGH`.

The **Profile details** and **Work emails** tabs show the same rows with the extra PROFILE and PROFILE_EMAIL fields listed under [What you get](#what-you-get).

**Ambiguous company name** (a free status row; nothing is charged):

```json
{
    "rowType": "company_status",
    "inputCompany": "Notion",
    "status": "AMBIGUOUS_COMPANY",
    "coverageStatus": "AMBIGUOUS_COMPANY",
    "message": "\"Notion\" matches 2 LinkedIn companies. Re-run with the company URL you mean.",
    "ambiguousCandidates": [
        { "name": "Notion", "url": "https://www.linkedin.com/company/notionhq/", "industry": "Software Development" },
        {
            "name": "Notion",
            "url": "https://www.linkedin.com/company/notion-agency/",
            "industry": "Advertising Services"
        }
    ]
}
```

### FAQ

**Which company inputs are supported?**
LinkedIn company URLs (`https://www.linkedin.com/company/stripe/`), slugs (`stripe`) and names (`Stripe`). URLs and slugs are the most reliable. Numeric company IDs can't be opened without a login, so use the URL or slug instead. Renamed companies are followed to their current page.

**Do I need a LinkedIn login or cookies?** No. Nothing is ever asked of you, and no LinkedIn account is used.

**Does it return current employees only?**
Yes. That is the core promise. Every row has `isCurrentEmployee: true` and an `employmentMatchConfidence`:

- `HIGH`: two independent signals, or the person's public profile, show the company as their current position.
- `MEDIUM`: one clear current-position signal.

Set **Minimum current-employment confidence** to `HIGH` for maximum precision. `employmentEvidence` lists the signals behind each row.

**Can it get *all* employees?**
Not guaranteed, and the actor won't claim it does. Employees are found through public search results, so large companies usually return a **partial** set. Every row and every company carries a `coverageStatus`:

| coverageStatus                            | Meaning                                                                     |
| ----------------------------------------- | --------------------------------------------------------------------------- |
| `LIMIT_REACHED`                           | Your *Max employees per company* was reached.                               |
| `PARTIAL_PUBLIC`                          | All the current employees public search could surface, short of your limit. |
| `FULL_OR_NEAR_FULL`                       | At least 90% of the employees LinkedIn lists for the company.               |
| `NO_EMPLOYEES_FOUND`                      | No current employees matched (filters may be too narrow).                   |
| `COMPANY_NOT_FOUND` / `AMBIGUOUS_COMPANY` | The company couldn't be resolved unambiguously.                             |
| `BLOCKED` / `PROVIDER_ERROR`              | A temporary failure. The company is listed in `RETRY_INPUT`.                |

**How accurate are the filters?**
Title filters match the **current** title first and fall back to the headline. Employment at the company is always required to be current. Location filters use the public profile location. A person with no public location is excluded when a location filter is set. Seniority and function are normalized from the current title, and a title that doesn't clearly indicate one is left `null`, not guessed.

**What are BASIC, PROFILE and PROFILE_EMAIL?**

- **BASIC** is the fastest and cheapest mode: everything in the table above.
- **PROFILE** adds public profile details. LinkedIn shows logged-out visitors only part of a profile: expect followers, connections, photo, education and about, but not a work history or skills. `profileSource` tells you where the data came from: `PUBLIC_PAGE`, `PROVIDER` or `UNAVAILABLE`.
- **PROFILE_EMAIL** adds a work email.

**Are the work emails verified or guessed?**
Every work email the provider finds is returned, labelled so you can decide what to send to:

- `VERIFIED`: the provider confirmed the mailbox exists (`emailConfidence: "HIGH"`).
- `UNVERIFIED`: the provider found the address but couldn't complete the check (`"MEDIUM"`).
- `CATCH_ALL`: the company's mail server accepts any address, so nobody can confirm a mailbox there. The address follows the company's email pattern for this person, who is a confirmed current employee, but it may bounce (`"LOW"`).

For outreach that must not bounce, filter to `emailStatus: "VERIFIED"`. Invalid addresses and personal (free-mail) addresses are never returned. Other statuses: `NOT_FOUND`, `NOT_REQUESTED`, `NO_COMPANY_DOMAIN`, `PROVIDER_ERROR`, `PROVIDER_NOT_CONFIGURED`.

**Can I scrape multiple companies?** Yes, up to 1,000 per run. One company failing never stops the others.

**What's the maximum per company?** 5,000.

**How does pricing work?**
Pay per result. You're charged **once per delivered unique employee**, at the rate for what that row actually contains:

| Event                    | When                                                                              |
| ------------------------ | --------------------------------------------------------------------------------- |
| `employee-basic`         | A BASIC row, or a PROFILE row for which no profile data could be obtained.        |
| `employee-profile`       | A row with profile details, or a PROFILE_EMAIL row where no work email was found. |
| `employee-profile-email` | A row with profile details **and** a work email (any label).                      |

You're never charged for company lookups, search pages, retries, duplicates, filtered-out people, former employees or company status rows. An empty run costs nothing. This README deliberately doesn't quote figures, so check the **Pricing** tab for current rates, which can differ by Apify plan. Runs respect your *maximum charge per run*.

**Can I call it through the API or MCP?**
Yes. Like any Apify Actor, you can run it from the Apify API, the JavaScript or Python clients, and Apify's MCP server for AI agents.

**How do I find work emails for these people with another tool?**
Feed `fullName` + `currentCompany.domain`, or `profileUrl`, into Coregent's Work Email Finder or LinkedIn Profile Scraper. You can also use PROFILE_EMAIL mode here.

### Run summary

Every run writes a `RUN_SUMMARY` record to the key-value store. It contains per-company status and coverage, candidates found, the current-employee validation rate, former/stale profiles rejected, filter match rate, duplicates, profile and email enrichment success rates, charged events, and runtime. Companies that failed for temporary reasons are written to `RETRY_INPUT`, which you can run again unchanged.

#### 🚦 Proxy policy

Use **Apify Datacenter** proxy (the default) or **no proxy** for normal runs. Both work for LinkedIn company pages.

**Apify Residential proxy is not supported.** The actor fails at startup if `apifyProxyGroups` includes `RESIDENTIAL`. In pay-per-event actors, residential bandwidth is billed to the developer, not the run user, so one bandwidth-heavy run could cost more than it earns.

If you need residential routing, for example to improve the free public-profile path in PROFILE mode, supply your own provider via the proxy editor's **Custom proxy URLs**:

```
http://user:pass@proxy.iproyal.com:12321
http://user:pass@proxy.brightdata.com:22225
http://user:pass@proxy.oxylabs.io:7777
```

### Limitations

- **Coverage is public, not complete.** People with hidden or unindexed profiles can't be found. Large companies return a partial set.
- **Logged-out profile views are partial.** LinkedIn hides much of a profile from visitors who aren't logged in, so PROFILE mode rarely includes past jobs, job titles inside `experience[]`, or skills. Treat it as follower, education and about enrichment, not a work-history export.
- **Seniority and function are derived from titles.** Unusual titles get `null`.
- **Work emails need the company's domain.** At companies whose mail servers accept any address, emails are returned as `CATCH_ALL`: the right pattern for a real employee, but not provably deliverable.

### Compliance and privacy

This is an unofficial tool, not affiliated with LinkedIn. It processes **public, professional** information only, and doesn't infer private or sensitive attributes. You're responsible for processing results lawfully (for example under GDPR or CAN-SPAM). Collect only the fields you need for your purpose. Never send connection requests, messages or automated outreach in ways that breach LinkedIn's terms.

# Actor input Schema

## `companies` (type: `array`):

LinkedIn company URLs (https://www.linkedin.com/company/stripe/) or slugs (stripe) work best. Names (Stripe) also work; a name matching several LinkedIn companies is reported as AMBIGUOUS_COMPANY with the candidates instead of guessing.

## `profileMode` (type: `string`):

BASIC: name, profile URL, headline, title, location, seniority, function, company. PROFILE: adds public profile details and a second employment check. PROFILE_EMAIL: adds a work email, labelled VERIFIED, CATCH_ALL or UNVERIFIED. Each row is charged for what it actually carries.

## `maxEmployeesPerCompany` (type: `integer`):

Stop after this many current employees per company. Coverage comes from public search results, so large companies usually return a partial set (coverageStatus PARTIAL_PUBLIC) rather than every employee.

## `jobTitles` (type: `array`):

Keep only employees whose current title (or, failing that, headline) contains one of these phrases, e.g. "Recruiter", "Account Executive". The titles are also used to target the search, so they find more matching people rather than just filtering.

## `excludeJobTitles` (type: `array`):

Drop employees whose current title or headline contains any of these phrases, e.g. "Intern".

## `locations` (type: `array`):

Keep only employees whose public location contains one of these, e.g. "Australia", "Sydney", "San Francisco Bay Area". Employees with no public location are excluded when this is set.

## `excludeLocations` (type: `array`):

Drop employees whose location contains any of these.

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

Keep only these normalized seniority levels. Seniority is derived from the current title; a title that does not clearly indicate a level gets no seniority and is excluded when this filter is set.

## `functions` (type: `array`):

Keep only these normalized functions, derived from the current title.

## `searchQuery` (type: `string`):

Free-text terms that must all appear in the employee's current title, headline, name or (in PROFILE modes) public About. Use quotes for phrases, e.g. "machine learning" platform.

## `minEmploymentMatchConfidence` (type: `string`):

MEDIUM (default): at least one source clearly shows the company as the person's current position. HIGH: two independent signals, or the person's public profile itself, confirm it - fewer rows, higher precision. Former employees and people who merely mention the company are never returned.

## `includeCompanyDetails` (type: `boolean`):

Add industry, company size, LinkedIn employee count, headquarters and followers to each row's currentCompany object.

## `includeCompanyStatusRows` (type: `boolean`):

Add one free row (rowType "company_status") for each company that could not be resolved, was ambiguous, or returned no employees, explaining why. Never charged.

## `maxSearchPagesPerCompany` (type: `integer`):

Upper bound on search-result pages read per company. 0 = automatic (scaled to Max employees per company). Lower it to make runs faster at the cost of depth.

## `proxyConfiguration` (type: `object`):

Proxy for LinkedIn company and profile pages. Apify Datacenter proxy is the default. Apify Residential proxy is not supported (the run fails at startup); to use residential routing supply your own provider via Custom proxy URLs.

## Actor input object example

```json
{
  "companies": [
    "https://www.linkedin.com/company/stripe/"
  ],
  "profileMode": "BASIC",
  "maxEmployeesPerCompany": 100,
  "jobTitles": [],
  "excludeJobTitles": [],
  "locations": [],
  "excludeLocations": [],
  "seniorityLevels": [],
  "functions": [],
  "searchQuery": "",
  "minEmploymentMatchConfidence": "MEDIUM",
  "includeCompanyDetails": true,
  "includeCompanyStatusRows": true,
  "maxSearchPagesPerCompany": 0,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `overview` (type: `string`):

One row per current employee: name, profile URL, current title, headline, location, normalized seniority and function, company context, current-employment confidence and coverage status.

## `profiles` (type: `string`):

PROFILE / PROFILE_EMAIL modes: public profile fields per employee.

## `emails` (type: `string`):

PROFILE_EMAIL mode: work emails per employee, labelled by verification status.

## `runSummary` (type: `string`):

Per-company status, coverage, validation and cost metrics.

# 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 = {
    "companies": [
        "https://www.linkedin.com/company/stripe/"
    ],
    "maxEmployeesPerCompany": 100,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("coregent/linkedin-company-employees-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 = {
    "companies": ["https://www.linkedin.com/company/stripe/"],
    "maxEmployeesPerCompany": 100,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("coregent/linkedin-company-employees-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 '{
  "companies": [
    "https://www.linkedin.com/company/stripe/"
  ],
  "maxEmployeesPerCompany": 100,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call coregent/linkedin-company-employees-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,coregent/linkedin-company-employees-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/SBp8ROIK9iekBdcgQ/builds/OBvh3GTyxNGswoClC/openapi.json
