# LinkedIn Company Posts Scraper (`lightmoon/linkedin-company-posts-scraper`) Actor

Posts from any LinkedIn company page or person: text, exact publication time, reactions, comment counts and media. No login, no cookie. Up to 70 posts per company, and a date filter so a repeat run only pays for what is new. Comment text optional.

- **URL**: https://apify.com/lightmoon/linkedin-company-posts-scraper.md
- **Developed by:** [Stable](https://apify.com/lightmoon) (community)
- **Categories:** Social media, Lead generation, Marketing
- **Stats:** 1 total users, 0 monthly users, 86.7% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.35 / 1,000 posts

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?

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

Paste LinkedIn company pages, people, or single post links. Get their posts as
CSV, Excel or JSON — text, engagement, media and the exact minute each one was
published. No LinkedIn account, no cookie, nothing to install.

```json
{ "companies": ["stripe"], "maxPostsPerSource": 70 }
```

> **Unofficial.** This Actor is not affiliated with, endorsed by or sponsored by LinkedIn or Microsoft. It reads only publicly available pages, does not log in and does not bypass any access control. All product names and trademarks belong to their respective owners.

### What one row looks like

From a real run, unedited except for the comment list, which is trimmed to one:

```json
{
  "postId": "7496332962540589056",
  "postUrl": "https://www.linkedin.com/feed/update/urn:li:activity:7496332962540589056/",
  "postedAt": "2026-08-20T22:30:53Z",
  "sourceType": "company",
  "sourceKey": "stripe",
  "text": "Kraken is launching Krak Cards in the US, powered by Stripe Issuing. Spend crypto anywhere Visa is accepted.",
  "textLength": 108,
  "reactions": 105,
  "comments": 7,
  "topReactions": "LIKE, EMPATHY, PRAISE",
  "authorName": "Stripe",
  "authorHeadline": "1,657,349 followers",
  "authorUrl": "https://www.linkedin.com/company/stripe",
  "authorType": "company",
  "isRepost": true,
  "quotedAuthorName": "Kraken",
  "quotedAuthorUrl": "https://www.linkedin.com/company/krakenfx",
  "quotedText": "Krak Card has landed in the U.S. Most cards force a trade-off…",
  "mediaType": "video",
  "mediaUrl": "https://dms.licdn.com/playlist/vid/v2/D5605AQHqmHlYPqjGkA/…",
  "commentsFetched": 5,
  "commentList": [
    {
      "text": "Thats dope! Well done Kraken team!",
      "commentedAt": "2026-08-21T09:07:16Z",
      "authorName": "Oleh Muraviov",
      "authorUrl": "https://ch.linkedin.com/in/oleh-muraviov-995a04161",
      "likes": 0
    }
  ]
}
```

### The publication time is exact on every row

`postedAt` is a real timestamp — `2026-08-20T22:30:53Z` — for the first post and
for the seventieth alike. LinkedIn's own public page prints `2mo` past the first
few, so a date column built by reading the page runs out after ten posts. This
one does not: it is recovered from the post itself and checked, where LinkedIn
does print a date, against theirs. On the posts where both exist they agree to
the second.

That matters the moment you sort, group by week, or ask "what did they publish
between the funding round and the launch".

### Fields

29 columns across a run; 12 on every row, the rest present when the post has
them.

| Field | What it is |
|---|---|
| `postId` · `postUrl` | The post's id, and a link that opens it |
| `postedAt` | Exact publication time, ISO 8601, UTC |
| `postedAtSource` | LinkedIn's own printed date, where it prints one — for checking the column above |
| `text` · `textLength` | The post body, and its length |
| `reactions` · `comments` | LinkedIn's own totals |
| `topReactions` | Which reactions, most used first: `LIKE, PRAISE, EMPATHY, INTEREST, ENTERTAINMENT, APPRECIATION` |
| `authorName` · `authorHeadline` · `authorUrl` · `authorType` | Who posted it, and whether that is a person or a company |
| `isRepost` | Whether this is somebody else's content |
| `repostedByName` | Who put it in the feed you asked about, when they are not the author |
| `repostId` · `repostedAt` | The reshare's own id and time, which differ from the original's |
| `quotedAuthorName` · `quotedAuthorUrl` · `quotedText` | The post being quoted, when this one quotes another |
| `mediaType` | `video`, `image`, `link` or empty |
| `mediaUrl` · `mediaUrls` · `thumbnailUrl` | Direct links to what is attached |
| `linkUrl` · `linkTitle` | The shared link and its headline |
| `commentsFetched` · `commentList` | Comment text, author, time and likes — when you turn comments on |
| `sourceType` · `sourceKey` | Which of your inputs this row came from |
| `scrapedAt` | When this row was read |

#### A repost names both people

If a company reposts an employee, `authorName` is the employee who wrote it and
`repostedByName` is the company that amplified it. If a post quotes another
post, `authorName` wrote the commentary and `quotedAuthorName` wrote what is
being commented on. These are different relationships and they stay different
columns, because "who wrote this" and "who chose to share it" are different
questions.

### Filters, and why they are the cheap part

Every filter here is decided before a row is stored, so **a post a filter drops
costs you nothing** — no charge, and no second request for its comments.

| Filter | Use |
|---|---|
| `postedAfter` · `postedBefore` | Both bounds include the day you name |
| `minReactions` · `minComments` | Against LinkedIn's own totals |
| `skipReposts` | Only what they wrote themselves |
| `onlyWithText` | Drops a bare reshare with no words of its own |

`postedAfter` is the one that pays for itself. Set it to your last run's date
and a weekly check over a company that posts twice a week costs two posts
instead of seventy.

### Recipes

- **Watch a competitor's page weekly.** `companies`, `postedAfter` = last
  Monday. Schedule it and you get only what is new.
- **Find who engages.** Turn `includeComments` on for the posts that cleared
  `minReactions` — you get commenter names and profile links.
- **Study what works.** Run one company at `maxPostsPerSource: 70`, then sort by
  `reactions` and group by `mediaType` and hour of `postedAt`.
- **Track one person's output.** Put them in `profiles`; about a dozen recent
  posts come back with the same columns.
- **Re-read one post.** Paste its link into `postUrls` to refresh its counts, or
  to pull its comments on their own.

### What it costs

Pay per event, and there are exactly two:

| Event | What it is | GOLD, per 1,000 |
|---|---|---|
| **Post** | One stored post, all its columns | **$1.35** |
| **Post with comments** | The extra request that fetches comment text | $1.35 |

**Comments are charged once per post, not once per comment.** A post with two
comments and a post with forty cost the same. Turning comments off — the default
— means that event never fires.

| Plan | Per 1,000 posts |
|---|---|
| Free | $2.00 |
| Bronze | $2.00 |
| Silver | $1.70 |
| Gold · Platinum · Diamond | $1.35 |

A run of 70 posts from one company is 7 requests and about 11 seconds. Apify's
$5 of monthly free credit is roughly **2,500 posts** on the free plan, or about
1,250 with comments on.

### Limits

Said plainly, because they are properties of what LinkedIn shows the public and
no scraper can talk them away.

- **70 posts per company page.** That is where LinkedIn's public feed stops. Ask
  for more and you still get 70; the run summary names every company that
  reached the ceiling, so a company that stopped at 70 is never confused with a
  company that has only 70 posts.
- **About a dozen posts per person.** A public profile shows a fixed window and
  offers no way to ask for more.
- **Comments are the ones a signed-out visitor sees** — usually the first
  handful, not all of them. The full count is in `comments` on every row
  regardless, and `commentsFetched` says how many actually came back.
- **No reactor list.** Counts and the top reaction types, not the names of the
  people who reacted.
- **Long-form articles are not posts.** A person's `/pulse/` articles are a
  different content type with no exact timestamp, and they are left out rather
  than mixed into the same columns.
- **A company's name is not its address.** Back Market lives at `back-market`.
  A wrong address is reported by name in the summary and never charged for.
- **Free-plan users** get everything the paid plans do, at the Free rate above,
  until their platform credit runs out; the run then stops cleanly and keeps
  what it collected.

### Output

CSV, Excel, JSON, XML or the API. Four ready-made views: **Posts** (what was
said), **Engagement** (how it did), **Authors** (who said it) and **Media**.
Every run also writes a `RUN_SUMMARY` with what was stored, what was filtered
out and why, which companies hit the ceiling, and how much traffic it used.

### FAQ

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

No. This reads the pages LinkedIn serves to a visitor who is not signed in.
There is no field to paste a session into, and nothing about your account is
involved.

#### How much does it cost to scrape 10,000 posts?

$13.50 on Gold, $20.00 on the Free plan. With comment text on, double it.

#### Why is a date filter cheaper than filtering afterwards?

Because the publication time is known before the row is stored. A post outside
your window is never charged for and never triggers the second request that
comments would need — so a repeating run costs what changed, not what exists.

#### Can I get all posts a company has ever published?

No, and no listing that reads the public page can. LinkedIn's public feed ends
at 70 posts per company. This one says so and reports which companies reached it.

#### Does it work for people as well as companies?

Yes, in the same run — `profiles` takes public profile addresses. The window
there is about a dozen recent posts, because that is all a public profile shows.

#### What happens on a post that no longer exists?

It is reported in the run summary by id and not charged for.

# Actor input Schema

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

One per line: `https://www.linkedin.com/company/stripe/`, `linkedin.com/company/stripe`, or just `stripe`. Up to **70 posts per company** — that is where LinkedIn's public feed ends, and the run summary names every company that reached it. **A company's name is not always its address**: Back Market lives at `back-market`. Open the company on LinkedIn and copy the part of the URL after /company/.

## `profiles` (type: `array`):

One per line: `https://www.linkedin.com/in/satyanadella` or just `satyanadella`. A person's public page shows **about a dozen recent posts and no more** — there is no second page to ask for, so this is a window rather than an archive.

## `postUrls` (type: `array`):

A post link copied from LinkedIn, in any form: the long `…-activity-7496332962540589056-vQLE` address, `https://www.linkedin.com/feed/update/urn:li:activity:7496332962540589056/`, or the bare id.

## `maxPosts` (type: `integer`):

Across every source. 0 means no limit. Free-plan runs are capped by your own Apify usage, so start small.

## `maxPostsPerSource` (type: `integer`):

70 is the most a company page will give and about a dozen is the most a person's page holds, so anything above 70 has the same effect as 70.

## `includeComments` (type: `boolean`):

Off by default because it is the expensive half: comments need a second request to a page ten times heavier than the post itself, and they are billed as a separate event. Every post already carries its **comment count** without this. What you get here is the comments LinkedIn shows a signed-out visitor — usually the first handful, not all of them.

## `maxComments` (type: `integer`):

How many comments to keep per post when the switch above is on.

## `postedAfter` (type: `string`):

`2026-08-01`, or a full timestamp. **This is the filter that makes a repeating run cheap**: the publication date is known before anything else is fetched, so an older post is skipped without being billed and without its comments being fetched.

## `postedBefore` (type: `string`):

`2026-08-31`, or a full timestamp. Both bounds include the day you name.

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

A post whose counter LinkedIn did not render is not treated as zero: it is skipped and reported separately in the run summary.

## `minComments` (type: `integer`):

Counted from LinkedIn's own total, not from how many comments are readable.

## `skipReposts` (type: `boolean`):

Keeps only what this company or person wrote themselves. A repost still names the original author in the row, so leave this off if you want to see who they amplify.

## `onlyWithText` (type: `boolean`):

Drops a repost shared without any words of its own.

## Actor input object example

```json
{
  "companies": [
    "stripe",
    "https://www.linkedin.com/company/figma/"
  ],
  "profiles": [
    "satyanadella",
    "https://www.linkedin.com/in/reidhoffman"
  ],
  "postUrls": [
    "https://www.linkedin.com/feed/update/urn:li:activity:7496332962540589056/"
  ],
  "maxPosts": 200,
  "maxPostsPerSource": 30,
  "includeComments": false,
  "maxComments": 10,
  "postedAfter": "2026-08-01",
  "postedBefore": "2026-08-31",
  "minReactions": 100,
  "minComments": 10,
  "skipReposts": false,
  "onlyWithText": false
}
```

# Actor output Schema

## `posts` (type: `string`):

No description

## `engagement` (type: `string`):

No description

## `authors` (type: `string`):

No description

## `media` (type: `string`):

No description

## `all` (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 = {
    "companies": [
        "stripe"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("lightmoon/linkedin-company-posts-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"] }

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

```

## MCP server setup

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