# LinkedIn Company Scraper with Headcount Growth (No Cookies) (`northbell/linkedin-company-growth-scraper`) Actor

Scrape LinkedIn company pages for the real headcount and follower count — then keep the series, so you also get how fast each company is growing. No login, no cookies.

- **URL**: https://apify.com/northbell/linkedin-company-growth-scraper.md
- **Developed by:** [Northbell](https://apify.com/northbell) (community)
- **Categories:** Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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

## LinkedIn Company Scraper with Headcount Growth

Scrape LinkedIn company pages for industry, HQ, founding year, type, website, follower count — and the **real headcount**, the number LinkedIn actually counts rather than the size band the company typed in years ago.

Then keep running it, and you get the thing a single scrape cannot give you: **how fast each company is growing.**

### Anthropic says 501–1,000. LinkedIn counts 5,810.

Every company page carries two different numbers for the same thing:

| | source | when it was last true |
|---|---|---|
| `501-1,000 employees` | the size band an admin picked from a dropdown | whenever they last thought about it |
| `5,810 employees` | LinkedIn counting profiles that list this employer | today |

Measured live. **The stated band points at less than a fifth of the real number** — and it will keep pointing there until somebody remembers to change it. Stripe states 5,001–10,000 and counts 17,148. OpenAI states 1,001–5,000 and counts 10,675.

Most company scrapers hand you the band. This one hands you both, plus `bandDrifted`, `bandDriftDirection` and `bandDriftRatio` so you can find the companies whose own description of themselves has gone stale. Those are usually the ones that grew.

### The number that only means something twice

`17,148 employees` is a fact. `17,148, up from 16,148 a month ago` is a signal — that a company is hiring hard, that a competitor is scaling, that a prospect just raised.

**Headcount cannot be back-filled.** There is no endpoint that tells you what a company's headcount was last month. If you did not record it then, that month is gone. This Actor records it and keeps the series:

```
stripe
  Jul 25   16,148 employees   1,553,570 followers
  Aug 24   17,148 employees   1,653,570 followers
           ─────────────────────────────────────
           +1,000 / month     +6.19 % / month
```

### What you get

Every run appends to your dataset. Rows are tagged by `type`.

**`company`** — one row per company.

| field | meaning |
|---|---|
| `employees` | the real headcount LinkedIn counts today |
| `followers` | follower count today |
| `sizeBand`, `sizeBandMin`, `sizeBandMax` | what the company says about itself |
| `bandDrifted`, `bandDriftDirection`, `bandDriftRatio` | how far the stated band is from reality, and which way |
| `employeesChanged`, `employeesPerMonth`, `employeesPercentPerMonth` | growth, measured across your own observations |
| `employeeGrowthReliable` | `false` when the figure cannot be trusted — see below |
| `employeesAddedSinceLastRun` | change since you last looked |
| `followersChanged`, `followersPerMonth`, `followersPercentPerMonth`, `followerGrowthReliable` | the same for followers |
| `observedDays`, `observations`, `firstSeenAt` | how long you have been watching and how many samples you have |
| `industry`, `headquarters`, `founded`, `website`, `companyType`, `specialties`, `description`, `tagline` | the profile itself |

**`error`** — anything that failed, written where you will actually see it.

### Three questions this answers that a snapshot cannot

**"Is this prospect actually growing?"** — a company adding 6% headcount a month is spending. One that has been flat for six months is not, whatever its funding announcement said.

**"Is my competitor scaling or bleeding?"** — `employeesChanged` goes negative on contraction, and the Actor reports that rather than hiding it.

**"Which companies have outgrown their own description?"** — `bandDrifted` finds them. A company still claiming 51–200 while LinkedIn counts 600 is a company that has been too busy to update its profile.

### Numbers this Actor refuses to give you

A growth rate computed over three days is mostly noise multiplied by ten. So:

- Fewer than **7 days** of observation → `employeeGrowthReliable: false`, note `observed-for-less-than-7-days`. The figure is still returned; it is just labelled.
- **First observation** → no rate at all. One point has no slope, and inventing one would be a lie with a decimal point on it.
- **Headcount went down** → `count-went-down`. Real contraction and a LinkedIn profile cleanup look identical from outside, so the Actor reports the fact and declines to call it a trend.
- **No count on the page** → `no-value`, rather than a zero that would poison an average.

A rate without that flag is a rate that will eventually lie to you.

### No login. Not as a policy — as a property of the code.

This Actor never signs in, never asks you for a session cookie, and never sends one. It reads the public company page, the same one an anonymous visitor sees.

That is enforced, not promised:

- The request headers are a **frozen object** with no `Cookie` and no `Authorization` field, and nothing can add one at runtime.
- A guard rejects any attempt to attach a credential header, and **the input schema refuses any field** whose name looks like `cookie`, `session`, `token`, `auth` or `password`.
- Unit tests assert all of the above.

If a competing tool asks you to paste your `li_at` cookie, it is operating your LinkedIn account on your behalf. This one cannot, by construction.

### Two more things it gets right

**A page that parses to nothing fails loudly.** If neither the follower count nor the headcount can be read, the Actor raises rather than emitting a row full of nulls. A silently empty result is indistinguishable from a company that shrank to zero, and you would not notice for months.

**A failed fetch becomes a row, not a log line.** Nobody reads run logs. Failures land in the dataset as `error` rows, and the run is marked failed when nothing at all came back.

### Input

```json
{
  "companies": ["stripe", "https://www.linkedin.com/company/shopify", "general-motors"],
  "maxRequestsPerMinute": 20
}
```

Handles or full URLs both work. `linkedin.com/company/stripe` → `stripe`.

#### Sizing a run

One request per company. 100 companies is 100 requests, about five minutes at the default rate.

#### Daily runs

Just run it on a schedule with the same company list. Every run adds a point to each company's series, and the growth figures sharpen as the history deepens. Seven days in, the rates become reliable.

### What you pay for

Pay per event, charged only for results actually delivered:

| event | when |
|---|---|
| Actor start | once per run |
| Company recorded | one company's profile, counts and growth figures delivered |

**A failed fetch is never charged.** A dead handle produces an `error` row and no charge. You are paying for data, not attempts.

### Limits worth knowing

- **Headcount counts LinkedIn profiles**, not payroll. It undercounts companies whose staff are not on LinkedIn and overcounts stale profiles. It is a consistent proxy, tracked over time — which is exactly what makes the *change* meaningful even when the absolute number is not.
- **Some pages carry no headcount at all** (very small or new companies). Those rows report `employees: null` rather than a guess, and `employeesSource: "none"` so you can tell that apart from a parsing failure.
- **`headquarters`, `founded` and `specialties` are not on every page** — measured 6/7, 3/7 and 4/7 across a sample of large companies. `employees`, `followers`, `sizeBand`, `industry`, `website` and `companyType` came back on all of them.
- The rate-limit budget persists in a key-value store, so overlapping runs of this Actor share one budget rather than stacking up.

### On data and privacy

This Actor collects **company pages**, not people. It does not read, store or return employee identities, profiles, names or contact details — the headcount is a count and nothing else.

Company descriptions pass through to your dataset but are **never written to the Actor's own history**. The persistent store holds numbers and identifiers only: handles, counts, dates.

Company logos are not redistributed.

### Storage

History lives in a named key-value store, `linkedin-company-history`, so it survives between runs. Deleting it resets the baselines — every company reports as a first observation again, and growth goes quiet until it has two samples a week apart.

### Running locally

```bash
npm install
npm test          # 25 unit tests, no network, including the no-login guarantees
```

# Actor input Schema

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

Company handles or full LinkedIn URLs. The handle is the last part of the URL: linkedin.com/company/stripe → stripe.

## `maxRequestsPerMinute` (type: `integer`):

One request per company. Kept polite by default. The budget is shared across your runs of this Actor, so parallel runs do not stack up.

## Actor input object example

```json
{
  "companies": [
    "stripe",
    "shopify"
  ],
  "maxRequestsPerMinute": 20
}
```

# Actor output Schema

## `all` (type: `string`):

Every row: company and error.

## `companies` (type: `string`):

One row per company: real headcount, followers, industry, HQ, founding year, type and website.

## `growth` (type: `string`):

How fast each company is adding people and followers, measured across your own observations.

## `drift` (type: `string`):

Companies whose self-reported size band no longer matches the number of people LinkedIn actually counts.

# 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": [
        "stripe",
        "shopify"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("northbell/linkedin-company-growth-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": [
        "stripe",
        "shopify",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("northbell/linkedin-company-growth-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": [
    "stripe",
    "shopify"
  ]
}' |
apify call northbell/linkedin-company-growth-scraper --silent --output-dataset

```

## MCP server setup

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