# LinkedIn Post Tracker and Comment Capture (`mambalabs/linkedin-post-engager-capture`) Actor

Tracks new posts from LinkedIn person profiles and company pages, with no cookies and no login. Returns full post data plus the commenters LinkedIn shows publicly. One flat row per post, built for Clay.

- **URL**: https://apify.com/mambalabs/linkedin-post-engager-capture.md
- **Developed by:** [Mamba Labs](https://apify.com/mambalabs) (community)
- **Categories:** Lead generation, Automation, Social media
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.70 / 1,000 post collecteds

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/platform/actors/running/actors-in-store#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 can LinkedIn Post Tracker and Comment Capture do?

Point it at **LinkedIn person profiles and company pages**. It returns **their recent posts**, as
flat rows, with the real engagement counts on every one. No cookies, no LinkedIn account, no
credentials of any kind.

It also returns **the commenters LinkedIn shows publicly**, which is a bonus on top of the post feed
rather than the product. The limits on that are stated below and they are not small.

| 📦 What you get | ⚙️ Features and integrations |
|---|---|
| 📝 **One row per post**: text, media, permalink, author, published time<br>📊 **Live reaction and comment counts** on every post<br>🔑 **A stable `post_id`**, so a feed posts each item exactly once<br>🧾 **30 flat fields**, `snake_case`, built for Clay | 🍪 **No cookies and no LinkedIn account**, ever<br>📅 **`posted_since`**, so a scheduled run collects only what is new<br>💬 **Public commenters** as a labeled partial sample<br>⬇️ **Export** to JSON, CSV, Excel, HTML or XML |

Bought by go to market and content teams tracking **what a set of companies and executives is
publishing**, and how those posts are landing.

### 📝 What you get per post

Every post row carries `post_id`, `post_permalink`, `author_name`, `author_url`, `author_type`
(person or company), `post_text`, `post_media_url`, `published_at`, `reaction_count` and
`comment_count`.

`post_id` is the numeric part of LinkedIn's activity URN. It is stable across runs, across country
subdomains, and across both spellings of a permalink, so it is safe as a primary key and safe as the
"have I already posted this to Slack" check. The four-character token at the end of a permalink is
**not** stable and is never part of the key.

`posted_since` filters before anything is charged, so a scheduled run that finds nothing new costs
the actor start and nothing else.

### 💬 The commenter bonus, and its limits

Alongside each post you get one row per commenter LinkedIn renders publicly, with their **name,
profile URL, comment text and the likes on that comment**.

**Read this before you count on it:**

- **You get about ten commenters per post**, whatever the real total. LinkedIn renders roughly ten
  top-level comments to a logged-out visitor. On a post with 400 comments you get about ten.
- **Measured whole-run coverage was 178 commenters out of 4,857 that existed, which is 3.7%.**
- **About 30% of comment rows carry no timestamp.** In the same run `engaged_at` was null on 54 of
  178 rows, so those rows cannot be filtered by recency. The exact timestamp only comes from the
  page's structured data, and it is copied across only when that entry is unambiguously the same
  comment. When it is not, the row keeps its identity and text and the timestamp stays null rather
  than being guessed.
- **No headline.** LinkedIn does not render a commenter's headline to a logged-out visitor, so the
  field is declared and left null.

Every post row carries `commenters_collected` next to `commenters_available`, so the coverage on any
given post is a number you can read rather than something you have to infer.

### 🚧 What this actor cannot do

| | What you get |
|---|---|
| 📝 Posts | ✅ **Complete.** Text, author, media, permalink, published time, and the true reaction and comment counts. |
| 💬 Commenters | ⚠️ **Around ten per post**, 3.7% coverage measured, and roughly 30% of those rows have no timestamp. |
| 👍 Reactors | ❌ **None.** LinkedIn serves no reactor identities to a logged-out visitor. Every post row carries the real `reaction_count`, and `reactors_status` says `unavailable_without_login`. |
| 🪪 Commenter headline | ❌ **Not served.** The field exists and stays null rather than being guessed at. |

No row is ever an empty success. A blocked profile, a private page and a removed post each get their
own status and a plain-English `notice`.

> ⚠️ **`false` and `null` mean different things on every row.** `false` is a
> measurement: the actor looked and the answer was no. `null` is the absence of
> one: the actor could not look. When something it needed fails, the row comes
> back with `degraded: true`, a plain-language `degradation_reason`, and every
> field it could not measure set to `null` rather than to a confident `false` or
> `0`. Filter on `degraded` before you trust a negative.
>
> Two things that look like failures are deliberately **not** degraded, because
> both are measurements. `reactors_unavailable` is a permanent property of
> logged-out LinkedIn, not a fault in your run. `no_posts_found` on a page that
> loaded means the page really lists no recent activity. Turning commenter
> collection off is the reverse case: `commenters_collected` comes back `null`
> rather than `0`, because the actor did not look.

The trade is deliberate. Getting reactors means holding a LinkedIn session cookie or paying a vendor
who holds one. This actor does neither, so nothing you own is ever at risk and there is no
credential to hand over.

### 💡 Why use it

- **Content tracking without a login.** See what a list of companies and executives is publishing,
  with real engagement counts, on a schedule.
- **A Slack or CRM feed.** `post_id` is stable, so a downstream system can post each item exactly once.
- **Warm leads as a side effect.** A person who commented on three of your posts is warmer than one
  who commented on one. `person_profile_url` is the deduplication key across posts.

### 📥 Input

| Field | Type | What it does |
|---|---|---|
| `profile_urls` | array | Person profiles, like `https://www.linkedin.com/in/williamhgates`. Country subdomains are fine. |
| `company_urls` | array | Company pages, like `https://www.linkedin.com/company/microsoft`. |
| `posted_since` | string | ISO date. Posts older than this are skipped **before anything is charged**. |
| `collect_commenters` | boolean | Default on. Turn it off to collect posts only. |
| `collect_reactors` | boolean | Default on, and it returns no reaction rows. It is in the schema so the limit is visible instead of silent. |
| `max_engagers_per_post` | integer | Default 10. See below. |
| `use_residential_proxy` | boolean | Default off. Only needed if LinkedIn starts refusing the platform's datacenter addresses. Apify bills residential bandwidth on top of this actor's events. |

#### 💸 About `max_engagers_per_post`

**This is your cost lever, and the default is set to the platform ceiling on purpose.**

LinkedIn shows a logged-out visitor about ten comments per post, so ten is the most this actor can
ever return. Setting the default to 10 means you are never charged for engager rows the page cannot
produce, and raising it above 10 does nothing. Set it to 0 and you pay for posts only.

### 📤 Output

One dataset, two entity types, told apart by `row_type`.

- `post` is one row per LinkedIn post.
- `engager` is one row per person who commented, joined to the post on `post_id`.
- `notice` is a run-level or source-level message. **Filter these out before loading a table.**

#### Status values

`ok`, `commenters_truncated`, `reactors_unavailable`, `source_unavailable`, `post_unavailable`,
`no_posts_found`, `fetch_failed`, `no_input`, `free_tier_exhausted`, `free_tier_unmetered`,
`sources_truncated`, `posts_truncated`.

### 🧪 Using it in Clay

Add an Apify enrichment column and map your source column to `profile_urls` or `company_urls`.

The actor returns many rows for one input row, so use **Write to Other Table** to send post rows to a
posts table filtered to `row_type = post`, and commenters to a people table filtered to
`row_type = engager`. Clay caps a single actor run import at 5,000 records.

### 🔔 Sending results somewhere else

**You do not need this actor to do it, and it does not try.** Apify has built-in run webhooks that
cost nothing and need no code from us.

On this actor's page in Apify Console, open the **Integrations** tab and add a webhook on
`ACTOR.RUN.SUCCEEDED` pointing at your endpoint. Apify posts a JSON body carrying `eventType` and a
`resource` object with the run and dataset IDs, then your system reads the dataset. You configure it,
you own it, and it works on any actor including this one.

### 🎁 Free plan

Free Apify plans get **50 posts a month**. Paid plans are uncapped. When the allowance runs out the
run stops and says so on a row rather than returning a quietly short result.

### 🔒 Personal data

**Commenter rows are named people.** Each one carries a display name, a public LinkedIn profile URL
and the text of a comment they published in public.

Everything collected is served by LinkedIn to a logged-out visitor with no account and no cookies.
Nothing behind a login is read, and no private field is inferred.

When you run this actor **you are the data controller** for the personal data it returns, and Apify
is the processor. Your lawful basis for collecting and using it is yours to establish. We do not
advise on it and nothing here is legal advice.

### ⚖️ Unofficial

This actor is **unofficial**. It is not affiliated with, sponsored by, or endorsed by LinkedIn or
Microsoft. It reads public pages the way any logged-out visitor does.

### 🧯 Known limitations

- **No reactors, about ten commenters per post, and no commenter headline.** Covered above. These are
  the main ones.
- **Discovery sees a page's recent activity, not a full archive.** A profile or company page
  advertises its latest posts, so `posted_since` filters what was found rather than reaching back
  through history.
- **Posts referenced without a permalink cannot be fetched.** Embedded reshares appear on a page as a
  bare activity reference, and LinkedIn will not serve a post without its permalink token. The count
  is reported once per source.
- **No proxy by default.** Turn on `use_residential_proxy` if datacenter addresses start being
  refused.
- **200 sources per run and 50 posts per source.** Both are hard caps. Neither
  is silent: hitting either emits a `sources_truncated` or `posts_truncated` row
  saying exactly how many were left out. LinkedIn lists newest first, so what a
  truncated source gives you is the most recent 50.
- **No result cache.** Every run refetches every page, which is the right
  behavior for an actor whose job is to notice new posts, and it means a
  scheduled run costs the same whether or not anything changed. Use
  `posted_since` to keep the charge down: it filters before anything is billed.

### 🆘 Support

Questions, bugs and feature requests go in the Issues tab on the actor page.

Built by [Mamba Labs](https://apify.com/mambalabs).

# Actor input Schema

## `profile_urls` (type: `array`):

Person profiles whose recent posts you want, like https://www.linkedin.com/in/williamhgates. Country subdomains such as uk.linkedin.com are fine.

## `company_urls` (type: `array`):

Company pages whose recent posts you want, like https://www.linkedin.com/company/microsoft.

## `posted_since` (type: `string`):

ISO date or timestamp, like 2026-08-01. A scheduled run set to the last run time collects only what is new, and posts older than this are skipped before anything is charged. Leave empty to take everything the page advertises.

## `collect_commenters` (type: `boolean`):

Return one row per person who commented, with their name, profile URL and comment text. Turn this off to collect posts only.

## `collect_reactors` (type: `boolean`):

LinkedIn does not serve reactor identities to a logged-out visitor, so this returns no reaction rows. Every post row still carries the real reaction\_count. Left in the schema so the limit is visible rather than silent; see the README.

## `max_engagers_per_post` (type: `integer`):

Caps the engager rows charged per post. LinkedIn shows about ten comments to a logged-out visitor, so ten is the platform ceiling and the default. Lower it to spend less on posts with heavy comment counts.

## `use_residential_proxy` (type: `boolean`):

Off by default, which is the shipping default and what the pricing assumes. Turn it on only if LinkedIn starts refusing the platform's datacenter addresses. Residential bandwidth is billed by Apify on top of this actor's events.

## Actor input object example

```json
{
  "profile_urls": [
    "https://www.linkedin.com/in/williamhgates"
  ],
  "company_urls": [
    "https://www.linkedin.com/company/microsoft"
  ],
  "collect_commenters": true,
  "collect_reactors": true,
  "max_engagers_per_post": 10,
  "use_residential_proxy": false
}
```

# Actor output Schema

## `results` (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 = {
    "profile_urls": [
        "https://www.linkedin.com/in/williamhgates"
    ],
    "company_urls": [
        "https://www.linkedin.com/company/microsoft"
    ],
    "collect_commenters": true,
    "collect_reactors": true,
    "max_engagers_per_post": 10,
    "use_residential_proxy": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("mambalabs/linkedin-post-engager-capture").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 = {
    "profile_urls": ["https://www.linkedin.com/in/williamhgates"],
    "company_urls": ["https://www.linkedin.com/company/microsoft"],
    "collect_commenters": True,
    "collect_reactors": True,
    "max_engagers_per_post": 10,
    "use_residential_proxy": False,
}

# Run the Actor and wait for it to finish
run = client.actor("mambalabs/linkedin-post-engager-capture").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 '{
  "profile_urls": [
    "https://www.linkedin.com/in/williamhgates"
  ],
  "company_urls": [
    "https://www.linkedin.com/company/microsoft"
  ],
  "collect_commenters": true,
  "collect_reactors": true,
  "max_engagers_per_post": 10,
  "use_residential_proxy": false
}' |
apify call mambalabs/linkedin-post-engager-capture --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,mambalabs/linkedin-post-engager-capture"
        }
    }
}

```

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/oiGLNPuaf5BRaz9K5/builds/cMEF1eep3YKj2lcMg/openapi.json
