# LinkedIn Company Employee Role Filter (`fanndev/linkedin-company-employee-filter`) Actor

Find the decision makers inside a target account: scrape a company's people and keep only the titles you care about - CMO, Marketing Director, Head of HR. Requires your own li\_at session cookie, because LinkedIn publishes no employee list to logged-out visitors.

- **URL**: https://apify.com/fanndev/linkedin-company-employee-filter.md
- **Developed by:** [Faisal Ahdan naufal](https://apify.com/fanndev) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.80 / 1,000 results

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

## LinkedIn Company Employee Role Filter

Account-based marketing runs on one question: *who inside this company can say yes?* This actor answers it — scrape a target company's people and keep only the titles you care about: CMO, Marketing Director, Head of HR.

It is the cheap alternative to a Sales Navigator seat for the narrow job of finding decision makers at a named list of accounts.

### Read this before you buy

**The employee list requires your own `li_at` session cookie.** LinkedIn publishes no employee directory to logged-out visitors at all: `/company/{slug}/people/` redirects to the login wall and the private API answers HTTP 403. Verified 2026-09-20. There is no workaround, in this actor or any other.

**Without a cookie the run still succeeds** and returns each company's public record — name, follower count, headcount on LinkedIn, website, **web domain** and LinkedIn's internal company id — plus a `NOTICE` row stating plainly why no people came back. That is genuinely useful for account research on its own, and it means an empty people list always arrives with a reason attached rather than looking like a bug.

### Getting your cookie

Sign in to LinkedIn in your browser → DevTools → Application → Cookies → `www.linkedin.com` → copy the **value** of `li_at` → paste into `sessionCookie`.

This actor never asks for your password and never signs in on your behalf.

**The honest risk.** Scraping while signed in breaches LinkedIn's User Agreement and accounts do get restricted for it. Three mitigations, all of which this actor does or exposes:

- **Residential proxy in your own country** — the default here. LinkedIn weighs the exit IP against where your account normally signs in; an Apify datacenter IP is exactly the mismatch that flags a session. The actor warns in the log if you turn it off.
- **Low caps** — `maxEmployees` defaults to 500, `maxEmployeesPerCompany` to 100. Long, greedy runs are what abuse scoring reacts to.
- **The 3–8 second randomised delay** — with a cookie in play it keeps the run looking like a person, not a script.

Use an account you would not mind losing.

### Role matching, and why it is whole-word

```json
{
  "companyUrls": ["anthropicresearch", "openai"],
  "roleKeywords": ["CMO", "Marketing Director", "Head of Marketing"],
  "excludeKeywords": ["Assistant", "Intern", "Former"],
  "sessionCookie": "AQEDAT..."
}
```

Keywords are matched as **whole words** against the headline. That distinction matters more than it looks: a substring search for `HR` pulls in every **Chris** and every **Thread**, and a substring search for `CEO` matches nothing useful in `CEO's office`. Multi-word entries like `Marketing Director` match as a phrase. `excludeKeywords` is applied after, on the same whole-word basis.

**One keyword is cheaper than several.** With exactly one, it is handed to LinkedIn's own search and narrows results server-side, so fewer pages are fetched. With several, the search runs unfiltered and matching happens locally — because LinkedIn treats multiple search terms as AND and would return almost nothing.

`matchedKeyword` on each row tells you which term the person matched, and is null when no include filter was set — so you can always tell "matched everything" from "matched nothing in particular".

### The company id

LinkedIn's internal numeric company id is the key the people search facets on, and it is never rendered on the page. This actor lifts it out of the "see all employees" call-to-action link, where it leaks double-percent-encoded. You get it on every `COMPANY` row as `companyId`, which is useful in its own right if you build anything else against LinkedIn.

If a page does not carry one — some showcase and redirected pages do not — the run writes a `company_id_not_found` notice rather than silently skipping the company.

### Output

`COMPANY` rows always; `EMPLOYEE` rows when a cookie was supplied.

```json
{
  "recordType": "COMPANY",
  "companyName": "Anthropic",
  "companyId": "74126343",
  "website": "https://www.anthropic.com/",
  "companyDomain": "anthropic.com",
  "followers": 4913113,
  "employeeCount": 5979
}
```

`employeeCount` is a useful sanity check: it tells you how large the organisation is on LinkedIn, so you can see what fraction of it any single run could ever reach.

### No email addresses

`companyDomain` gives you the employer's web domain. This actor does **not** invent `first.last@domain.com`. A guessed address that bounces costs you your sending domain's reputation, which is far more expensive than a missing column — put the name and domain through a verification tool that actually checks.

### Universities do not work here

LinkedIn blocks `/school/` pages entirely for logged-out visitors (HTTP 999 on every path, including the `/company/` alias that redirects there). Use the **LinkedIn University Alumni Career Mapper** for schools.

### Before you use this on people

These are real people, and in the EU, UK and similar jurisdictions they carry data-protection rights even though their profiles are public. Legitimate interest is a defensible basis for B2B outreach; it is not a blank cheque. Say where you got the data when you make contact, honour opt-outs, and keep it out of consumer marketing lists. That is your obligation as the data controller, not the actor's.

### Related actors

- **LinkedIn Event Attendees Extractor** — an audience that already self-selected on your topic.
- **LinkedIn Post Engagers Scraper** — warm leads from a competitor's post, mostly without a cookie.
- **LinkedIn Smart Job Description & Recruiter Extractor** — which accounts are hiring, and for what.

# Actor input Schema

## `companyUrls` (type: `array`):

Target accounts. A full /company/ URL or just the slug both work. Note LinkedIn blocks /school/ pages entirely for logged-out visitors, so universities will not resolve here.

## `startUrls` (type: `array`):

The same company links in the request-list format, for callers that already keep one.

## `sessionCookie` (type: `string`):

LinkedIn publishes NO employee list to logged-out visitors: /company/{slug}/people/ redirects to the login wall and the private API answers 403. Without this cookie the run still succeeds and returns each company's public record, but no people. To get it: sign in to LinkedIn in your browser, open DevTools > Application > Cookies > www.linkedin.com, copy the VALUE of li\_at. This actor never asks for your password and never signs in on your behalf. Scraping while signed in breaches LinkedIn's User Agreement and accounts do get restricted - use one you are willing to risk, pair it with a residential proxy, and keep the caps low.

## `roleKeywords` (type: `array`):

Keywords matched as whole words against each person's headline - 'CMO', 'Marketing Director', 'Head of HR'. Whole-word matching is deliberate: a substring search for 'HR' would drag in every 'Chris' and 'Thread'. Multi-word entries match as a phrase. Leave empty to keep everyone. Giving exactly ONE keyword is cheapest, because it is passed to LinkedIn's own search and narrows results server-side; several keywords are matched locally instead, since LinkedIn treats multiple terms as AND.

## `excludeKeywords` (type: `array`):

Whole-word exclusions applied after the include filter - 'Assistant', 'Intern', 'Former'.

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

Cap per company, applied before role filtering. Keeping this modest is the best protection for your session cookie - long, greedy runs are what LinkedIn's abuse scoring reacts to.

## `maxEmployees` (type: `integer`):

Overall ceiling across every company, counted after role filtering.

## `minDelaySeconds` (type: `integer`):

LinkedIn answers request bursts with HTTP 999 and a near-empty page. A randomised gap between requests is what keeps a run under that gate - and with a session cookie in play it is also what keeps the run looking like a person rather than a script.

## `maxDelaySeconds` (type: `integer`):

The ceiling of the randomised gap. Raise both bounds if the log shows rate-gate warnings.

## `exportFormats` (type: `array`):

Besides the dataset, write ready-made files into this run's key-value store.

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

Whenever you supply a session cookie, use RESIDENTIAL in your own country. LinkedIn scores the exit IP against where your account normally signs in, and an Apify datacenter IP is precisely the mismatch that gets a session flagged. The actor warns in the log if a cookie is supplied without one.

## Actor input object example

```json
{
  "companyUrls": [
    "anthropicresearch"
  ],
  "roleKeywords": [
    "CMO",
    "Marketing Director",
    "Head of Marketing"
  ],
  "maxEmployeesPerCompany": 100,
  "maxEmployees": 500,
  "minDelaySeconds": 3,
  "maxDelaySeconds": 8,
  "exportFormats": [],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

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

One COMPANY row per company (always), one EMPLOYEE row per matching person (cookie only), plus NOTICE and ERROR rows explaining any gaps.

# 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 = {
    "companyUrls": [
        "anthropicresearch"
    ],
    "roleKeywords": [
        "CMO",
        "Marketing Director",
        "Head of Marketing"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("fanndev/linkedin-company-employee-filter").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 = {
    "companyUrls": ["anthropicresearch"],
    "roleKeywords": [
        "CMO",
        "Marketing Director",
        "Head of Marketing",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("fanndev/linkedin-company-employee-filter").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 '{
  "companyUrls": [
    "anthropicresearch"
  ],
  "roleKeywords": [
    "CMO",
    "Marketing Director",
    "Head of Marketing"
  ]
}' |
apify call fanndev/linkedin-company-employee-filter --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,fanndev/linkedin-company-employee-filter"
        }
    }
}
```

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/36b3FcYdoY7kaOibo/builds/qpAZ6IbpMlA9afpKd/openapi.json
