# LinkedIn Top Voice & Competitor Activity Monitor (`fanndev/linkedin-competitor-activity-monitor`) Actor

Profile the content strategy behind any LinkedIn person or company page: how often they post, what format they use, which hashtags they lean on, and which choices actually earn engagement on their audience. No login and no cookie required.

- **URL**: https://apify.com/fanndev/linkedin-competitor-activity-monitor.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 $1.30 / 1,000 results

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 Top Voice & Competitor Activity Monitor

Point this at any LinkedIn person or company and get their content strategy as numbers: how often they post, in what format, at what length, with which hashtags — and which of those choices actually earns engagement from their audience.

**No cookie, no login, no account at risk.** LinkedIn embeds an account's recent posts, with full text and timestamps, in the page it serves to logged-out visitors. That is the entire data source.

### What you get

One `ACCOUNT` row per person or company:

```json
{
  "accountName": "Satya Nadella",
  "accountType": "PERSON",
  "followers": 12177102,
  "postsAnalysed": 6,
  "daysCovered": 9,
  "postsPerWeek": 4.67,
  "avgReactions": 4378.2,
  "medianReactions": 3574.0,
  "maxReactions": 12195,
  "engagementRatePct": 0.036,
  "mediaMix": { "TEXT": 3, "LINK": 3 },
  "avgTextLength": 600,
  "topHashtags": [],
  "hashtagUsagePct": 0.0
}
```

plus one `POST` row per analysed post with its text, date, reactions, hashtags and length.

**`engagementRatePct` is the number that matters.** Raw reaction counts only tell you who has the bigger audience. Reactions per post divided by followers is what lets you say that a 50-reaction post from a 2,000-follower account is outperforming a 500-reaction post from an account with twelve million.

`medianReactions` sits next to `avgReactions` on purpose: one viral post drags an average badly, and the median is the better read on what a typical post does.

### The two limits, stated plainly

**LinkedIn embeds roughly the last 6–10 posts per account.** Not thirty, not a full archive. That is enough to characterise a posting style — and running this on an Apify schedule turns those snapshots into a real time series — but it is not a backfill tool. Every `ACCOUNT` row carries `postsAnalysed`, `daysCovered`, `firstPostAt` and `lastPostAt` so you always know the window you are looking at.

**Company pages embed no engagement data at all.** Member profiles embed reaction counts with each post; company pages embed only the text and date. Reporting those missing counts as zero would make every company look like it gets no engagement, so this actor leaves them **null** instead, and flags it with `engagementDataAvailable: false`.

Turn on `fetchPostEngagement` to fix it properly: the actor then opens each company post's own permalink and reads the true reaction and comment counts. It costs one extra request per post, which is why it is opt-in rather than always on.

### Universities do not work

LinkedIn blocks `/school/` pages outright for logged-out visitors — HTTP 999 on every path tried, including the `/company/` alias that redirects there. That is structural, not rate limiting: it does not clear with delays, and no proxy changes it. Universities cannot be monitored with this actor, and the input schema says so rather than letting you discover it from an empty dataset.

### Input

```json
{
  "profileUrls": ["satyanadella", "https://www.linkedin.com/in/someone-else"],
  "companyUrls": ["anthropicresearch", "openai"],
  "fetchPostEngagement": true,
  "minReactions": 0
}
```

Handles and slugs work as well as full URLs. `minReactions` drops low performers before the metrics are computed, which turns the question from "what is their average post" into "what does their good content look like".

### Who this is for

- **Social media agencies** — a competitive content report on a client's rivals, produced in one run instead of an afternoon of scrolling.
- **Brand analysts** — posting cadence and format mix tracked over time, on a schedule.
- **Founders and marketers** — find out whether the accounts you are benchmarking against are actually outperforming you, or just bigger.

### Rate gating

LinkedIn answers request bursts with **HTTP 999** and a ~1.5 KB stub page. Three back-to-back requests were enough to trip it in testing; the same URLs answered 200 again after a four-second pause. It is per-IP and per-burst, and it is **not** a TLS fingerprint gate — five browser fingerprints all returned 200 on the same page.

So the actor sleeps a random 3–8 seconds between page loads, backs off exponentially when gated, and rotates fingerprints. If you monitor many accounts in one run and see rate-gate warnings, raise both delay bounds or enable a proxy.

### Related actors

- **LinkedIn Post Engagers Scraper** — who engaged with a specific post, with their comments.
- **LinkedIn Newsletter & Article Scraper** — long-form content and its performance.

# Actor input Schema

## `profileUrls` (type: `array`):

People to profile - industry influencers, competitors' founders, your own team. A full /in/ URL or just the handle both work.

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

Company or showcase pages to profile. A full /company/ URL or just the slug both work. Note that LinkedIn blocks /school/ pages entirely for logged-out visitors, so universities cannot be monitored this way.

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

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

## `maxPostsPerAccount` (type: `integer`):

Cap on posts analysed per account. LinkedIn embeds roughly the last 6-10 posts on a logged-out page regardless of what you ask for, so values above that change nothing - the ceiling is LinkedIn's, not this actor's.

## `minReactions` (type: `integer`):

Drop low-performing posts before the metrics are computed. Useful for asking 'what does their good content look like' rather than 'what is their average'.

## `fetchPostEngagement` (type: `boolean`):

Company pages embed their posts WITHOUT any engagement data, while member profiles embed it. Turn this on to open each company post's own permalink and read its true reaction and comment counts - one extra request per post. Leave it off and company engagement metrics stay null rather than being reported as zero.

## `emitPostRecords` (type: `boolean`):

Write each analysed post as its own POST row: text, publish date, reactions, hashtags, media type and length.

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

LinkedIn answers request bursts with HTTP 999 and a near-empty page - three back-to-back requests were enough to trip it in testing. A randomised gap is what keeps a run under that gate.

## `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`):

Optional. These pages carry no WAF and Apify's datacenter range reaches them, but LinkedIn's per-IP rate gate is real - a proxy helps most when you are monitoring many accounts in one run.

## Actor input object example

```json
{
  "profileUrls": [
    "satyanadella"
  ],
  "companyUrls": [
    "anthropicresearch"
  ],
  "maxPostsPerAccount": 25,
  "minReactions": 0,
  "fetchPostEngagement": false,
  "emitPostRecords": true,
  "minDelaySeconds": 3,
  "maxDelaySeconds": 8,
  "exportFormats": [],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

One ACCOUNT row per person or company with their content metrics, one POST row per analysed post, plus NOTICE and ERROR rows.

# 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 = {
    "profileUrls": [
        "satyanadella"
    ],
    "companyUrls": [
        "anthropicresearch"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("fanndev/linkedin-competitor-activity-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 = {
    "profileUrls": ["satyanadella"],
    "companyUrls": ["anthropicresearch"],
}

# Run the Actor and wait for it to finish
run = client.actor("fanndev/linkedin-competitor-activity-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 '{
  "profileUrls": [
    "satyanadella"
  ],
  "companyUrls": [
    "anthropicresearch"
  ]
}' |
apify call fanndev/linkedin-competitor-activity-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,fanndev/linkedin-competitor-activity-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/UU0dW23lMW5HdYgle/builds/BfUX4E3MtWle4YFdw/openapi.json
