# LinkedIn Open Profile Status Scraper (`khadinakbar/linkedin-open-profile-status-scraper`) Actor

Check which LinkedIn members accept free InMail from non-connections (Open Profile) and who publicly shows the #OpenToWork badge. Pass profile URLs or vanity names; get honest true/false/null verdicts per member with confidence and reason. Charged per profile checked plus platform usage.

- **URL**: https://apify.com/khadinakbar/linkedin-open-profile-status-scraper.md
- **Developed by:** [Khadin Akbar](https://apify.com/khadinakbar) (community)
- **Categories:** Social media, Lead generation, Automation
- **Stats:** 6 total users, 5 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event + usage

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## LinkedIn Open Profile Status Scraper

Check which LinkedIn members are genuinely **open to receiving messages from people outside their network** (the Open Profile / free-InMail setting) and, separately, which ones publicly display the **#OpenToWork** badge. Every profile input produces one dataset record carrying two independent booleans, so no buyer ever mistakes one signal for the other.

| Field | Meaning | Resolvable without login? |
|---|---|---|
| `openProfile` | Member accepts free InMail from non-connections (`true` / `false` / `null` = unknown) | For a definitive answer, yes — supply a `li_at` cookie; otherwise returns `null` |
| `openToWork` | Public **#OpenToWork** badge on the profile (`true` / `false` / `null`) | Yes — read from the public profile page |

Most "open profile" checkers on the Store read the public **#OpenToWork** badge and call it "Open Profile." Those are different signals. This actor keeps them apart and reports each one honestly, including when the answer is genuinely unknown.

***

### Best fit

Choose this actor when you need a per-person contactability verdict for sales outreach, recruiting, B2B lead enrichment, or AI-agent lead triage. It is designed for the person profile only and pairs cleanly with the [linkedin-company-profile-scraper](https://apify.com/khadinakbar/linkedin-company-profile-scraper): start with the company actor to research accounts, then feed this actor the decision-makers' profile URLs to see who you can reach directly.

It also works as a focused standalone workflow on its own, with no companion actor required: pass vanity names or full URLs, get back a structured contactability + job-seeker verdict for each.

| Good for | Example |
|---|---|
| Sales outreach | Prioritize the prospects who can receive your message without a connection request or InMail credit |
| Recruiting | Find candidates open to recruiter messages and actively job-seeking |
| Lead enrichment | Add a `contactable` / `openToWork` column to your list before import into outreach software |
| AI-agent triage | A clean, structured per-profile verdict your agent can filter on |

#### Use this when

- You need a **per-person** contactability verdict (free-InMail `openProfile`) and/or job-seeker flag (`openToWork`) for a bounded list of LinkedIn **person** profiles.
- You are enriching a known lead/candidate list before outreach and want an honest `true`/`false`/`null` per record, with `confidence` and `reason` you can audit.
- You have (or can obtain) your own `li_at` cookie and need a definitive free-InMail answer.

#### When not to use this actor

- You need **company / organization** data — use [linkedin-company-profile-scraper](https://apify.com/khadinakbar/linkedin-company-profile-scraper) instead; this actor is person-only.
- You are crawling **public LinkedIn search/feed surfaces** at scale — this actor only resolves explicitly supplied person URLs or vanity names and is not a search engine.
- You need a definite true/false with no unknowns — `null` is an honest, expected outcome (see Boundaries below), and is documented behavior.
- Your target data sits beyond what you are authorized to access, or at a scale beyond your rights — LinkedIn restricts bulk automated access.

***

### Workflow: from lead list to outreach list

A realistic run looks like this. A sales team **starts** with a raw lead list of profile URLs, runs this actor to score each person's contactability and job-seeker flag, **then** exports the marked-up CSV from the dataset, and **feeds** that file into their outreach sequence. Details on the reviewer side use `includeProfileBasics` to keep name and headline alongside the boolean verdicts, and `maxProfiles` keeps the batch (and the bill) predictable.

***

### Input

```json
{
  "profiles": [
    "https://www.linkedin.com/in/elonmusk",
    "satya-nadella",
    "https://www.linkedin.com/in/tim-cook"
  ],
  "maxProfiles": 100,
  "liAtCookie": "",
  "includeProfileBasics": true,
  "includeRawData": false
}
```

| Field | Type | Notes |
|---|---|---|
| `profiles` | string array | Full profile URLs **or** bare vanity names. Required. |
| `maxProfiles` | integer | 1–1000, default 100. Caps your spend. |
| `liAtCookie` | string (secret) | Your own LinkedIn session cookie. Recommended for a definitive `openProfile` (free-InMail) answer. Without it, `openProfile` stays `null` and only the public signals are returned. |
| `includeProfileBasics` | boolean | Attach name + headline. Default `true`. |
| `includeRawData` | boolean | Attach compact raw source for debugging. Default `false`. |

***

### Output

One dataset record per profile. Example:

```json
{
  "profileUrl": "https://www.linkedin.com/in/elonmusk",
  "vanityName": "elonmusk",
  "name": "Elon Musk",
  "headline": "CEO of X and SpaceX",
  "openProfile": null,
  "openProfileConfidence": "unknown",
  "openProfileReason": "Unauthenticated view - supply an li_at cookie for free-InMail (Open Profile) status.",
  "openToWork": false,
  "openToWorkConfidence": "medium",
  "openToWorkReason": "No public #OpenToWork badge signal found in a rendered profile view.",
  "source": "owned",
  "scrapeMethod": "owned-render",
  "scrapedAt": "<run timestamp>"
}
```

The default key-value store also receives `RUN_SUMMARY` and `OUTPUT`.

> **Reading the three states.** `true` and `false` are definitive. `null` is honest "unknown": for `openProfile` it usually means the run had no `li_at` cookie (an unauthenticated view does not reveal message permissions), and for `openToWork` it means the profile page did not fully render. Each value ships with a `confidence` and a `reason` so downstream logic can decide how much weight to give it.

***

### Pricing — Pay per event + platform usage

Charged per **profile checked** (each record saved to the dataset):

| Event | Price |
|---|---|
| Actor start | $0.00005 (once per run) |
| Profile checked | **$0.0035** |

You also pay **platform usage** (compute + proxy) on top of the per-event charges.

**Billing unit:** one `profile-checked` event = one dataset record persisted = `$0.0035`, regardless of how that profile resolved (owned render, provider, or honest `null` — the charge covers the check itself, and the verdict may honestly be `null`).

Worked examples for a bounded run:

- **100 profiles → 100 records:** 100 × $0.0035 = **$0.35** in events (+ the one-time $0.00005 start event) plus platform usage.
- **500 profiles → 500 records** (a typical monthly batch): 500 × $0.0035 = **$1.75** in events plus platform usage.

Set `maxProfiles` (capped at 1,000) to keep the bill predictable, and check the **live Pricing tab** on the Actor page for the current platform-usage rates.

***

### API example (curl)

```bash
curl "https://api.apify.com/v2/acts/khadinakbar~linkedin-open-profile-status-scraper/runs?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "profiles": ["https://www.linkedin.com/in/satya-nadella", "tim-cook"],
    "maxProfiles": 50
  }'
```

Then read the run's default dataset for results — the Output tab provides ready-made export URLs. Each record keeps its source URL (provenance), confidence, and collection timestamp for audit trails.

***

### AI agent / MCP prompt

> To check LinkedIn contactability, for each `profiles` entry return `openProfile` (accepts free InMail from non-connections; requires the `li_at` cookie to resolve, otherwise `null`) and `openToWork` (public #OpenToWork badge). Read the returned booleans from the default dataset, respect the per-record `confidence` and provenance `source URL`, and note the $0.0035 per-record cost plus platform usage before running at scale. Filter results on `openProfile=true` to prioritize outbound messages. Scope the run with `maxProfiles`; the dataset holds one record per profile with no pagination needed.

***

### Best results & practical guidance

- **Provide a valid `li_at` cookie** from your own account. This is the single biggest accuracy lever — it turns `openProfile` from `null` into a definitive `true`/`false`.
- Pass vanity names alongside full URLs; both work.
- Keep batches moderate (dozens to a few hundred) and schedule recurring runs at low frequency for LinkedIn-heavy workloads.
- Treat `openToWork=false` as "badge not shown" and confirm directly — a job-seeker may hide the badge while still being open to outreach.
- Validate output against the per-record `confidence` and `reason` before feeding it into automated decisioning.

***

### Boundaries, troubleshooting, and recovery

Honest scope of what this actor returns:

- **Top-tier celebrity profiles sit behind LinkedIn's login wall.** High-traffic profiles require an authenticated visitor, and managed providers return the same outcome — those records come back as honest `null` / unresolved. Everyday public profiles render directly and return real `openToWork` verdicts.
- **`openProfile` needs your `li_at` cookie.** An unauthenticated view never reveals message permissions, so without a cookie this field is `null` by design. Supply a valid `li_at` cookie from your own account to turn it into `true`/`false`.
- **Public renders are best-effort per profile.** The actor renders through residential sessions and retries with fresh exits; when LinkedIn still challenges a specific profile, the record reports a truthful `null` instead of guessing.
- **Recovery path:** if a batch returns mostly `null`, (1) add a valid `li_at` cookie, (2) reduce batch size / set `maxProfiles`, (3) retry after a cooldown, and (4) check the `RUN_SUMMARY` in the key-value store for `unresolved` / `providerDetermined` / `privatelyDetermined` counts before treating the run as complete.

***

### Provenance & verification

Every run writes a `RUN_SUMMARY` (outcome, per-source counts, cost) to the key-value store, and each dataset record carries its `source`, `scrapeMethod`, `confidence`, `reason`, and collection timestamp. Release verification — validator output, canary run IDs, and per-build acceptance evidence — is maintained in the actor's audit log (`release-evidence.json`) rather than in this README, so public copy stays current while evidence stays precise. Re-verify against a fresh run before relying on results for a live campaign.

***

### Builder's note

I built this actor after comparing store checkers that read the public **#OpenToWork** badge and label that person "Open Profile." I found that the genuine Open Profile signal — whether a member accepts free InMail from outside their network — only appears in an authenticated view; LinkedIn's Sales Navigator payload calls it `openLink`. So I designed this actor to return both signals as separate, honest three-state fields with a `confidence` and a `reason` on every value, and to charge only per profile record saved. Where the owned render is unavailable, it falls back to ScrapeCreators / SociaVault managed providers.

**Responsible use:** LinkedIn restricts bulk automated access. Use this tool for data you are authorized to access, at a scale you have rights to, and in line with LinkedIn's terms and applicable law.

# Actor input Schema

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

One or more public LinkedIn person profiles to check. Each item is either a full profile URL (e.g. https://www.linkedin.com/in/elonmusk) or just the vanity name (e.g. elonmusk). Not a company page - for companies use the linkedin-company-profile-scraper actor.

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

Maximum number of profiles to check and bill in this run. Accepts 1 to 1000; defaults to 100. Extra input values beyond this cap are ignored. Caps your spend at maxProfiles x the per-profile price.

## `liAtCookie` (type: `string`):

Your own LinkedIn session cookie li\_at value, used to detect the true Open Profile (free InMail) signal in an authenticated view. Without it openProfile stays null (unknown) and only the public #OpenToWork badge plus public signals are returned. Paste the value only, not the full Cookie header. Stored as a secret. Optional: leave empty for cookie-less public checks.

## `includeProfileBasics` (type: `boolean`):

Attach the member's public name and headline to each record. Defaults to true; set false for a leaner status-only record. Does not add a separate charge.

## `includeRawData` (type: `boolean`):

Add the compact raw source payload under rawData for debugging or custom parsing. Defaults to false. Increases record size noticeably; leave off for normal use.

## Actor input object example

```json
{
  "profiles": [
    "https://www.linkedin.com/in/elonmusk"
  ],
  "maxProfiles": 100,
  "includeProfileBasics": true,
  "includeRawData": false
}
```

# Actor output Schema

## `profiles` (type: `string`):

Structured dataset records for the checked LinkedIn profiles.

## `profilesCsv` (type: `string`):

CSV export of the same LinkedIn open profile status records.

## `runSummary` (type: `string`):

Machine-readable telemetry with profiles processed, sources used, provider status codes, and stop reason.

## `outputSummary` (type: `string`):

Compact agent-friendly summary with profiles checked, openProfile and openToWork counts, sources used, and stop reason.

# 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/elonmusk"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("khadinakbar/linkedin-open-profile-status-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": ["https://www.linkedin.com/in/elonmusk"] }

# Run the Actor and wait for it to finish
run = client.actor("khadinakbar/linkedin-open-profile-status-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": [
    "https://www.linkedin.com/in/elonmusk"
  ]
}' |
apify call khadinakbar/linkedin-open-profile-status-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,khadinakbar/linkedin-open-profile-status-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/4fyBh2lzIM7SeEmp8/builds/YdEdnv8LfbzXdCjVZ/openapi.json
