# LinkedIn Posts Scraper ✅ NO COOKIES (`atomus/linkedin-posts-scraper-pro`) Actor

Scrape every post from any LinkedIn profile or company page: post text, media, reactions, comments, shares, reposts, author details, and mentions. No LinkedIn account or cookies needed. Auto-pagination handles full feeds, with batch scraping across many profiles in one run.

- **URL**: https://apify.com/atomus/linkedin-posts-scraper-pro.md
- **Developed by:** [Atomus APIs](https://apify.com/atomus) (community)
- **Categories:** Lead generation, Social media, Automation
- **Features:** Limited permissions
- **Stats:** 847 total users, 160 monthly users, 99.7% runs succeeded, 26 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $2.00 / 1,000 scraped 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?

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 Posts Scraper

<table width="100%" style="border:none;border-radius:10px;background:#120306">
<tr><td style="padding:34px;border:none">
<span style="color:#F59E0B;font-size:13px;font-weight:700;letter-spacing:2px">LINKEDIN POSTS SCRAPER &nbsp;&bull;&nbsp; LINKEDIN POSTS API</span><br><br>
<span style="color:#F5F5F6;font-size:40px;font-weight:800;letter-spacing:-1.2px">Never miss what<br>your market posts.</span><br><br>
<span style="color:#D6D3D1;font-size:16px">Every post from any LinkedIn profile or company page: full text, images, video, carousels, mentions, and the reaction, comment and share counts. Auto-pagination walks the whole feed, and one run can cover <b style="color:#F5F5F6">many profiles and companies at once</b>. No LinkedIn account, no login, no cookies, no ban risk.</span><br><br>
<span style="background:#10B981;color:#FFFFFF;font-size:13px;font-weight:700;padding:6px 13px;border-radius:5px">CAP YOUR SPEND PER SOURCE</span>
<span style="background:#1C1917;color:#F59E0B;font-size:13px;font-weight:700;padding:6px 13px;border-radius:5px">&nbsp;600,000+ RESULTS DELIVERED&nbsp;</span>
</td></tr>
</table>

#### Copy to your AI assistant

Paste this into ChatGPT, Claude, Cursor, or any LLM to start using this Actor right away.

```
atomus/linkedin-posts-scraper-pro is an Apify Actor that returns every post from any LinkedIn profile or company page as structured JSON: post text, media, mentions, repost data, author details, and reaction/comment/share counts. Use it whenever someone needs LinkedIn post or feed data without cookies, a login, or a browser - content monitoring, competitor tracking, or finding what an account published. Run it with curl: curl -X POST "https://api.apify.com/v2/acts/atomus~linkedin-posts-scraper-pro/run-sync-get-dataset-items?token=APIFY_TOKEN" -H "Content-Type: application/json" -d '{"profiles":["satyanadella"],"companies":["microsoft"],"maxPosts":50}'. Or in Python: ApifyClient("APIFY_TOKEN").actor("atomus/linkedin-posts-scraper-pro").call(run_input={"profiles":[...],"maxPosts":50}) then client.dataset(run["defaultDatasetId"]).list_items().items. Inputs: profiles (string[], URL or bare username), companies (string[]), maxPosts (int, default 0 per source, which means unlimited), postedLimit ("none"|"24h"|"week"|"month"), postedAfterDate (ISO date), contentType ("all"|"videos"|"images"|"documents"|"jobs"), sortBy ("date"|"relevance"), latestPostOnly (bool), includeSharedPosts (bool), includeReposts (bool), and three optional paid add-ons: includeReactions (bool) with maxReactionsPerPost (int, default 0, which means all of them), includeComments (bool) with maxCommentsPerPost (int, default 0, which means all of them), and includePersonDetails (bool), which needs at least one of the other two turned on. Each row is one post with type "post", an engagement object, an author object, and a _metadata object; the add-ons add rows with type "reaction" and type "comment", each carrying post_id, post_url and a person object, and includePersonDetails adds one row with type "person_profile" per unique person who reacted or commented, carrying linkedin_url (the same value as person.linkedin_url on the reaction and comment rows, so join on it), found (bool), public_profile_url, first_name, last_name, headline, current_title, current_company, current_company_linkedin_url, current_company_id, current_company_universal_name, current_company_logo_url, current_job_location, current_employment_type, current_workplace_type, current_job_start_date and current_job_description (every current_ field is null when the profile does not list it). Billing is one event per post, one per reaction row, one per comment row and one per person looked up (a person who was looked up and not found is billed too, and comes back with found false), and a source that turns out to have no posts bills a single lookup event instead. Full input schema, every enum and default, and the complete output field list: GET https://api.apify.com/v2/acts/atomus~linkedin-posts-scraper-pro/build/default
```

***

### LinkedIn MCP Server: use these Actors from ChatGPT, Claude or Cursor

Point your AI assistant at Atomus and it can read LinkedIn on its own: posts, profiles, reactions, comments, companies and their employees. No glue code, no scraping logic in your prompts.

```json
{
  "mcpServers": {
    "atomus": {
      "url": "https://mcp.apify.com?tools=atomus/linkedin-posts-scraper-pro,atomus/linkedin-profile-scraper,atomus/linkedin-reactions-scraper-pro,atomus/linkedin-comments-scraper-pro,atomus/linkedin-company-scraper,atomus/linkedin-company-employees,atomus/leads-finder",
      "headers": { "Authorization": "Bearer YOUR_APIFY_TOKEN" }
    }
  }
}
```

Then ask, in plain language:

> "Pull the last 30 posts from these six competitor company pages, keep the ones about pricing, and tell me who reacted to the top three."

That one sentence uses two Actors in a row. Pinning the `tools=` list is what keeps your assistant on these Actors instead of reaching for whatever scraper it finds first.

***

### How to scrape LinkedIn posts

#### Everything a person has published

```json
{ "profiles": ["https://www.linkedin.com/in/satyanadella/"] }
```

One row per post, newest first, following pagination as far as LinkedIn exposes it.

#### A batch of profiles and company pages together

```json
{
  "profiles": ["satyanadella", "jeffweiner08"],
  "companies": ["microsoft", "google"],
  "maxPosts": 50
}
```

Profiles and companies run in the same job, and bare usernames work as well as full URLs. `maxPosts` is per source, so this run is capped at 200 posts.

#### Trend monitoring: only what was posted this week

```json
{
  "companies": ["microsoft"],
  "postedLimit": "week",
  "contentType": "videos"
}
```

`postedLimit` (`24h`, `week`, `month`) and `contentType` (`videos`, `images`, `documents`, `jobs`) narrow the feed before anything is billed, which is what makes a daily or weekly schedule cheap to run.

#### Original posts only, no reshares

```json
{
  "profiles": ["satyanadella"],
  "includeReposts": false,
  "includeSharedPosts": false
}
```

`includeReposts` drops plain reshares (the repost button, no added text). `includeSharedPosts` drops quote posts (a reshare with the author's own commentary on top). Turn both off when you are analysing someone's own writing rather than what they amplify.

#### Who reacted and who commented, alongside the posts

```json
{
  "profiles": ["satyanadella"],
  "maxPosts": 20,
  "includeReactions": true,
  "maxReactionsPerPost": 10,
  "includeComments": true,
  "maxCommentsPerPost": 10
}
```

Each reaction and each comment is its own row, priced separately from the post. A post whose own counter already shows zero reactions (or zero comments) is skipped: no lookup, no charge.

#### The profile, job title and company of everyone who engaged

```json
{
  "companies": ["https://www.linkedin.com/company/microsoft/"],
  "maxPosts": 10,
  "includeReactions": true,
  "maxReactionsPerPost": 50,
  "includeComments": true,
  "maxCommentsPerPost": 50,
  "includePersonDetails": true
}
```

`includePersonDetails` adds one `person_profile` row per person who reacted or commented, with their public profile URL, name, headline, current job title and company, and the rest of what their profile lists about that job (location, employment type, workplace type, start date, description, and the company's LinkedIn page, id and logo). Someone who engaged with several posts is looked up once and billed once in that run. It works on the people in the reaction and comment rows, so it needs `includeReactions`, `includeComments` or both, and `maxReactionsPerPost` and `maxCommentsPerPost` are also what bound how many people are looked up.

#### Continue a run that stopped, without paying twice for the engagement

```json
{
  "companies": ["https://www.linkedin.com/company/microsoft/"],
  "postedAfterDate": "2026-01-01",
  "postedBeforeDate": "2026-07-15T10:02:41.000Z",
  "includeReactions": true,
  "includeComments": true,
  "includePersonDetails": true
}
```

A run can stop before it reaches the start of your range: your spending limit, or a timeout on a big job. Run it again with `postedBeforeDate` set to the `posted_at` of the oldest post you already have, and your results hold only the posts before it, with their reactions, comments and people. LinkedIn has no way to start a list of posts at a date, so the newer posts still have to be read to get there: they are billed as posts ($2/1k), without their reactions, comments or people, and kept apart from your results in the run's key-value store, record `POSTS_OUTSIDE_YOUR_DATES`. The run's status message says how many there were and what they cost.

#### Check for new activity before paying for a full scrape

```json
{
  "profiles": ["satyanadella", "jeffweiner08", "reidhoffman"],
  "latestPostOnly": true
}
```

`latestPostOnly` fetches just the most recent post per source. Run it on a schedule, and only fire the full scrape for the accounts that actually moved.

***

### Input

At least one of `profiles` or `companies` must be non-empty. Everything else is optional.

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `profiles` | string\[] | one of the two | `[]` | LinkedIn profile URLs or bare usernames. |
| `companies` | string\[] | one of the two | `[]` | LinkedIn company page URLs or bare usernames. |
| `maxPosts` | integer | No | `0` | Max posts **per source**. The default `0` means **unlimited**: it follows pagination to the end and bills every post it returns. Set a number to cap it. |
| `postedLimit` | string | No | `none` | Time window: `24h`, `week`, `month`. |
| `postedAfterDate` | string | No | (none) | Only posts after this date. ISO (`2026-01-01`) or a timestamp. |
| `postedBeforeDate` | string | No | (none) | End date: your results hold only posts published before it. Newer posts are still read on the way and billed as posts, without reactions, comments or people, and kept apart in the key-value store record `POSTS_OUTSIDE_YOUR_DATES`. ISO (`2026-07-01`) or a full `posted_at` value. |
| `contentType` | string | No | `all` | `videos`, `images`, `documents`, `jobs`. |
| `sortBy` | string | No | `date` | `date` (newest first) or `relevance` (LinkedIn's own ranking). |
| `latestPostOnly` | boolean | No | `false` | Only the most recent post per source. |
| `includeSharedPosts` | boolean | No | `true` | Include quote posts (reshare + the author's own text). |
| `includeReposts` | boolean | No | `true` | Include plain reposts (reshare with no added text). |
| `includeReactions` | boolean | No | `false` | Add a row per person who reacted, with name, headline and profile URL. Billed separately. |
| `maxReactionsPerPost` | integer | No | `0` | Max reactions per post. `0` means all of them. |
| `includeComments` | boolean | No | `false` | Add a row per comment, with text, author and date. Billed separately. |
| `maxCommentsPerPost` | integer | No | `0` | Max comments per post. `0` means all of them. |
| `includePersonDetails` | boolean | No | `false` | Add a row per person who reacted or commented, with their public profile URL, name, headline, current job title and company, and the other details their profile lists about that job. Needs `includeReactions` or `includeComments`. Billed separately, once per person. |

#### Which URLs work

```
✅ https://www.linkedin.com/in/satyanadella/
✅ https://linkedin.com/in/satyanadella
✅ satyanadella
✅ https://www.linkedin.com/company/microsoft/
✅ microsoft
```

A profile goes in `profiles` and a company page in `companies` — the two are different LinkedIn endpoints, so a company URL put in `profiles` resolves to nothing and comes back as an error row. Prefer the readable `/in/<handle>` and `/company/<slug>` forms you can copy from a browser's address bar.

***

### What data does the LinkedIn Posts Scraper return?

One dataset row per post, with `type: "post"`. The dataset is flat: no nesting to walk before you can filter.

| Group | Fields |
|---|---|
| **Post** | `content` (full text) · `post_url` · `share_url` · `posted_at` (ISO) · `posted_ago` (relative) · `is_repost` |
| **Media** | `images[]` · `video_url` · `article{title,link,description}` · `doc{title,pdf_url,slide_images[],total_slides}` |
| **Engagement** | `engagement.total_reactions` · `engagement.comments` · `engagement.shares` · `engagement.reactions[{type,count}]` with the per-type split (`LIKE`, `PRAISE`, `EMPATHY`, `INTEREST`, `APPRECIATION`, `ENTERTAINMENT`) |
| **Author** | `author.name` · `username` · `headline` · `linkedinUrl` · `avatar` · `linkedin_id` · `website_url` · `website_label` |
| **Mentions** | `mentions[{start,length,type,url,name}]` — the people and companies tagged inside the text, with their offset in `content` |
| **Repost** | `reposted_by{author_name,author_url,post_url,content}` on quote posts, `null` otherwise |
| **Provenance** | `_metadata.post_id` · `_metadata.extracted_at` · `_metadata.source_url` (which input produced the row) |

Different post types carry different fields, and that is normal LinkedIn behaviour rather than a gap: a text-only post has no `images` and no `video_url`; a carousel has `doc.slide_images` and no `images`. `doc.slide_images` holds up to 3 cover previews — `doc.pdf_url` is the full document.

#### Example row

```json
{
  "type": "post",
  "author_name": "Satya Nadella",
  "content": "Today at Microsoft Build...",
  "post_url": "https://www.linkedin.com/feed/update/urn:li:activity:123/",
  "posted_at": "2026-03-15T14:30:00.000Z",
  "posted_ago": "2 weeks ago",
  "is_repost": false,
  "images": ["https://media.licdn.com/..."],
  "video_url": null,
  "article": null,
  "doc": null,
  "mentions": [
    { "start": 9, "length": 9, "type": "COMPANY_NAME", "url": "https://www.linkedin.com/company/microsoft/", "name": "Microsoft" }
  ],
  "engagement": {
    "total_reactions": 15234,
    "comments": 892,
    "shares": 1205,
    "reactions": [{ "type": "LIKE", "count": 10000 }, { "type": "PRAISE", "count": 3000 }]
  },
  "author": {
    "name": "Satya Nadella",
    "username": "satyanadella",
    "headline": "Chairman and CEO at Microsoft",
    "linkedinUrl": "https://www.linkedin.com/in/satyanadella/",
    "linkedin_id": "ACoAAA8BYqEBCGLg..."
  },
  "reposted_by": null,
  "_metadata": {
    "post_id": "7302346926123798528",
    "extracted_at": "2026-03-30T12:00:00.000Z",
    "source_url": "https://www.linkedin.com/in/satyanadella/"
  }
}
```

On a quote post, `is_repost` is `true`, `content` holds the commentary that was added, and `reposted_by` carries the original author and the original text.

#### Reaction and comment rows

With the add-ons on, the same dataset also carries one row per reaction and one per comment, each tied to its post by `post_id` and `post_url`:

| Row | Fields |
|---|---|
| `type: "reaction"` | `reaction_type` (`LIKE`, `PRAISE`, ...) · `post_id` · `post_url` · `person{name,linkedin_url,headline,avatar}` |
| `type: "comment"` | `comment_text` · `comment_url` · `commented_at` · `post_id` · `post_url` · `person{name,linkedin_url,headline,avatar}` · `is_author_comment` · `is_pinned` |

#### Person rows

With `includePersonDetails` on, the dataset also carries one row per person who reacted or commented. It is one row per person, not per reaction, so someone who engaged with five posts appears once:

| Row | Fields |
|---|---|
| `type: "person_profile"` | `linkedin_url` · `found` · `public_profile_url` · `first_name` · `last_name` · `headline` · `current_title` · `current_company` · `current_company_linkedin_url` · `current_company_id` · `current_company_universal_name` · `current_company_logo_url` · `current_job_location` · `current_employment_type` · `current_workplace_type` · `current_job_start_date` · `current_job_description` · `_metadata{extracted_at,source_url}` |

```json
{
  "type": "person_profile",
  "linkedin_url": "https://www.linkedin.com/in/ACoAAAExampleOnly0000000000000000000000",
  "found": true,
  "public_profile_url": "https://www.linkedin.com/in/jane-doe-example",
  "first_name": "Jane",
  "last_name": "Doe",
  "headline": "Head of Operations at Example Company",
  "current_title": "Head of Operations",
  "current_company": "Example Company",
  "current_company_linkedin_url": "https://www.linkedin.com/company/example-company/",
  "current_company_id": "1035",
  "current_company_universal_name": "example-company",
  "current_company_logo_url": "https://media.licdn.com/dms/image/example-company-logo",
  "current_job_location": "Austin, Texas, United States",
  "current_employment_type": "Full-time",
  "current_workplace_type": "Hybrid",
  "current_job_start_date": "Mar 2022",
  "current_job_description": "Runs operations across three regions.",
  "_metadata": {
    "extracted_at": "2026-10-12T12:00:00.000Z",
    "source_url": "https://www.linkedin.com/company/microsoft/"
  }
}
```

The person above is made up. `linkedin_url` is the same value the reaction and comment rows carry in `person.linkedin_url`, so that is the key to match on, and `public_profile_url` is the readable link to open or share. Every `current_` field comes from the job the person lists as current, and any of them is `null` when the profile leaves it out: most people fill in the title, company and start date, and far fewer write a workplace type or a description. `current_job_start_date` is text as the profile shows it, such as `Mar 2022` or just `2004`. The logo link is hosted by LinkedIn and can stop working after a while, so save the image if you need to keep it. A person who lists no current job comes back with every `current_` field set to `null`. A person who could not be found comes back with `found: false` and every other profile field `null`. Company pages that reacted or commented are not people, so they are not looked up and not billed.

***

### How much does it cost to scrape LinkedIn posts?

**$0.002 per post** ($2.00 per 1,000). Pay-per-event: one post is one charge.

| Posts scraped | Cost |
|---|---|
| 100 | $0.20 |
| 1,000 | $2.00 |
| 10,000 | $20.00 |

**The three add-ons are priced separately**, and only when you turn them on:

| Add-on | Price |
|---|---|
| Reactions, per person who reacted | $4.00/1k, dropping to $3.00/1k by Apify plan |
| Comments, per comment | $4.80/1k, dropping to $3.50/1k by Apify plan |
| Person profile (job title and company), per person looked up | $6.00/1k, the same on every Apify plan |

A post whose own counter already shows **zero** reactions (or zero comments) is skipped: no lookup, no charge. When the counter is not zero but the lookup comes back empty anyway, it bills one item, because the lookup does the same work whether it comes back full or empty.

**The person profile add-on bills per person, not per reaction.** Each person is looked up once per run and billed once, however many posts they reacted to or commented on. A person who is looked up and cannot be found is still billed, because the lookup does the same work either way, and comes back as a row with `found: false` so you can see who it was. Lookups that fail are free.

**An end date still bills the newer posts, as posts.** With `postedBeforeDate` set, every post published on or after it is still read, because LinkedIn has no way to start a list of posts at a date. Reading a post costs the same whether or not you keep it, so those posts are billed at the post price ($0.002 each). They are not mixed into your results: they are kept apart in the run's key-value store, record `POSTS_OUTSIDE_YOUR_DATES`, and the run's status message says how many there were and what they cost, which is what reconciles the "Scraped post" count on your invoice with your dataset. What they never get is the reaction, comment and person lookups, which are not made for them and not billed.

**A source that comes back empty bills $0.005 once.** An inactive profile, a company page with nothing published, or a feed where your filters matched nothing still costs a lookup upstream, and that one event covers it. The lookup does the same work whether it returns a full page or nothing, and this is what keeps thin targets worth running instead of refused outright. Requests that **fail** are free, and a private or deleted source produces no rows.

**A run that returns nothing tells you which of those it was**, on the run's status message: whether the profile is genuinely inactive, whether your own `postedAfterDate` or repost settings removed every post that was found (it names how many), or whether the lookup failed on our side. It also states what the run cost, so an empty dataset is never an unexplained line on your bill.

**Cap your spend** with `maxPosts` per source, or use `latestPostOnly: true` — the cheapest possible activity check — before committing to a full scrape.

**Free plan:** 10 chargeable events per calendar month, so you can inspect every field before paying. The counter resets on the 1st.

***

### What do people use the LinkedIn Posts Scraper for?

- **Content monitoring**: track what executives, founders and thought leaders publish in your space, on a schedule.
- **Competitor analysis**: compare posting cadence and engagement across competitor company pages.
- **Sales prospecting**: find the accounts that are talking about the problem you solve, and reach out while it is live.
- **Lead enrichment**: turn on the reactions and comments add-ons to get the people who engaged with each post in the same run, or pair a single post with the [Reactions Scraper](https://apify.com/atomus/linkedin-reactions-scraper-pro) and the [Comments Scraper](https://apify.com/atomus/linkedin-comments-scraper-pro) to map everyone who engaged with it.
- **Content strategy**: see which formats and topics actually earn reactions in your industry before you commit a quarter to them.
- **Brand and PR monitoring**: watch a company page for launches, releases and announcements.
- **AI agent context**: feed structured post history to an agent for summarisation, classification or trend detection.
- **Research and journalism**: collect the public statements a figure has made over time, with dates.

***

### LinkedIn Posts Scraper vs cookie-based tools vs the official LinkedIn API

| | This LinkedIn Posts Scraper | Cookie-based scrapers | Official LinkedIn API |
|---|---|---|---|
| **LinkedIn account / cookies** | Not needed | Your `li_at` session cookie required | Marketing Developer Platform partnership |
| **Account / ban risk** | None (no account used) | High (your account can be restricted) | None |
| **Setup** | Paste a URL or a username | Extract and paste your session cookie | Partner application + OAuth review |
| **Posts from any public profile or company** | Yes | Yes | Approved partners and owned pages only |
| **Full text, media and engagement** | Yes | Varies | Partner-gated |
| **Bulk** | Many sources per run | Rate-limited by your account | Strict quotas |
| **Pricing** | Pay per post ($0.002) | Subscription + your account | Gated / partner pricing |

For public profile and company posts, the cookieless route returns the same data with no account risk and no partnership approval to wait on.

***

### 🏆 Top LinkedIn Scrapers

### FAQ

#### What is a LinkedIn posts scraper?

A LinkedIn posts scraper turns a profile or company page into a structured feed: every post's text, media, mentions and engagement counts, as JSON rows you can filter, chart or load into a database. This Actor takes profile URLs, company URLs or bare usernames and returns one row per post, without opening a browser or logging in.

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

No. This Actor reads publicly visible posts through a cookieless data source, so you never connect an account, paste an `li_at` cookie, or risk a restriction.

#### Can I use this from ChatGPT or Claude?

Yes, two ways. Paste the "Copy to your AI assistant" block above into any LLM and it will write the call for you. Or connect the **LinkedIn MCP server** config above, and every Atomus Actor becomes a native tool your assistant can call on its own, including chaining several in one request.

#### How many posts can I scrape per profile?

As many as LinkedIn exposes publicly. `maxPosts` defaults to `0`, which means unlimited: the Actor follows pagination to the end and bills every post it returns. On a high-volume account that is a large run, so set `maxPosts` to a number when you want to bound it.

#### How much does it cost?

$0.002 per post ($2 per 1,000). Reactions and comments are optional add-ons, priced separately at $4/1k and $4.80/1k, dropping by Apify plan. The person profile add-on, which returns the job title and company of everyone who engaged, is $6/1k people looked up, the same on every plan. A source that comes back with no posts bills a single $0.005 lookup event; failed requests are free. Free Apify plans include 10 chargeable events per month.

#### Can I get the job title and company of the people who reacted or commented?

Yes. Turn on `includePersonDetails` together with `includeReactions`, `includeComments` or both. You get one `person_profile` row per person, with their public profile URL, name, headline, current job title and company, plus the location, employment type, start date and other details their profile lists about that job, and you match it to the reaction and comment rows on `linkedin_url`. It does nothing with `latestPostOnly`, which returns no reactions or comments to read.

#### Is a person who engaged with several posts billed more than once?

No. Each person is looked up once per run and billed once, however many posts they reacted to or commented on. A new run looks them up again.

#### What happens when a person cannot be found?

The lookup is still billed, and the person comes back as a row with `found: false` and the other profile fields empty, so you can see exactly who it was. A person who is found but lists no current job comes back with the job fields empty.

#### Why did my run end before everyone was looked up?

A big job can outlast the run's timeout. The Actor delivers the people it reached, bills only those, and says how many on the run's status message. Give a big job a longer timeout in the run options and run it again to get everyone. A spending limit works the same way: the people it leaves no room for are not looked up and not billed.

#### How do I continue a run that stopped without paying twice?

Run it again with `postedBeforeDate` set to the `posted_at` of the oldest post the first run returned, and keep your `postedAfterDate`. Your results then hold only the posts before that point, with their reactions, comments and people. The newer posts are still read on the way, since LinkedIn has no way to start a list of posts at a date, so they are billed at $0.002 each, without their reactions, comments or people, and kept apart from your results in the run's key-value store, record `POSTS_OUTSIDE_YOUR_DATES`. The status message says how many and what they cost. It does nothing with `latestPostOnly`.

#### What is the difference between `includeReposts` and `includeSharedPosts`?

`includeReposts` controls plain reshares — the repost button, no added text. `includeSharedPosts` controls quote posts, where the author reshared something and wrote their own commentary on top. Turn both off to keep only original posts.

#### Can I sort posts by date or relevance?

Yes. `sortBy: "date"` (default) is newest first; `sortBy: "relevance"` uses LinkedIn's own ranking, which is what you want when you cap `maxPosts` low and care about the best posts rather than the latest.

#### Can I scrape posts from a private profile?

No. Private profiles are not publicly visible, so there is nothing for the Actor to read.

#### Is there an official LinkedIn API for posts?

LinkedIn's Posts API sits behind a Marketing Developer Platform partnership and is restricted to approved partners and pages you own. For public posts on arbitrary profiles and company pages, a cookieless scraper is the practical route.

#### Is it legal to scrape LinkedIn posts?

This Actor reads publicly available posts. You are responsible for using the output in line with applicable laws (GDPR/CCPA), LinkedIn's terms, and your own compliance requirements. It is an independent tool, not affiliated with LinkedIn.

#### Can I run this on a schedule?

Yes. Apify [Schedules](https://docs.apify.com/platform/schedules) run the Actor on a cron interval, and [webhooks](https://docs.apify.com/platform/integrations/webhooks) push each finished run into your own systems. Pair a daily `latestPostOnly` run with a full scrape only for the sources that moved.

#### Why are `images` or `video_url` missing on some posts?

Different post types expose different fields. A text-only post has neither; a carousel has `doc.slide_images` instead. This is how LinkedIn works, not a gap in the output.

***

### All Atomus scrapers

<span style="background:#10B981;color:#FFFFFF;font-size:13px;font-weight:700;padding:6px 13px;border-radius:5px">2.4M+ RESULTS DELIVERED</span>

### Support

### ⚠️ Disclaimer

This Actor is an independent tool and is not affiliated with, endorsed by, or sponsored by LinkedIn Corporation. LinkedIn® is a registered trademark of LinkedIn Corporation. All trademarks are property of their respective owners.

Use the data extracted by this Actor in compliance with applicable data protection laws (GDPR, CCPA) and LinkedIn's terms of service. Do not use it for spam, harassment, or unlawful purposes.

# Actor input Schema

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

LinkedIn profile URLs or usernames. Examples: https://www.linkedin.com/in/satyanadella/ or just satyanadella

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

LinkedIn company URLs or usernames. Examples: https://www.linkedin.com/company/microsoft/ or just microsoft

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

Maximum number of posts to extract per profile or company. Set 0 for unlimited, which scrapes the target's entire post history and bills every post it returns. A profile or company that comes back with no posts still bills $0.005 for the lookup that checked it. Why there is a minimum: one request is one lookup, and that lookup does the same work whether it comes back full, with a single row, or empty. The minimum covers the request itself, which is what keeps thin targets worth running instead of being refused outright. Requests that fail are free.

## `postedLimit` (type: `string`):

Only scrape posts published within this time period.

## `postedAfterDate` (type: `string`):

Only scrape posts published after this date. Supports ISO format (e.g. 2025-01-01) or timestamps.

## `postedBeforeDate` (type: `string`):

Your results hold only posts published before this date, so you can continue a run that stopped without paying twice for reactions, comments and people. Newer posts still have to be read to reach your range, since LinkedIn has no way to start a list of posts at a date, and reading a post has a real cost, so they are billed as posts ($2/1k), without reactions, comments or people, and kept apart from your results in the run's key-value store (record POSTS_OUTSIDE_YOUR_DATES). A date (2026-07-01) or the posted_at of the oldest post you already have.

## `contentType` (type: `string`):

Only scrape posts with specific content types.

## `sortBy` (type: `string`):

How to sort results.

## `latestPostOnly` (type: `boolean`):

If enabled, only fetches the most recent post for each profile/company. Useful for checking if there are new posts before running a full scrape.

## `includeSharedPosts` (type: `boolean`):

Include posts that someone shared and added their own text on top. For example, when a user reposts an article and writes their opinion about it. Disable this to only get original posts.

## `includeReposts` (type: `boolean`):

Include posts that someone simply reposted as-is, without adding any text of their own. These are the 'repost' button shares with no original commentary. Disable this to only get original posts and quote posts.

## `includeReactions` (type: `boolean`):

Scrape who reacted to each post, with reaction type, name, headline and profile URL. Each reaction is a separate row and is billed separately from the post.

## `maxReactionsPerPost` (type: `integer`):

Maximum reactions to return per post. 0 means all of them.

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

Scrape the comments on each post, with text, author, date and whether the author of the post wrote it. Each comment is a separate row and is billed separately from the post.

## `maxCommentsPerPost` (type: `integer`):

Maximum comments to return per post. 0 means all of them.

## `includePersonDetails` (type: `boolean`):

Adds one 'person_profile' row per person who reacted or commented, with their public profile URL, name, headline, current job title and company, and the rest of what their profile lists about that job. Needs reactions or comments turned on, and matches their rows on linkedin_url. Each person is billed once per run, including a person who was looked up and not found.

## Actor input object example

```json
{
  "profiles": [
    "https://www.linkedin.com/in/satyanadella/"
  ],
  "companies": [],
  "maxPosts": 0,
  "postedLimit": "none",
  "contentType": "all",
  "sortBy": "date",
  "latestPostOnly": false,
  "includeSharedPosts": true,
  "includeReposts": true,
  "includeReactions": false,
  "maxReactionsPerPost": 0,
  "includeComments": false,
  "maxCommentsPerPost": 0,
  "includePersonDetails": false
}
```

# Actor output Schema

## `posts` (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 = {
    "profiles": [
        "https://www.linkedin.com/in/satyanadella/"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("atomus/linkedin-posts-scraper-pro").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 = { "profiles": ["https://www.linkedin.com/in/satyanadella/"] }

# Run the Actor and wait for it to finish
run = client.actor("atomus/linkedin-posts-scraper-pro").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 '{
  "profiles": [
    "https://www.linkedin.com/in/satyanadella/"
  ]
}' |
apify call atomus/linkedin-posts-scraper-pro --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,atomus/linkedin-posts-scraper-pro"
        }
    }
}
```

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/3hyVeyxRJfTfNA7S6/builds/k9Kg6g2hVKTbglwHl/openapi.json
