# LinkedIn Profile Reactions Scraper (`automation-lab/linkedin-profile-reaction-activity`) Actor

Find which LinkedIn posts a person recently reacted to. Give profile URLs, get each public reaction with post URL, text, author, publish time, engagement counts and media. Batch profiles, dedupe keys, since cutoff. No login or cookies.

- **URL**: https://apify.com/automation-lab/linkedin-profile-reaction-activity.md
- **Developed by:** [Automation Lab](https://apify.com/automation-lab) (community)
- **Categories:** Lead generation, Social media
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.96 / 1,000 reaction extracteds

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 Profile Reactions Scraper

Find out **which LinkedIn posts a person recently reacted to**. Paste one or more LinkedIn profile URLs and get each public reaction as a structured row: the reaction (like, celebrate, love…), the reacted post URL and text, the post author, when the post was published, its reaction and comment counts, and attached media.

No LinkedIn login, cookies or account are needed. The Actor reads the public **Activity → Reactions** tab that LinkedIn shows to logged-out visitors.

### What does LinkedIn Profile Reactions Scraper do?

For every profile you supply, the Actor:

1. opens the public LinkedIn profile page as a logged-out visitor;
2. reads the server-rendered **Reactions** tab of the profile's Activity section;
3. saves one row per reaction, newest first, with full post context;
4. adds one free outcome row when a profile has no public reactions, is not visible to logged-out visitors, or could not be loaded.

It answers "what is this person engaging with?", not "who reacted to this post?" and not "what did this person comment?".

### Who is it for?

- **Account-based sales teams** checking every week which topics, companies and people their target buyers are engaging with, to time outreach and find conversation openers.
- **Founders and creators** monitoring which posts influential people in their niche react to.
- **Recruiters** learning what candidates care about before reaching out.
- **Competitive and market researchers** seeing which vendor or competitor posts executives at key accounts like.
- **RevOps and data teams** feeding engagement signals into a CRM, warehouse or alerting workflow.

### Why use this LinkedIn reactions scraper?

- **Profile-first:** start from the people you care about, not from posts.
- **Batch many profiles in one run:** up to 1,000 unique profiles; duplicates are processed once.
- **Stable dedupe key:** `activityId` = `<profile ID>:<post ID>` stays identical across runs, so weekly runs can be upserted and diffed.
- **Incremental cutoff:** `since` keeps only reactions on posts published after a date or "7 days" ago.
- **Explicit outcomes:** every supplied profile ends as reactions or a free `no_public_reactions`, `profile_unavailable`, `access_failed` or `not_processed` row. A blocked page is never reported as "no reactions", and a profile the run could not reach is never silently dropped.
- **Exact post timestamps:** decoded from LinkedIn post IDs, not just "3d ago".
- **No account risk:** nothing logs in, so no LinkedIn account can be restricted.

### What LinkedIn reaction data is extracted?

| Field | Meaning |
| --- | --- |
| `status` | `reaction` for a reaction row; `no_public_reactions`, `profile_unavailable`, `access_failed` or `not_processed` for a per-profile outcome row (these outcome rows cost nothing) |
| `reactingProfileUrl`, `reactingProfileSlug`, `reactingProfileName` | The supplied person who reacted |
| `activityId` | Stable profile-plus-post key, e.g. `satyanadella:7520880308594917376` |
| `reactionType` | `LIKE`, `PRAISE` (celebrate), `EMPATHY` (love), `APPRECIATION` (support), `INTEREST` (insightful), `ENTERTAINMENT` (funny) or generic `REACTION` |
| `actionText` | LinkedIn's activity line, e.g. "Satya Nadella liked this" |
| `reactedPostId`, `reactedPostUrn`, `reactedPostUrl` | The post the person reacted to |
| `postText` | Text of the reacted post |
| `postAuthor` | Post author name, URL and type (person, company, showcase, school) |
| `postPublishedAt`, `postAge` | Exact post publication time and LinkedIn's relative age |
| `reactionCount`, `commentCount` | Engagement on the reacted post |
| `topReactionTypes` | Most common reaction types on the post |
| `mediaUrls` | Image and video-cover URLs of the post |
| `position` | Order on the profile's Reactions tab (1 = most recent) |
| `message` | Explanation on free outcome rows |
| `scrapedAt` | When the row was produced |

### How much does it cost to scrape LinkedIn profile reactions?

The Actor uses pay-per-event pricing. You pay only for reactions saved, plus a tiny start fee per run. Empty, unavailable and failed profile rows are free.

| Event | Free plan | Bronze | Silver | Gold+ |
| --- | ---: | ---: | ---: | ---: |
| Run start (once per run) | $0.001 | $0.001 | $0.001 | $0.001 |
| Reaction saved | $0.00184 | $0.0016 | $0.001248 | $0.00096 |

Examples at the Bronze price:

- **1 profile, 5 reactions:** $0.001 + 5 × $0.0016 = **$0.009**
- **25 prospects weekly, about 5 reactions each:** $0.001 + 125 × $0.0016 = **about $0.20 per week**
- **200 accounts, about 5 reactions each:** $0.001 + 1,000 × $0.0016 = **about $1.60**

The number of reactions per profile depends on how active the person is; observed profiles showed 1 to 8 public reactions. To cap spend, lower the per-profile limit or set a maximum cost per run in the run options.

### How to scrape the posts a LinkedIn user reacted to

1. Open the Actor in Apify Console and click **Try for free**.
2. Paste LinkedIn profile URLs (for example `https://www.linkedin.com/in/satyanadella/`) or bare profile IDs into **LinkedIn profile URLs**.
3. Optionally set **Maximum reactions per profile** and **Only posts published since** (for example `7 days`).
4. Click **Start**. A run with a few profiles usually finishes in under a minute.
5. Open the **Reactions** view of the dataset, or export JSON, CSV or Excel.
6. To monitor weekly, save the input as a Task and add a Schedule.

### Input parameters

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `profileUrls` | array of strings | required | Personal profile URLs (`linkedin.com/in/...`) or public profile IDs. Company, school and post URLs are rejected. Up to 1,000 unique profiles. |
| `maxItemsPerProfile` | integer 1–100 | 20 | Per-profile cap on saved reactions, newest first. |
| `since` | string | empty | Keep only reactions on posts published on/after this date: `2026-10-01`, an ISO timestamp, or `7 days` / `2 weeks`. |

Example input:

```json
{
    "profileUrls": [
        "https://www.linkedin.com/in/satyanadella/",
        "https://www.linkedin.com/in/jeffweiner08/"
    ],
    "maxItemsPerProfile": 20,
    "since": "30 days"
}
```

### Output example

A reaction row from a real run:

```json
{
    "status": "reaction",
    "reactingProfileUrl": "https://www.linkedin.com/in/satyanadella",
    "reactingProfileSlug": "satyanadella",
    "reactingProfileName": "Satya Nadella",
    "activityId": "satyanadella:7520880308594917376",
    "reactionType": "LIKE",
    "actionText": "Satya Nadella liked this",
    "reactedPostId": "7520880308594917376",
    "reactedPostUrn": "urn:li:activity:7520880308594917376",
    "reactedPostUrl": "https://www.linkedin.com/posts/judsonalthoff_every-quarter-gives-us-an-opportunity-to-activity-7520880308594917376-q9Zz",
    "postText": "Every quarter gives us an opportunity to step back and look at the progress we are making with our customers and partners around the world. ...",
    "postAuthor": {
        "name": "Judson Althoff",
        "url": "https://www.linkedin.com/in/judsonalthoff",
        "type": "person",
        "headline": null
    },
    "postPublishedAt": "2026-10-27T16:13:16.768Z",
    "postAge": "3d",
    "reactionCount": 1389,
    "commentCount": 94,
    "topReactionTypes": ["LIKE", "PRAISE", "EMPATHY"],
    "mediaUrls": ["https://media.licdn.com/dms/image/v2/D4E05AQGWHaILeegb1w/videocover-high/..."],
    "position": 1,
    "message": null,
    "scrapedAt": "2026-10-31T07:20:48.070Z"
}
```

A free outcome row for a profile without public reactions:

```json
{
    "status": "no_public_reactions",
    "reactingProfileUrl": "https://www.linkedin.com/in/garyvaynerchuk",
    "reactingProfileSlug": "garyvaynerchuk",
    "reactingProfileName": "Gary Vaynerchuk",
    "activityId": null,
    "reactionType": null,
    "message": "LinkedIn shows no public reaction activity for this profile to logged-out visitors.",
    "scrapedAt": "2026-10-31T07:20:52.376Z"
}
```

The key-value store also contains `RUN_SUMMARY` with counts of requested and processed profiles, reactions, outcomes and requests, plus `stopReason` and `notProcessedProfiles` when a run stopped early.

### Limits and how failures are reported

- **Recent reactions only.** LinkedIn shows logged-out visitors only a person's most recent public reactions on the profile's Reactions tab, usually 1 to 10. The Actor does not log in, so it cannot page through older reaction history. Run it on a schedule (for example weekly) to build a history over time; `activityId` makes deduplication easy.
- **Reaction time is not public.** `postPublishedAt` is when the post was published. The `since` filter uses that time.
- **Generic reactions.** When LinkedIn only says "reacted on this", `reactionType` is `REACTION`.
- **Hidden or missing profiles** return `profile_unavailable`. LinkedIn answers missing profiles the same way it answers blocked visitors, so the Actor confirms with two healthy connections that load other public profiles before it reports a profile as unavailable.
- **Temporary blocks** return `access_failed` after up to five fresh datacenter connections. These rows are free; retry later.
- **Run timeout.** The default timeout is 1 hour, enough for about 1,000 profiles. If the run reaches its timeout first, every profile that did not finish gets a free `not_processed` row, `RUN_SUMMARY.notProcessedProfiles` lists them, and the run ends as **failed** so schedules notice. Saved reactions stay in the dataset; rerun only the listed profiles.
- **Your maximum charge limit.** If the run reaches the maximum cost you set, it stops, lists the remaining profiles as `not_processed` rows and ends as **succeeded** with a status message, because you chose that limit. A profile that was cut off midway keeps its saved (charged) reactions; its `not_processed` row says how many were saved and that the rest were not charged. `RUN_SUMMARY.partiallyProcessedProfiles` lists those profiles.
- **Storage or billing errors.** If a charged row cannot be saved after three attempts, the run stops instead of fetching the profile again, so no reaction is charged or saved twice. The profile being saved and all remaining profiles get `not_processed` rows (the rows themselves are not charged), and the run fails. If a reaction was charged but its row could not be saved, that profile's row and `RUN_SUMMARY.chargedWithoutRow` say so. If even the outcome rows cannot be written, the run still fails and `RUN_SUMMARY` lists the profiles.
- If every profile in a run fails to load, the run ends as failed so schedules and integrations notice.

### Integrations and workflows

- **Weekly ABM signal feed:** schedule a Task with your target buyers and `since: "7 days"`; send new rows to Google Sheets, HubSpot or Salesforce through Make, Zapier or n8n.
- **Slack alerts:** trigger a webhook on run success and post reactions on competitor or partner posts to a channel.
- **Warehouse sync:** load the dataset to BigQuery or Snowflake and upsert on `activityId`.
- **Combine with other data:** enrich `postAuthor.url` with profile data, or pull the full post with a post scraper.

### Use the API

cURL:

```bash
curl -X POST "https://api.apify.com/v2/acts/automation-lab~linkedin-profile-reaction-activity/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"profileUrls":["https://www.linkedin.com/in/satyanadella/"],"since":"14 days"}'
```

JavaScript:

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/linkedin-profile-reaction-activity').call({
    profileUrls: ['https://www.linkedin.com/in/satyanadella/', 'jeffweiner08'],
    maxItemsPerProfile: 20,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.filter((row) => row.status === 'reaction'));
```

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("automation-lab/linkedin-profile-reaction-activity").call(
    run_input={"profileUrls": ["https://www.linkedin.com/in/satyanadella/"], "since": "7 days"}
)
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    if row["status"] == "reaction":
        print(row["reactionType"], row["reactedPostUrl"])
```

### Use with AI agents via MCP

Connect this Actor to Claude Code, Claude Desktop, Cursor or another MCP client through the Apify MCP server.

#### Claude Code

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/linkedin-profile-reaction-activity"
```

#### Claude Desktop

Add this server object to the Claude Desktop MCP configuration:

```json
{
    "mcpServers": {
        "apify": {
            "url": "https://mcp.apify.com?tools=automation-lab/linkedin-profile-reaction-activity"
        }
    }
}
```

#### Cursor

Add the same `apify` server under **Settings → MCP** in Cursor.

#### VS Code

Add the same Actor-specific URL to your VS Code MCP server configuration. Your client opens the Apify OAuth flow the first time it connects.

This Actor-specific URL also exposes the helper tools `get-actor-run`, `get-dataset-items`, `get-key-value-store-record` and `abort-actor-run`, so the server offers more than one tool. List the tools first and use only the ones you need. A reliable agent workflow:

1. **Start one run and keep its IDs.** Keep the returned run ID, status and dataset ID (`defaultDatasetId`). These are run details, not results. `call-actor` waits at most 45 seconds (`waitSecs` 0–45, default 30), so the run may still be going.
2. **Follow the same run.** If the run is not finished, call `get-actor-run` for that same run ID with short backoff (2, 4, 8, then 10 seconds) up to a deadline you choose, such as 120 seconds. Set the client request timeout longer than the server wait. After a client timeout, check the known run again instead of starting a new one. If you never got a run ID, report that rather than rerunning blindly. At the deadline, report the run as still running with its ID and stop polling.
3. **Report failures honestly.** If the run fails, times out or is aborted, any rows already in the dataset are a partial result, not a complete answer. Check `status` for `not_processed` rows and `RUN_SUMMARY` (`get-key-value-store-record`) for `stopReason`, `notProcessedProfiles` and `partiallyProcessedProfiles`.
4. **Read bounded pages.** When the run succeeds, call `get-dataset-items` with the dataset ID, an explicit `limit` and `offset`, and only the `fields` you need (for example `reactingProfileSlug,reactionType,postAuthor,reactedPostUrl,postPublishedAt`). Use pages of about 20 rows from offset 0 and stop at a budget such as 100 rows or 64 KiB of result text in the model's context, whichever comes first. Track the source offset, not how many rows you kept after filtering.
5. **Handle large rows.** `postText` can be long. If a page is larger than your byte budget, have the client trim or summarize it before it reaches the model and say so. If your client cannot inspect responses before the model sees them, request fewer rows and fields; it cannot guarantee a hard byte limit.
6. **Say when results are partial.** When you stop at a budget, report how many rows you read, the next offset and that the result is truncated. For a full export, download the dataset through the API or an integration outside the model's context.

These page sizes and deadlines are example choices, not Apify defaults. They limit what enters the model's context only; the Actor still collects and stores every row.

Example prompts:

- "Which posts did satyanadella and jeffweiner08 react to in the last 14 days? Group them by post author."
- "For these 20 prospect profiles, list any reactions to posts from our competitors."

### Is it legal to scrape LinkedIn reactions?

The Actor reads only information that LinkedIn shows to any logged-out visitor of a public profile. It does not log in, bypass privacy settings or access private data. Reaction data is still personal data: you are responsible for having a lawful basis (for example GDPR legitimate interest), respecting LinkedIn's terms, and not using the data for spam. If in doubt, ask your legal counsel. This Actor is not affiliated with or endorsed by LinkedIn.

### Data handling and error diagnostics

Requests pass through Apify Proxy to LinkedIn. Page bodies and guest session cookies are processed in memory only; results stay in your Apify run storage under your account's retention settings, and you can delete the dataset and key-value store at any time. The Actor keeps no external history or cache.

Failed operations send sanitized diagnostic input, exceptions and Actor/build/run IDs to our private GlitchTip service so we can fix problems. Secret fields and URL queries are removed, raw page bodies and cookies are never sent, and reports are retained for 30 days. Expected empty profiles and ordinary retries are not error reports.

### FAQ

**Why do I get only a few reactions per profile?**
LinkedIn shows logged-out visitors only the most recent public reactions on the profile (usually 1 to 10). Schedule the Actor to collect new reactions over time.

**Why did a profile return `no_public_reactions`?**
The profile loaded, but LinkedIn shows no public reactions for it. Some people hide their activity or rarely react.

**Why did a profile return `access_failed`?**
LinkedIn refused several fresh connections. The row is free; rerun later.

**Can I scrape company page reactions?**
No. Only personal profiles (`linkedin.com/in/...`) have a reactions activity tab.

**Does `since` filter by when the person reacted?**
No. LinkedIn does not publish reaction times, so `since` uses the reacted post's publication time.

### Related LinkedIn scrapers

- [LinkedIn Profile Posts Scraper](https://apify.com/automation-lab/linkedin-profile-posts-scraper): posts written by a profile.
- [LinkedIn Profile Activity Time Scraper](https://apify.com/automation-lab/linkedin-profile-recent-activity-time): when a profile was last publicly active.
- [LinkedIn Profile Scraper](https://apify.com/automation-lab/linkedin-profile-scraper): profile details for enrichment.
- [LinkedIn Post Scraper](https://apify.com/automation-lab/linkedin-post-scraper): full details of a post URL.

# Changelog

This Actor's version history is a separate document: https://apify.com/automation-lab/linkedin-profile-reaction-activity/changelog.md

# Actor input Schema

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

Personal LinkedIn profile URLs (https://www.linkedin.com/in/<id>) or bare public profile IDs such as satyanadella. Company, school and post URLs are rejected. Duplicate profiles are processed once. Up to 1,000 unique profiles per run.

## `maxItemsPerProfile` (type: `integer`):

Upper limit of reaction rows saved for each profile, newest first. This is a per-profile cap, not a run total. LinkedIn shows logged-out visitors only a person's most recent public reactions (usually 1 to 10), so a higher value cannot return more than LinkedIn exposes.

## `since` (type: `string`):

Optional incremental cutoff. Keeps only reactions on posts published on or after this date. Accepts 2026-10-01, a full ISO timestamp, or a relative value such as "7 days" or "2 weeks". LinkedIn does not publish when the reaction itself happened, so the cutoff uses the reacted post's publication time. Leave empty to keep every visible reaction.

## Actor input object example

```json
{
  "profileUrls": [
    "https://www.linkedin.com/in/satyanadella/",
    "https://www.linkedin.com/in/jeffweiner08/"
  ],
  "maxItemsPerProfile": 20,
  "since": "7 days"
}
```

# Actor output Schema

## `overview` (type: `string`):

Dataset rows: one per public reaction (status reaction) and one free outcome row per profile with no reactions, hidden/missing profile or access failure.

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

RUN_SUMMARY record with counts of profiles, reactions saved, outcome rows, requests and early-stop flags.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "profileUrls": [
        "https://www.linkedin.com/in/satyanadella/",
        "https://www.linkedin.com/in/jeffweiner08/"
    ],
    "maxItemsPerProfile": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/linkedin-profile-reaction-activity").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "profileUrls": [
        "https://www.linkedin.com/in/satyanadella/",
        "https://www.linkedin.com/in/jeffweiner08/",
    ],
    "maxItemsPerProfile": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/linkedin-profile-reaction-activity").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "profileUrls": [
    "https://www.linkedin.com/in/satyanadella/",
    "https://www.linkedin.com/in/jeffweiner08/"
  ],
  "maxItemsPerProfile": 20
}' |
apify call automation-lab/linkedin-profile-reaction-activity --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/linkedin-profile-reaction-activity"
        }
    }
}
```

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/C5k8rmEK7Wg8UPIFO/builds/x7je9QtYtwet4QFsT/openapi.json
