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

Scrape recent public LinkedIn company posts, text, reaction and comment counts, images, and source links without login.

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

## Pricing

from $1.40 / 1,000 company post delivereds

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

## LinkedIn Company Posts Scraper

Monitor the recent posts that LinkedIn makes publicly visible on company pages. This Actor returns post text, source-visible reaction and comment counts, attachment images, company identity, and public post links when available. Use it to follow competitors, track company announcements, or refresh a research dataset. Each run reads LinkedIn directly and creates a fresh observation for your chosen companies.

Start with `{"companyUrls":["https://www.linkedin.com/company/nasa/"],"maxItems":10,"maxPagesPerCompany":1}`. No LinkedIn login or cookies are needed. Download results as JSON, CSV, or Excel from the Dataset, or retrieve them through the Apify API. For a recurring workflow, save the input as a Task and schedule it.

**Price: $0.002 per delivered post ($2 per 1,000 posts)**, with Bronze 10%, Silver 20%, and Gold/Platinum/Diamond 30% discounts. There is no start fee. Source errors and coverage diagnostics are free.

A compact example from a recorded public NASA response is `{"postId":"7504184320702722048","reactions":1246,"comments":7,"publishedAt":null}`. These example counts are historical; your run returns a new observation. Missing source fields stay null. Coverage is limited to the public recent feed: this Actor does not promise a full archive or additional pages. Check the free OUTPUT summary before comparing snapshots.

![Public company post coverage dashboard](https://api.apify.com/v2/key-value-stores/IwI7VoxszQjT6v69X/records/linkedin-company-posts-scraper-0a019ed7d49a3e9c-readme-hero.webp?signature=17nCE2A1T7feuMQ3piIVf)

![Workflow: public company URL, bounded public feed pages, Dataset posts and OUTPUT coverage](https://api.apify.com/v2/key-value-stores/IwI7VoxszQjT6v69X/records/linkedin-company-posts-scraper-b92d7ed9a036224a-readme-workflow.webp?signature=1s5dor8bvYZn8qehi378r)

### What this Actor does

Each Dataset row is one useful post card currently returned by LinkedIn's public company page. It includes a stable activity-derived `postId`, the displayed company name, text, reactions/comments when the source exposes them, public attachment image URLs, a source page URL, and `observedAt`.

The Actor reads only `https://www.linkedin.com/company/{slug}/`. It blocks redirects, non-HTTPS URLs, other hosts, `/posts/` input routes, query strings, credentials, and private-address substitution. The page may expose a source-derived guest-feed URL. Current source verification found that route can repeat the first page, so this Actor does not promise later-page discovery; its useful contract is a bounded current public company-feed observation.

This is a fresh observation, not a complete archive. LinkedIn can omit, reorder, restrict, or change cards. A row missing from a later run is never treated as a deletion. `OUTPUT.coverage` tells you whether the bounded page/item limits ended collection or a source read failed.

### Quick start

```json
{
  "companyUrls": ["https://www.linkedin.com/company/nasa/"],
  "maxItems": 20,
  "maxPagesPerCompany": 2
}
```

The default is NASA because its public company page was used only to verify the parser against live public HTML. It is a real source URL, not demo data. Change it to a company page you are entitled to monitor.

### Inputs

| Field | Range | Meaning |
| --- | --- | --- |
| `companyUrls` | 1–20 | Exact public LinkedIn company roots only. Personal profiles and company subpaths are rejected before any source request. |
| `maxItems` | 1–200, default 20 | Run-wide maximum of delivered post rows. |
| `maxPagesPerCompany` | 1–5, default 2 | Maximum source-derived feed reads per company. Repeated or non-advancing routes are recorded as incomplete coverage; later-page discovery is not promised. |

### Dataset fields

| Field | Meaning |
| --- | --- |
| `postId` | Numeric activity ID extracted from the public card. |
| `postURL` | Public `/posts/` overlay URL only when the source exposes one that matches `postId`; otherwise `null`. |
| `company`, `companySlug` | Source-visible company display name and input company slug. Cards attributed to another organization are excluded. |
| `text` | Public card commentary. Empty cards are not delivered. |
| `reactions`, `comments` | Source-visible counts, or `null` when not provided. Missing is never converted to zero. |
| `images` | Public media attachment URLs from the card's attachment container; excludes avatars and reaction icons. |
| `publishedTimeText` | The source's relative or textual time, or `null`. |
| `publishedAt` | ISO timestamp only when the source has a parseable datetime attribute; otherwise `null`. |
| `sourceURL`, `observedAt` | Exact feed page that contained the card and the Actor's observation time. |

### Real public fixture example

A parser check against a public NASA company response observed ten unique cards. One card had `postId` `7504184320702722048`, company `NASA - National Aeronautics and Space Administration`, `reactions` `1246`, `comments` `7`, one attachment image, and source time `19h`. The source exposed a matching public `/posts/` link for nine of the ten cards. These are parser observations, not a claim that the posts or counts remain current.

```json
{"postId":"7504184320702722048","company":"NASA - National Aeronautics and Space Administration","reactions":1246,"comments":7,"publishedTimeText":"19h","publishedAt":null,"sourceURL":"https://www.linkedin.com/company/nasa/"}
```

To automate, submit the same bounded JSON to the Apify Actor API with your API token in an Authorization header, retain the returned run ID, wait for its terminal status, then GET that run's Dataset and `OUTPUT`. Check `response.ok`/HTTP status on every read and never retry an ambiguous run-start POST.

### OUTPUT and repeat workflow

`OUTPUT` is free and contains the outcome, per-company page evidence, source errors, item/page stopping reasons, and a snapshot made only from delivered rows. Dataset rows are the only billable results.

For a repeat snapshot, run the **same** `companyUrls`, `maxItems`, and `maxPagesPerCompany` again, save the new `OUTPUT`, and compare `snapshot.rows` by `postId` downstream. Keep the scope stable; different item or page depth is a different coverage boundary. This Actor does not claim historical completeness or infer removed posts.

### Coverage and errors

- `completed` means every configured company reached a source-exhausted bounded feed path.
- `partial` means a valid bounded read stopped at `maxItems`/page limit, or some companies failed while others yielded validated cards.
- `source_failed` means every company source read failed or was blocked; it is not an empty feed.
- `empty` means validated source reads yielded no useful post cards within the requested scope.
- `budget_stopped` means the verified run budget could not cover another result; when it is zero, no source request is made.

All source diagnostics are in `OUTPUT`, not the Dataset. A source failure, malformed response, redirect, empty card, duplicate, or unconfirmed delivery does not create a paid row.

### Pricing and budget

Pricing is **$0.002 per delivered post** via `result-found`, with Bronze 10%, Silver 20%, and Gold/Platinum/Diamond 30% discounts. There is no Actor-start fee and no default Dataset-item fee.

Set Apify's **Max total charge** before a run. The runtime checks the run budget before source collection and before each paid delivery. It does not retry an uncertain post write or event charge.

For API runs, use a **positive** `maxTotalChargeUsd`. In the verified platform response, passing `0` selected the account's available default limit; it did not mean “spend nothing.” A positive limit below one post's price stops this Actor before it reads LinkedIn.

### Task recipes

**Quick company check**

```json
{"companyUrls":["https://www.linkedin.com/company/nasa/"],"maxItems":10,"maxPagesPerCompany":1}
```

**Bounded multi-company batch**

```json
{"companyUrls":["https://www.linkedin.com/company/nasa/","https://www.linkedin.com/company/linkedin/"],"maxItems":50,"maxPagesPerCompany":2}
```

**Repeat observation**

```json
{"companyUrls":["https://www.linkedin.com/company/nasa/"],"maxItems":20,"maxPagesPerCompany":2}
```

### FAQ

**Does this log in or scrape personal profiles?** No. It makes bounded HTTPS reads of public company pages only and rejects login redirects.

**Why is `publishedAt` null?** LinkedIn may render only relative text such as `19h`. The Actor preserves that in `publishedTimeText` and does not invent an absolute timestamp.

**Why did I get fewer rows than requested?** There may be fewer public cards, a source page may be blocked, duplicate cards may repeat across pages, or the page/item boundary ended collection. Check `OUTPUT.coverage` before treating a short result as a business signal.

**Can I automate it?** Yes. Start an Actor run using the same JSON through Apify API, wait for a terminal run, then read its default Dataset and `OUTPUT`. If a client times out, inspect the original run before submitting another one because a delivery may already have been charged.

### Source contract

The collector has one source identity: a public LinkedIn company page. It does not accept a person, a company post permalink, a search result, an employee profile, a URL shortener, a saved browser session, or a cookie. This distinction matters because a page that appears public in one browser can redirect another request to sign-in. A redirect is a source failure, not permission to follow a different route.

A page card is included only when it has a LinkedIn activity identifier, non-empty public commentary, and a company link that matches the submitted slug. The source may contain sponsored cards, links in commentary, company logos, reaction graphics, duplicate cards, or cards attributed to another organization. Those do not become independent records. An attachment image must be inside the source's post-media container; profile avatars and reaction icons are excluded.

### Pagination boundary

The first company page may contain an opaque pagination token. The Actor does not compose that token, change its values, or discover a generic guest API route. It extracts and validates the complete route from received HTML before an optional bounded read. Current live verification found a valid route that repeated first-page IDs, so it is not marketed as pagination progress. A repeated URL or pagination cycle stops the company with an explicit coverage reason. At most five pages are ever read for one company.

If LinkedIn removes the pagination marker, the received page is treated as exhausted. If the page limit is reached while another source-supplied route exists, coverage remains partial. A page limit is a collection boundary, not evidence that later posts do not exist.

### Freshness and snapshots

`observedAt` records when this Actor read a particular page. It is not the post publication time. `publishedTimeText` preserves source wording such as `19h`; only a source datetime attribute is normalized to `publishedAt`. The Actor never estimates an absolute date from relative text.

Every `OUTPUT.snapshot` contains only rows actually delivered in that run. It is intentionally small and suitable for a downstream key-based comparison. Repeat runs must use identical `companyUrls`, `maxItems`, and `maxPagesPerCompany`; these are included in `snapshot.scope`. Different caps create different observable universes and cannot support a deletion conclusion.

A practical workflow is: store OUTPUT A, run the same bounded input later, store OUTPUT B, join the two snapshot arrays by `postId`, and label any difference as "not observed in this bounded run" until your own broader evidence supports a stronger statement.

### Safe automation pattern

Use one immutable input object per scheduled observation. Set an explicit max charge that covers only the number of posts you intend to buy. Start one run, retain its run ID, wait for a terminal status, retrieve Dataset rows and OUTPUT from that same run, then decide whether to schedule the next observation. Do not resend a timed-out POST: a Dataset write or named event may already have completed.

For API use, keep the Apify token in an Authorization header or secret manager. Do not put it in a company URL, a public Task input, a README screenshot, or source control. The LinkedIn company URLs themselves are not credentials, but only submit pages you are allowed to monitor and use the output according to the source and LinkedIn terms that apply to you.

### Partial-result decisions

A partial result can still contain useful validated cards. It does not establish whole-company completeness. Read `OUTPUT.coverage.companies` before combining pages, comparing days, or calculating engagement summaries. `page_limit` means there may be later public feed cards. `max_items` means the run-wide delivery cap ended the collection. `pagination_cycle` means a source token repeated. `sourceError` identifies an individual blocked or malformed company read without fabricating missing posts.

When all companies fail, `source_failed` is returned with zero paid rows. When the source is valid but no card satisfies the record contract, `empty` is returned. These outcomes have different operational meanings and should remain different in your pipeline.

### Billing examples

At the FREE tier, ten delivered posts cost $0.020 and twenty cost $0.040. Bronze is $0.0018/post, Silver $0.0016/post, and Gold, Platinum, and Diamond $0.0014/post. These figures describe named `result-found` rows only. Diagnostics, duplicate cards, unsupported URLs, source errors, missing metrics, and OUTPUT do not create result rows.

The runtime verifies the resolved pricing before source collection. It rejects a nonzero Actor-start price, a nonzero default-Dataset-item price, an unapproved post price, or an unexpected positive event. It also checks the run's charge limit before a source read and before every paid row. Treat the Pricing tab as authoritative once published.

### Limits and performance

The hard input maximum is 20 company URLs, 200 post rows, and five pages per company. One response is capped at 5 MiB. A request has a 15-second deadline; the collection reserves a shutdown margin inside a 120-second run. Network timeouts, 429, and 5xx responses may receive at most two bounded retries. Redirects, 4xx responses, content-type mismatches, invalid UTF-8, oversized bodies, and malformed pagination do not retry.

Those limits protect both the buyer budget and the source contract. They also mean the Actor is not a historical export tool or a promise to enumerate every post a company has ever made.

### Data-use notes

This product returns public company-feed facts that LinkedIn's unauthenticated response exposes at observation time. It does not collect private account data, invite-only posts, direct messages, personal profile timelines, applicant data, or engagement identities. Do not use it to make employment, credit, housing, insurance, legal, or other high-impact decisions about a person.

The post text and source URLs are source content. Retain only what you need, respect applicable terms and rights, and keep any downstream storage and distribution policy under your control. Source links are evidence of where the card was observed; they do not grant affiliation, endorsement, or a license beyond the source's terms.

### Operational checklist

Before a production observation, confirm that each value is an exact public company root, that `maxItems` and page depth match the comparison you intend to make, and that the run charge cap is intentional. After it finishes, inspect the terminal status and `OUTPUT.coverage` before interpreting the Dataset. Keep the run ID with any downstream export. If a source constraint changes, treat it as a new collection method rather than silently mixing it with prior snapshots.

# Actor input Schema

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

One to twenty public company URLs. Personal profiles, /posts paths, query strings, other hosts and non-HTTPS URLs are rejected before any source request.

## `maxItems` (type: `integer`):

Run-wide cap on useful, billable post rows. Set Max total charge too.

## `maxPagesPerCompany` (type: `integer`):

Maximum source-derived feed reads per company. Later-page progression is not guaranteed; repeated routes remain explicit incomplete coverage.

## Actor input object example

```json
{
  "companyUrls": [
    "https://www.linkedin.com/company/nasa/"
  ],
  "maxItems": 20,
  "maxPagesPerCompany": 2
}
```

# Actor output Schema

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

One billable linkedin\_company\_post row per useful delivered public post.

## `summary` (type: `string`):

Bounded source coverage, failures, delivery state and a snapshot of delivered rows only.

# 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 = {
    "companyUrls": [
        "https://www.linkedin.com/company/nasa/"
    ],
    "maxItems": 20,
    "maxPagesPerCompany": 2
};

// Run the Actor and wait for it to finish
const run = await client.actor("zinin/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 = {
    "companyUrls": ["https://www.linkedin.com/company/nasa/"],
    "maxItems": 20,
    "maxPagesPerCompany": 2,
}

# Run the Actor and wait for it to finish
run = client.actor("zinin/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 '{
  "companyUrls": [
    "https://www.linkedin.com/company/nasa/"
  ],
  "maxItems": 20,
  "maxPagesPerCompany": 2
}' |
apify call zinin/linkedin-company-posts-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,zinin/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/NeMDMsfYFVuwxagUq/builds/r8nSZMYuFKkCHQtej/openapi.json
