# LinkedIn Public Profile Scraper (No Login) (`usestring/linkedin-profiles`) Actor

Scrape public LinkedIn profiles by slug or /in/ URL, with no login and no cookies. Each row carries the full name, headline, About text, dated work history and education with start and end years, location, current company, languages, profile photo and an exact follower count.

- **URL**: https://apify.com/usestring/linkedin-profiles.md
- **Developed by:** [String](https://apify.com/usestring) (community)
- **Categories:** Lead generation, Social media, Jobs
- **Stats:** 3 total users, 1 monthly users, 96.2% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.25 / 1,000 results

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

This Actor collects public LinkedIn profiles. You give it profile slugs or `/in/` URLs and it
returns one row per person: the headline, the About text, work history and education with the years
attached, location, languages and an exact follower count. No LinkedIn account, cookie or session
token is used at any point.

### What does the LinkedIn Profile Scraper do?

LinkedIn publishes each public profile as schema.org JSON-LD inside the page, and this Actor reads
that structured data rather than handing rendered markup to a model. The difference shows up in
three places a buyer cares about.

Work history comes back dated. `positions` and `education` are arrays, each entry carrying the
organisation, its LinkedIn URL and `startYear` and `endYear`, so tenure is a subtraction rather than
a parse of "3 yrs 4 mos". Values are stable: the same profile read twice gives the same strings,
where the model-driven extraction this replaced returned a job title as "Chair" on one run and
"Co-chair" on the next. And counts are exact, so `followerCount` arrives as `40560540` rather than
"40M".

The profile page's JSON-LD graph also holds the person's articles and posts, each with a stub author
node. The Actor picks the `Person` node that actually carries profile fields instead of the first
one it finds, so a prolific poster does not come back as an empty row.

### What data does it extract?

| Field | Type | Description |
| --- | --- | --- |
| `handle` | string | The profile slug, e.g. `"janedoe"`. Your join key |
| `fullName` | string | Name as shown on the profile |
| `headline` | string | The short line under the name |
| `about` | string | The About section text |
| `jobTitles` | string\[] | Every title the profile lists, e.g. `["Founder", "Board Member"]` |
| `location` | string | City or region as stated on the profile |
| `country` | string | Country from the same address block |
| `currentCompany` | string | The first employer in `positions`, so the most recent one |
| `positions` | object\[] | Work history: `{ company, url, startYear, endYear }` per role |
| `education` | object\[] | Schools: `{ school, url, startYear, endYear }` per entry |
| `followerCount` | number | Exact follower count, not rounded |
| `languages` | string\[] | Languages listed on the profile |
| `imageUrl` | string | Profile photo URL |
| `sourceUrl` | string | The canonical profile URL that was read |
| `collectedAt` | string | ISO 8601 timestamp of the run |

An entry still in progress has `endYear` of `null`. Any field the profile does not publish is
`null` or an empty array, never a guess.

There are no email addresses and no phone numbers in this list, and the Actor does not look them up
anywhere else. What you get is what the public profile shows.

### Why scrape LinkedIn profiles?

Most of the value here is tenure. A job title on its own says very little; a title with a start year
next to it tells you whether someone just took the seat or has been in it for six years, and that is
usually what decides whether an outreach lands. Sales teams use it to enrich CRM records, recruiters
use it to sort candidates by time in role without opening a single profile.

Other things people run it for:

- Executive move tracking. Re-run a watchlist weekly and diff `currentCompany` against the top entry
  of `positions`.
- Org mapping before an account plan, from a list of slugs the team already has.
- Alumni and cohort lists, grouping people whose `education` years overlapped at the same school.

### How to use it

1. Open the Actor on Apify Store and click **Try for free**.
2. Paste your profiles into the **Profiles** field, one per line. Slugs (`janedoe`) and full URLs
   (`https://www.linkedin.com/in/janedoe`) both work, and the two forms for the same person collapse
   to a single fetch.
3. Click **Start**. Profiles are fetched in parallel, five at a time by default.
4. Export from the **Dataset** tab as JSON, CSV or Excel, or read it through the API. Slugs that did
   not resolve are listed in the `SUMMARY` record in the key-value store.

The Actor takes profiles you already have. It does not search LinkedIn, so pair it with a list you
built elsewhere: from a CRM export, a conference attendee list, or the companies in a funding
announcement.

### Input

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `profiles` | string\[] | required | Public profile slugs or `/in/` URLs, 1 to 500 per run |
| `maxItems` | integer | 1000 | Cap on dataset rows, 1 to 50000. Free plans stop earlier, see below |
| `concurrency` | integer | 5 | Profiles fetched in parallel, 1 to 10 |

```json
{
  "profiles": ["janedoe", "https://www.linkedin.com/in/john-smith-7a3b21"],
  "concurrency": 5
}
```

Only `linkedin.com` URLs are accepted. Anything else is rejected before it is fetched and reported
as a failed target, so a stray URL in a list of five hundred cannot route traffic somewhere else
under this Actor's pricing.

### Output

One row per profile. Illustrative values for a fictional person, not a captured run.

```json
{
  "handle": "janedoe",
  "fullName": "Jane Doe",
  "headline": "VP Engineering at Meridian Logistics",
  "about": "I build platform teams and the tooling that keeps them shipping...",
  "jobTitles": ["VP Engineering", "Engineering Manager"],
  "location": "Amsterdam",
  "country": "NL",
  "currentCompany": "Meridian Logistics",
  "followerCount": 4821,
  "positions": [
    { "company": "Meridian Logistics", "url": "https://nl.linkedin.com/company/meridian-logistics", "startYear": 2022, "endYear": null },
    { "company": "Bellweather Systems", "url": "https://www.linkedin.com/company/bellweather-systems", "startYear": 2017, "endYear": 2022 }
  ],
  "education": [
    { "school": "Delft University of Technology", "url": "https://nl.linkedin.com/school/tudelft", "startYear": 2010, "endYear": 2014 }
  ],
  "languages": ["Dutch", "English"],
  "imageUrl": "https://media.licdn.com/dms/image/v2/example/profile-displayphoto.jpg",
  "sourceUrl": "https://www.linkedin.com/in/janedoe",
  "collectedAt": "2026-08-20T09:31:02.118Z"
}
```

### Reliability

Measured across 30 profile slugs in one run: every profile that exists was collected, 19 of 19. The
other 11 were slugs that do not resolve on LinkedIn, and they were reported as failures rather than
emitted as empty rows.

Because fields come from the profile's own structured data, a value is either present and correct or
absent. It does not drift between runs for the same profile.

A profile that cannot be read lands in the run's `SUMMARY` under `failures` with the URL and the
error, and the rest of the batch still returns. A run where every profile failed exits with an
error, so a schedule notices instead of silently recording an empty day.

### Limitations

Public profile fields only. No connections, no posts or activity feed, no skills or endorsements, no
recommendations, and no search or discovery. Profiles set to private publish no JSON-LD, so they
return nothing and are reported as failures.

`positions` and `education` carry years, not months, because years are what the public profile's
structured data holds. Up to 500 profiles per run, one row each.

### How much does it cost?

Pay per event: you are charged per result row written to the dataset, plus Apify's own platform
usage for the run. The current per-result price is on this Actor's Store page under Pricing.

A slug that does not resolve writes no row and costs nothing, and duplicate entries for the same
person are fetched and billed once.

Runs started from an Apify **free plan** stop at 250 requests and 250 results, and say so in the
run's status message. Any paid plan runs the full input and whatever `maxItems` you set. The limit
exists because this Actor fetches through our own anti-bot infrastructure, which Apify does not
cover for free-plan runs. It binds on requests as well as rows so that a long input list cannot
spend those fetches on rows the run will never return.

### Using it with the Apify API

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

const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });

const run = await client.actor('usestring/linkedin-profiles').call({
    profiles: ['janedoe', 'https://www.linkedin.com/in/john-smith-7a3b21']
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
const thisYear = new Date().getFullYear();
for (const person of items) {
    const current = person.positions.find((role) => role.endYear === null);
    const years = current?.startYear ? thisYear - current.startYear : null;
    console.log(person.fullName, person.currentCompany, years === null ? '' : `${years} yrs in role`);
}
```

The Python client and the REST API take the same input. The run's `SUMMARY` record, with the item
count and every slug that failed, is in the default key-value store.

### Is it legal to scrape LinkedIn profiles?

This Actor reads public LinkedIn profiles as a logged-out visitor. It sends no account credentials,
no session cookie and no `li_at` token, and it reads nothing that a signed-in member can see but a
signed-out one cannot. It does not create accounts, send connection requests or interact with the
platform in any way.

It returns no contact details. Email addresses and phone numbers are not on the public profile, and
the Actor does not enrich, guess or look them up from another source.

What it does return is still personal data. Names, employers and education relate to identifiable
people, so GDPR, the CCPA and their equivalents apply to what you do with the rows once you have
them: you need your own lawful basis for holding and using them, and a person may have the right to
ask you to delete their record. Whether the collection itself is permitted is a separate question
from whether your intended use is, and the second one is the one that usually bites.

Please treat this section as orientation rather than legal advice. If people data is core to what
you are building, get your own.

### Related actors

- [TikTok Profile Scraper](https://apify.com/usestring/tiktok-profiles)
- [Indeed Job Listings Scraper with Salaries](https://apify.com/usestring/indeed-jobs)
- [Glassdoor Jobs Scraper - Pay and Ratings](https://apify.com/usestring/glassdoor-jobs)
- [Upwork Freelance Jobs and Budgets Scraper](https://apify.com/usestring/upwork-jobs)
- [Wellfound Startup Jobs Scraper](https://apify.com/usestring/wellfound-jobs)

A common pairing is this Actor with one of the job scrapers: the job board tells you a company is
hiring, and the profiles tell you who already runs the team.

### FAQ

**Do I need a LinkedIn account, cookies or a session token?**
No. Only the public, logged-out profile page is read, and no credentials of any kind are supplied.

**Does it return email addresses or phone numbers?**
No. Those are not on the public profile, and this Actor does no contact enrichment of any kind. If
you need contact data, that is a different tool and a different legal analysis.

**Does the work history include dates?**
Yes. Each entry in `positions` has `startYear` and `endYear`, with `endYear` `null` for a current
role, and `education` follows the same shape. Years, not months.

**Is the follower count exact?**
Yes. It comes from the profile's structured data as an integer, so you get `4821` rather than
"4.8K".

**Can I search LinkedIn for people to scrape?**
No. You supply the profiles. There is no keyword search, no filter by company or title, and no
discovery step in this Actor.

**What happens with a private profile or a slug that does not exist?**
It is recorded under `failures` in the run's `SUMMARY` and the rest of the batch still returns. A
private profile publishes no structured data, so there is nothing to read and nothing is invented in
its place.

### Feedback

If a profile came back with a field you expected to see filled, open an issue from this Actor's
Store page and include the slug. Requests for fields that are on the public profile but not in the
table above are worth sending too.

# Actor input Schema

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

LinkedIn public profile slugs or /in/ URLs.

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

Global cap on dataset items. Runs started from an Apify free plan stop at 250 requests and 250 results; any paid plan runs the full amount.

## `concurrency` (type: `integer`):

Targets fetched in parallel.

## Actor input object example

```json
{
  "profiles": [
    "williamhgates"
  ],
  "maxItems": 1000,
  "concurrency": 5
}
```

# Actor output Schema

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

Scrape public LinkedIn profiles by slug or /in/ URL, with no login and no cookies. Each row carries the full name, headline, About text, dated work history and education with start and end years, location, current company, languages, profile photo and an exact follower count.

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

Item count, failure count and every target that failed, with its error.

# 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": [
        "williamhgates"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("usestring/linkedin-profiles").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": ["williamhgates"] }

# Run the Actor and wait for it to finish
run = client.actor("usestring/linkedin-profiles").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": [
    "williamhgates"
  ]
}' |
apify call usestring/linkedin-profiles --silent --output-dataset

```

## MCP server setup

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

```

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/JaJHdggfjeUm3fLYf/builds/WacEhxlXeL0ZKzQrc/openapi.json
