# LinkedIn Headcount Monitor (`cyprusapi/linkedin-headcount-monitor`) Actor

Employee-count time series for LinkedIn company pages: anonymous snapshots with deltas, open-jobs count as hiring signal, persistent per-watchlist history (up to 400 snapshots), typed values, provenance block. No cookies, no login. Error items never charged.

- **URL**: https://apify.com/cyprusapi/linkedin-headcount-monitor.md
- **Developed by:** [Torchtechnology LTD](https://apify.com/cyprusapi) (community)
- **Categories:** Lead generation, Social media, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.50 / 1,000 headcount snapshots

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

### What does LinkedIn Headcount Monitor do?

LinkedIn Headcount Monitor turns the **employee count of any public LinkedIn company page into a time series**. Give it a watchlist of company URLs or slugs, run it on a schedule, and every check stores a snapshot and reports the **delta against your previous check** — headcount grew, shrank, or held — plus the company's **open jobs count** as a complementary hiring signal. All data comes from **anonymous, public LinkedIn pages: no cookies, no login, no LinkedIn account**, with a typed JSON output, a per-field coverage status, and an audit-proof provenance block (URL, HTTP status, SHA-256 of the page) on every item.

History is kept in a persistent per-watchlist store (**up to 400 snapshots per company**) and can be attached to results on demand. **History starts with your first check** — there is no backfill, and we don't pretend otherwise.

The actor runs on the Apify platform, so you get **scheduling, API access, integrations, and proxy rotation** out of the box — schedule a daily run and the deltas build themselves. Errors are pushed as transparency items and **never charged**.

### What makes this LinkedIn headcount monitor different?

Most LinkedIn company scrapers sell you a one-off snapshot. The signal is in the *change* — and that's what this actor is built around.

| Capability | LinkedIn Headcount Monitor | [ramsford/linkedin-company-monitor](https://apify.com/ramsford/linkedin-company-monitor) | [freshdata/linkedin-company-custom-headcount](https://apify.com/freshdata/linkedin-company-custom-headcount) | [riceman/linkedin-company-data-insights-scraper](https://apify.com/riceman/linkedin-company-data-insights-scraper) |
|---|---|---|---|---|
| **Employee-count time series with deltas** (delta + delta %) | ✅ built in, persistent per-watchlist history (up to 400 snapshots) | ⚠️ delta vs. last run only, no history export | ❌ one-off counts | ⚠️ advertised, not verified |
| **Open jobs count** as complementary signal | ✅ included in the same event | ✅ (bundled in a $0.75 report) | ❌ | ✅ separate charged event |
| **Typed values + provenance block** (HTTP status, SHA-256 page hash) | ✅ on every item | ❌ | ❌ | ❌ |
| **Per-field coverage status** (`obtained`/`absent`/`unknown`) | ✅ never invents a value | ❌ | ❌ | ❌ |
| **Honest about login-only data** (headcount by function/location) | ✅ documented limitation | ❌ silent | ❌ | ⚠️ markets "insights" heavily |
| **No cookies / no LinkedIn account** | ✅ | ✅ | ⚠️ unclear | ✅ |
| **Errors never charged** | ✅ | — | — | — |
| **Price per 1,000 company checks** | **$2.50** | **$750** ($0.75/report) | **$8.00** | per-event, multiple charges |

(Competitor data from the public Apify Store API, August 2026.)

### How much does it cost to monitor LinkedIn headcounts?

The actor uses **pay-per-event** pricing — you pay only for delivered snapshots:

- **`snapshot`** — **$2.50 per 1,000 companies checked** ($0.0025 each), charged once per successfully checked company. Includes the company fields, the open-jobs signal, the delta computation, and the snapshot store.
- **`history-read`** (opt-in) — **$0.50 per 1,000 history snapshots returned** ($0.0005 each), charged only when `includeHistory` is on and past snapshots are actually attached.
- **`apify-actor-start`** — a one-time **$0.01 per run**.

**Errors are never charged.** Invalid slugs, deleted company pages (404), blocked requests, and transport failures produce transparency items in the dataset at zero cost.

#### Cost by volume (snapshots + $0.01 per run)

| Companies per run | Per run | Daily for 30 days |
|---|---|---|
| 10 companies | **$0.04** | $1.05 |
| 100 companies | **$0.26** | $7.80 |
| 1,000 companies | **$2.51** | $75.30 |

The Apify **free tier** is enough to try the actor on a handful of companies.

### Who is this actor for?

| Audience | What they use it for |
|---|---|
| **Sales / BD teams** | Growth-signal triggers: alert when a target account's headcount or open-jobs count jumps — the classic "they're scaling, time to call" moment. |
| **Recruiters & talent intelligence** | Layoff detection and hiring-velocity tracking across a competitor watchlist. |
| **Investors / VCs** | Portfolio and pipeline benchmarking: weekly headcount deltas without a data subscription. |
| **Competitive intelligence** | Longitudinal headcount series on competitors, with SHA-256 provenance for every data point. |
| **Data teams** | Clean, typed, honest time series via API — `null` means verifiably absent, `"unknown"` means not observable anonymously. |

### How to monitor LinkedIn headcount with LinkedIn Headcount Monitor

1. Open the actor in [Apify Store](https://apify.com/store) and click **Try for free**.
2. On the **Input** tab, add your watchlist: **company slugs** (`google`, `apify`) and/or **company URLs** (`https://www.linkedin.com/company/google/`).
3. Keep the **Watchlist ID** as `default`, or set your own to keep separate histories for separate lists.
4. Click **Start**, then download results from the **Output** tab — or pull them via the Apify API.
5. **Schedule it** (daily or weekly) in Apify Console — from the second run on, every item carries the delta vs. your previous check.

#### Using the Apify API (JavaScript)

```js
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });

const run = await client.actor('CyprusAPI/linkedin-headcount-monitor').call({
    companySlugs: ['google', 'microsoft', 'apify'],
    watchlistId: 'competitors-q3',
    includeJobsSignal: true,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

#### Using the Apify API (Python)

```python
from apify_client import ApifyClient

client = ApifyClient('<YOUR_APIFY_TOKEN>')

run = client.actor('CyprusAPI/linkedin-headcount-monitor').call(run_input={
    'companySlugs': ['google', 'microsoft', 'apify'],
    'watchlistId': 'competitors-q3',
    'includeJobsSignal': True,
})

for item in client.dataset(run['defaultDatasetId']).iterate_items():
    print(item)
```

### Input

The actor accepts the following parameters (see the **Input** tab for the full form). At least one of `companySlugs` or `companyUrls` is required.

| Field | Type | Description |
|---|---|---|
| `companySlugs` | array | LinkedIn company slugs — the part after `linkedin.com/company/` (e.g. `["google", "apify"]`). One dataset item per company. |
| `companyUrls` | array | LinkedIn company page URLs; the slug is extracted from each URL. |
| `watchlistId` | string | History namespace. Snapshots are stored per watchlist in a persistent key-value store (`headcount-history-<watchlistId>`). Default `default`. |
| `includeJobsSignal` | boolean | Also count open jobs per company via LinkedIn's public jobs search (worldwide scope). Default `true`. |
| `includeHistory` | boolean | Attach past snapshots to each result item (newest first, current check excluded). Charged as `history-read` per returned snapshot. Default `false`. |
| `historyLimit` | integer | Max past snapshots attached per company when `includeHistory` is on (0 = all stored, up to 400). Default `10`. |
| `country` | string | ISO alpha-2 proxy exit country (default `US`) — selects geo-matched proxies. |
| `requestDelayMs` | integer | Optional politeness delay between company checks. Default `0`. |
| `proxyConfiguration` | object | Optional Apify proxy settings, used as fallback tiers after your own / the bundled proxy pool. Residential recommended and preselected. |
| `proxiesByCountry` | object | Optional geo-pinned custom proxy URLs per ISO country code — always tried first. |

### Output

One dataset item per company check. Core metrics (`employeeCount`, `employeeCountRange`, `openJobsCount`) sit next to the delta block (`previousEmployeeCount`, `employeeCountDelta`, `employeeCountDeltaPct`, `openJobsCountDelta`, `changeSummary`), the history metadata (`firstSeenAt`, `checksCount`, `historyCount`), company profile fields, the `coverage` status, and the `capture` provenance block. You can download the dataset in various formats such as JSON, HTML, CSV, or Excel.

```json
{
  "companySlug": "apify",
  "companyUrl": "https://www.linkedin.com/company/apify/",
  "name": "Apify",
  "employeeCount": 256,
  "employeeCountRange": "201-500",
  "companySizeMin": 201,
  "companySizeMax": 500,
  "headquarters": "Prague",
  "industry": "Technology, Information and Internet",
  "website": "https://apify.com",
  "openJobsCount": 19,
  "openJobsCountApprox": false,
  "previousEmployeeCount": 256,
  "employeeCountDelta": null,
  "employeeCountDeltaPct": null,
  "previousOpenJobsCount": 17,
  "openJobsCountDelta": 2,
  "changeDetected": true,
  "changeSummary": "Open jobs rose from 17 to 19.",
  "isFirstCheck": false,
  "firstSeenAt": "2026-08-20T15:34:53+00:00",
  "checksCount": 2,
  "historyCount": 1,
  "coverage": { "company": "obtained", "jobs": "obtained" },
  "capture": {
    "captured_at_utc": "2026-08-20T15:35:49+00:00",
    "url": "https://www.linkedin.com/company/apify/",
    "http_status": 200,
    "content_hash": "sha256:9f2c…"
  },
  "scrapedAt": "2026-08-20T15:35:50+00:00"
}
```

First checks carry `isFirstCheck: true` and no delta fields — the time series starts with *your* first check. On later runs, deltas compare against the most recent stored snapshot of the same watchlist. With `includeHistory: true`, each item also carries a `history` array of past snapshots (newest first).

Fields follow a **three-state discipline**: a value means *observed*, `null` means *verifiably absent*, and `"unknown"` in the `coverage` block means *not observable anonymously right now*. Failed companies appear as transparency items with `error` and `errorDescription` and are **never charged**.

### Data table

| Field | Description |
|---|---|
| `companySlug`, `companyUrl`, `name` | Company identity and canonical page URL |
| `employeeCount` | **Employees on LinkedIn** — member profiles associated with the company page, as a typed integer from the page's structured data |
| `employeeCountRange` | LinkedIn's company size band (e.g. `201-500`, `10001+`), plus `companySizeMin` / `companySizeMax` |
| `openJobsCount` | Open roles from LinkedIn's public jobs search, worldwide scope. Capped display values ("5,000+") are lower bounds and flagged with `openJobsCountApprox: true` |
| `previousEmployeeCount`, `employeeCountDelta`, `employeeCountDeltaPct` | Delta vs. your previous check of this watchlist (`null` on first check or when no numeric change) |
| `previousOpenJobsCount`, `openJobsCountDelta` | Same for the jobs signal |
| `changeDetected`, `changes`, `changeSummary` | Human-readable change report (e.g. *"Employee count grew from 315,495 to 316,102 (+0.19%)"*) |
| `firstSeenAt`, `checksCount`, `historyCount` | When this company entered the watchlist, how often it was checked, how many snapshots are stored |
| `history` | Past snapshots (opt-in via `includeHistory`, charged as `history-read`) |
| `headquarters`, `industry`, `organizationType`, `website`, `specialties`, `companyId` | Public company profile fields |
| `coverage` | Per-field-group status: `obtained` / `absent` / `unknown` |
| `capture` | Provenance: capture timestamp, final URL, HTTP status, SHA-256 content hash |
| `scrapedAt` | Item timestamp (ISO 8601, UTC) |

### Known limitations

We'd rather you know these up front:

- **Headcount by function, location, or seniority (LinkedIn's "Insights" tab) is login-only.** We verified this anonymously on 2026-08-20: the insights subpage redirects to LinkedIn's login wall. This actor deliberately does **not** offer it — no LinkedIn account, no cookies, no exceptions. What you get is the public total (`employeeCount`) plus the open-jobs signal.
- **`employeeCount` is "employees on LinkedIn"** — the count of member profiles associated with the company page — not the company's payroll headcount. It is the same figure LinkedIn displays on the public page, and it is exactly what makes a longitudinal signal: the definition is stable, so deltas are meaningful.
- **The open-jobs count can be rounded.** LinkedIn caps large counts at display values like "5,000+" — we store the parsed number as a lower bound and set `openJobsCountApprox: true`.
- **History starts with your first check.** There is no backfill of past headcounts — anyone selling you historical LinkedIn headcount series is reselling someone else's stored observations, not scraping history.
- **LinkedIn guest endpoints are volatile.** Anonymous access is sometimes throttled or auth-walled for specific exit IPs; the actor retries through a proxy fallback chain and, if all tiers fail, pushes an uncharged error item instead of a guess.

### Tips and advanced options

- **Schedule daily runs** in Apify Console — the actor is designed for it: snapshots accumulate per `watchlistId`, and from the second run on you get deltas for free.
- **Separate watchlists, separate histories**: use `watchlistId` per segment (e.g. `competitors`, `pipeline`, `portfolio`) — histories never mix.
- **Alerting**: filter runs on `changeDetected: true`, or set thresholds on `employeeCountDeltaPct` / `openJobsCountDelta` in your integration (Zapier, Make, Slack webhook).
- **Proxy fallback chain**: each company is attempted through your `proxiesByCountry` entries or the bundled pool first, then Apify datacenter, then Apify residential (geo-pinned to `country`). Retries happen only on bot-detection/transport — a 404 is final.
- **Keep watchlists batched**: one run with 100 companies is far cheaper per snapshot than 100 single-company runs (each run carries the fixed start cost).

### FAQ

#### Where does the employee count come from?

From the public company page's structured data (schema.org `Organization.numberOfEmployees`) — the same "employees on LinkedIn" figure LinkedIn renders on the page. It is scraped live at run time; `capture.captured_at_utc` records the exact moment.

#### Do I need a LinkedIn account or cookies?

No. The actor only reads pages LinkedIn serves anonymously. That's a deliberate design constraint: it is also why headcount-by-function/location (the login-only Insights tab) is documented as a limitation instead of being advertised.

#### Will LinkedIn block the actor?

LinkedIn rate-limits anonymous traffic harder than most sites, which is why residential proxies are preselected and every check retries through a tiered fallback chain (your proxies → the bundled pool → Apify datacenter → Apify residential). Blocked checks surface as transparency items with `error` / `errorDescription` and are **never charged**.

#### How do I build a real time series?

Run the actor on a schedule (daily or weekly) with a fixed `watchlistId`. Every run appends one snapshot per company (up to 400 are kept) and reports the delta. Use `includeHistory` when you want the stored series returned with the results.

#### Is scraping LinkedIn company data legal?

Web scraping is legal when you extract **publicly available data** that is not behind a login — a position supported by the hiQ v. LinkedIn precedent in the US. This actor scrapes only anonymous, public pages and never touches login-only data. Note that regulations such as the GDPR in the EU can still apply when company data relates to identifiable individuals; you should have a legitimate reason to process such data. If you are unsure, consult a lawyer. Also review LinkedIn's Terms of Service before scraping. Apify is not liable for how you use the extracted data.

#### Why did a field come back as `null` or the coverage as `"unknown"`?

That is the honest-data design, not a bug: `null` means the field is verifiably absent on the page, `"unknown"` means it could not be observed anonymously in this run (e.g. the jobs endpoint auth-walled). The `coverage` block tells you which case applies — no invented values.

#### Something isn't working — where do I report it?

Open the **Issues** tab on the actor's page in Apify Console and describe the problem, ideally with the failing company slug and the run ID. Error items in the dataset (`error` / `errorDescription`) already tell you what went wrong for individual companies.

#### Can you build a custom LinkedIn data solution?

Yes — if you need different fields, company search, or a fully managed monitoring pipeline, reach out via the Issues tab and ask about a custom solution.

# Actor input Schema

## `companySlugs` (type: `array`):

LinkedIn company slugs to watch — the part after linkedin.com/company/ (e.g. "google", "apify"). One dataset item per company.

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

LinkedIn company page URLs (https://www.linkedin.com/company/<slug>/). The slug is extracted from each URL.

## `watchlistId` (type: `string`):

History is stored per watchlist in a persistent key-value store (headcount-history-<watchlistId>). Use different IDs for separate watchlists. History starts with your first check — there is no backfill.

## `includeJobsSignal` (type: `boolean`):

Also count open jobs per company via LinkedIn's public jobs search (worldwide scope). Rounded counts ('5,000+') are lower bounds and flagged with openJobsCountApprox.

## `includeHistory` (type: `boolean`):

Attach past snapshots of each company to its result item (newest first, current check excluded). Charged as 'history-read' per returned snapshot.

## `historyLimit` (type: `integer`):

Maximum number of past snapshots attached per company when includeHistory is on (0 = all stored, up to 400).

## `country` (type: `string`):

ISO alpha-2 country code for the proxy exit location (e.g. "US", "DE"). Picks the matching entries from proxiesByCountry / the bundled proxy pool and geo-pins Apify residential proxies.

## `requestDelayMs` (type: `integer`):

Optional politeness delay between company checks.

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

Optional Apify proxy settings — used as fallback tiers after your own / the bundled proxy pool. Residential proxies are recommended.

## `proxiesByCountry` (type: `object`):

Optional geo-pinned custom proxy URLs per ISO country code, e.g. {"us": \["http://user:pass@host:port"], "de": \[...]}. Tried first, ahead of the bundled pool and the Apify proxy tiers.

## Actor input object example

```json
{
  "companySlugs": [
    "apify"
  ],
  "companyUrls": [
    "https://www.linkedin.com/company/google/"
  ],
  "watchlistId": "default",
  "includeJobsSignal": true,
  "includeHistory": false,
  "historyLimit": 10,
  "country": "US",
  "requestDelayMs": 0,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

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

No description

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

No description

# 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 = {
    "companySlugs": [
        "apify"
    ],
    "companyUrls": [
        "https://www.linkedin.com/company/google/"
    ],
    "watchlistId": "default",
    "country": "US"
};

// Run the Actor and wait for it to finish
const run = await client.actor("cyprusapi/linkedin-headcount-monitor").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 = {
    "companySlugs": ["apify"],
    "companyUrls": ["https://www.linkedin.com/company/google/"],
    "watchlistId": "default",
    "country": "US",
}

# Run the Actor and wait for it to finish
run = client.actor("cyprusapi/linkedin-headcount-monitor").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 '{
  "companySlugs": [
    "apify"
  ],
  "companyUrls": [
    "https://www.linkedin.com/company/google/"
  ],
  "watchlistId": "default",
  "country": "US"
}' |
apify call cyprusapi/linkedin-headcount-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,cyprusapi/linkedin-headcount-monitor"
        }
    }
}

```

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/nAXFA3U5WpVSLbKxF/builds/HhhV2BLWhBRazR1kR/openapi.json
