# LinkedIn Profile Data Scraper (`lightmoon/linkedin-profile-data-scraper`) Actor

Public LinkedIn profiles by URL or handle: name, headline, About, employer with its LinkedIn page and start year, past organisations, schools, city, followers and photo. No login, no cookie. A profile LinkedIn refuses comes back as a free row saying so, never a gap in your lead export.

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

## Pricing

from $3.60 / 1,000 profiles

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

Paste LinkedIn profile addresses, get one row per person: name, headline, the
About text, the current employer **with its LinkedIn page and the year they
started**, past organisations, schools with years, city, country, follower count
and photo.

No account, no cookie, no session to keep alive. The public profile page is read
as a signed-out visitor sees it.

### What one row looks like

```json
{
  "status": "ok",
  "profileUrl": "https://www.linkedin.com/in/williamhgates",
  "handle": "williamhgates",
  "fullName": "Bill Gates",
  "firstName": "Bill",
  "lastName": "Gates",
  "headline": "Chair, Gates Foundation and Founder, Breakthrough Energy",
  "about": "Chair of the Gates Foundation. Founder of Breakthrough Energy. Co-founder of Microsoft. Voracious reader. Avid traveler. Active blogger.",
  "badges": "Creator, Top Voice",
  "currentCompany": "Gates Foundation",
  "currentCompanyUrl": "https://www.linkedin.com/company/gates-foundation",
  "currentCompanyStartYear": 2000,
  "jobTitles": ["Co-chair", "Founder", "Co-founder"],
  "companies": [
    {"name": "Gates Foundation", "url": "https://www.linkedin.com/company/gates-foundation", "startYear": 2000},
    {"name": "Breakthrough Energy", "url": "https://www.linkedin.com/company/breakthrough-energy", "startYear": 2015},
    {"name": "Microsoft", "url": "https://www.linkedin.com/company/microsoft", "startYear": 1975}
  ],
  "schools": [
    {"name": "Harvard University", "url": "https://www.linkedin.com/school/harvard-university/", "startYear": 1973, "endYear": 1975}
  ],
  "city": "Seattle, Washington, United States",
  "country": "US",
  "followers": 40675985,
  "connections": "500+",
  "languages": [],
  "awards": [],
  "photoUrl": "https://media.licdn.com/dms/image/...",
  "maskedFields": [],
  "error": null,
  "scrapedAt": "2026-09-23T07:41:12+00:00"
}
```

25 fields. Views in the dataset tab: **Profiles**, **Lead list** (first name,
last name, company, location — the shape a CRM import wants), **Career**,
**Audience**, **All fields**.

### The two limits, stated up front

**1. About one profile in five is refused.** LinkedIn answers some requests from
a datacenter address with a sign-in wall under HTTP 999. The run retries and
moves to a different address, and measured on 2026-09-23 four profiles in five
come back whole on the first pass. The fifth arrives as a row saying so:

```json
{"status": "blocked", "profileUrl": "https://www.linkedin.com/in/someone",
 "error": "... answered 1530 bytes with no profile card in them ..."}
```

**A refused profile is never charged.** Run the list again and the gaps usually
fill; nothing is billed twice for the same person, because nothing was billed
the first time.

**2. LinkedIn sometimes withholds a field.** The same page can come back with an
employer written as `********** ** *******`. The row never carries the
asterisks — the field is empty and its name appears in `maskedFields`, so
"no employer" and "LinkedIn would not say" stay different answers.

### Input

| field | what it does |
|---|---|
| **LinkedIn profiles** | one per line: `https://www.linkedin.com/in/satyanadella`, a country address like `de.linkedin.com/in/...`, or just `satyanadella` |
| **Maximum profiles** | ceiling for one run |
| **Minimum followers** | drop people below the bar **before** the row is stored, so they are never charged |
| **Only profiles with a current employer** | drop students, retirees and profiles whose employer was withheld |
| **Include refused profiles as rows** | on by default; turn off for a clean file |

A company page pasted here is skipped with a note rather than failing the run —
companies are a different listing.

### What it does not do

- **It does not log in**, so it sees exactly what a signed-out visitor sees: no
  contact details, no email, no phone, no connection list.
- **It does not search.** Give it addresses; finding people by keyword is a
  different job.
- **A name is not an address.** LinkedIn handles are not always the person's
  name, and there is no lookup from one to the other without a search surface.

### Free plan

Everything works on the free plan: the same fields, the same limits. Refused and
filtered rows cost nothing there too.

# Actor input Schema

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

One per line. A profile link (`https://www.linkedin.com/in/satyanadella`), a country one (`https://de.linkedin.com/in/...`) or just the part after /in/ (`satyanadella`). A company page belongs in our company listing; pasted here it is skipped with a note, not charged.

## `maxProfiles` (type: `integer`):

A ceiling on one run, counted in profiles read. Refused and filtered profiles are never charged, whatever this says.

## `minFollowers` (type: `integer`):

Keep only people with at least this many followers. Judged from LinkedIn's own count before the row is stored, so a person below the bar is never charged. 0 keeps everybody.

## `onlyWithCurrentCompany` (type: `boolean`):

Drop people whose page lists no current organisation - students, retirees, and the profiles LinkedIn withheld the employer of. Dropped rows are free.

## `includeFailedProfiles` (type: `boolean`):

On by default: a profile LinkedIn would not serve comes back as a row with `status: blocked` and the reason, so a list of five hundred can be re-run against the gaps. Free either way.

## Actor input object example

```json
{
  "profiles": [
    "satyanadella",
    "williamhgates",
    "reidhoffman"
  ],
  "maxProfiles": 3,
  "minFollowers": 0,
  "onlyWithCurrentCompany": false,
  "includeFailedProfiles": true
}
```

# Actor output Schema

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

No description

## `leads` (type: `string`):

No description

## `career` (type: `string`):

No description

## `audience` (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 = {
    "profiles": [
        "satyanadella",
        "williamhgates",
        "reidhoffman"
    ],
    "maxProfiles": 3
};

// Run the Actor and wait for it to finish
const run = await client.actor("lightmoon/linkedin-profile-data-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 = {
    "profiles": [
        "satyanadella",
        "williamhgates",
        "reidhoffman",
    ],
    "maxProfiles": 3,
}

# Run the Actor and wait for it to finish
run = client.actor("lightmoon/linkedin-profile-data-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 '{
  "profiles": [
    "satyanadella",
    "williamhgates",
    "reidhoffman"
  ],
  "maxProfiles": 3
}' |
apify call lightmoon/linkedin-profile-data-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,lightmoon/linkedin-profile-data-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/4hlFo3L9L4XcQGeQ1/builds/RdBXTw7eIe0uVOea7/openapi.json
