# LinkedIn Jobs Scraper (`solidcode/linkedin-jobs-scraper`) Actor

\[💰 $0.95 / 1K] Extract LinkedIn job postings at scale — title, company, location, salary, description, seniority, employment type, applicant count, and recruiter details. Search by keyword and location with date, job-type, experience-level, and remote filters, or paste LinkedIn search URLs.

- **URL**: https://apify.com/solidcode/linkedin-jobs-scraper.md
- **Developed by:** [SolidCode](https://apify.com/solidcode) (community)
- **Categories:** Jobs, Developer tools, Automation
- **Stats:** 56 total users, 15 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.95 / 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.

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

Pull public LinkedIn job postings at scale — title, hiring company, location, full description, seniority, employment type, job function and industry on every single row, plus applicant counts, listed pay and the recruiter behind the role on the postings that publish them, and optional company profiles. Search with plain keyword + location fields (no hand-built URLs) and get one clean row per job. Built for recruiters, sourcers, talent-market analysts, and job-board builders who need fresh LinkedIn job data without manual copy-paste or maintaining a LinkedIn login.

### Why This Scraper?

- **No LinkedIn login or cookies required** — runs entirely against LinkedIn's public guest job listings, so there's no account to connect, no session to babysit, and a much lower compliance footprint.
- **Plain keyword + location search, not URL hacking** — type "data engineer" and "United States" instead of hand-crafting a LinkedIn URL; power users can still paste raw LinkedIn job-search URLs, and the run tells you by name if a pasted URL uses a filter LinkedIn's public search doesn't apply.
- **Company-level targeting from a plain LinkedIn page address** — paste `linkedin.com/company/microsoft` and get only that employer's postings; stack several companies to watch a whole competitor set in one run.
- **Four recency windows down to "Past 24 hours"** — combine with Most-recent sort to build a daily feed of brand-new postings, ordered by real posting date rather than LinkedIn's own shuffle.
- **Seniority, employment type, job function and industry on every row** — read straight off each posting, so you can slice the export by any of them after the fact in your spreadsheet or database.
- **Recruiter contact block pulled out whenever LinkedIn names a job poster** — name, title, photo, and LinkedIn profile URL, so warm outreach reaches a person instead of a careers inbox. Roughly 1 posting in 10 names one, and senior and executive roles name one far more often (about 1 in 4).
- **Listed pay split into a structured min/max pair you can sort on** — about 1 posting in 5 publishes pay, rising to roughly 1 in 2 in pay-transparency markets such as New York; you also get the live applicant count (present on about two thirds of postings) and the employer's Featured Benefits list on the minority of roles that publish one.
- **Optional one-toggle company enrichment** — flip on `scrapeCompanyDetails` to append each company's description, website, employee count, and industry to the job row.
- **Full job description in both plain text and HTML** — `descriptionText` for analysis and `descriptionHtml` to preserve formatting, plus job function and industry classification.
- **Up to ~1,000 jobs per search with smooth pagination** — pages straight through LinkedIn's guest ceiling, and de-duplicates jobs that appear across overlapping searches.

### Use Cases

**Recruiting & Sourcing**

- Build daily feeds of newly posted roles in your niche and territory
- Capture the recruiter or job poster behind a role whenever LinkedIn names one
- Track which companies are actively hiring for a given title
- Prioritize roles by applicant count so your outreach lands before the shortlist closes

**Market & Salary Research**

- Benchmark listed pay ranges by title, seniority, and location across the postings that publish them
- Measure hiring demand by employment type and workplace arrangement
- Analyze applicant counts to gauge how competitive roles are
- Map remote vs. hybrid vs. on-site distribution across a market

**Lead Generation**

- Identify companies scaling specific functions (sales, engineering, ops)
- Build outbound lists of firms hiring for roles your product serves
- Enrich target accounts with company description, size, and industry
- Spot fast-growing startups by tracking sustained job-posting volume

**Job-Board & Aggregator Content**

- Backfill a niche job board with structured, deduplicated postings
- Refresh listings on a schedule with the Past-24-hours window
- Power "jobs near you" or "remote jobs" feeds with workplace filters
- Normalize titles, functions, and industries across thousands of roles

**Competitive Hiring Intelligence**

- Monitor a competitor's open roles to infer roadmap and team growth
- Track which locations a rival is expanding into
- Compare seniority mix and employment types across peer companies
- Watch for leadership hires by filtering Director and Executive levels

### Getting Started

#### Simple Keyword Search

The fastest way to start — a keyword and a location:

```json
{
    "keywords": "data engineer",
    "location": "United States",
    "maxResults": 100
}
```

#### Daily Monitor

Brand-new roles posted in the past 24 hours, newest first:

```json
{
    "keywords": "product manager",
    "location": "Remote",
    "datePosted": "day",
    "sortBy": "recent",
    "maxResults": 200
}
```

#### Competitor Watch

Every role a named set of employers posted this week:

```json
{
    "keywords": "machine learning engineer",
    "location": "London, United Kingdom",
    "datePosted": "week",
    "companyUrls": [
        "https://www.linkedin.com/company/deepmind",
        "https://www.linkedin.com/company/stripe"
    ],
    "maxResults": 300
}
```

#### Advanced — Full Enrichment

Roles posted in the past week, with company profiles attached to every row:

```json
{
    "keywords": "machine learning engineer",
    "location": "London, United Kingdom",
    "datePosted": "week",
    "sortBy": "relevant",
    "scrapeCompanyDetails": true,
    "maxResults": 500
}
```

You can also paste raw LinkedIn job-search URLs and mix them with a keyword search:

```json
{
    "startUrls": [
        "https://www.linkedin.com/jobs/search/?keywords=nurse&location=Berlin"
    ],
    "maxResults": 100
}
```

### Input Reference

#### Search

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `keywords` | string | `"data engineer"` | Job title, skill, or keyword to search for. Leave empty if you are only using URLs. |
| `location` | string | `"United States"` | City, region, or country — matches what you'd type in LinkedIn's location box. |
| `startUrls` | string\[] | `[]` | Full LinkedIn job-search URLs, copied from your browser. Keyword, location, date-posted and company filters in the URL are applied; see the note below the table for the ones LinkedIn's public search doesn't apply. Mixable with the keyword search above. |

#### Filters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `datePosted` | select | `Any time` | Recency window: Any time, Past month, Past week, or Past 24 hours. |
| `companyUrls` | string\[] | `[]` | Only return jobs from these employers. Paste each company's LinkedIn page address, e.g. `https://www.linkedin.com/company/microsoft`. Empty = every company. |
| `sortBy` | select | `Most relevant` | Order results by Most relevant, or Most recent to get the freshest postings first, ordered by posting date. Most recent checks a wider pool of listings before picking the newest, so those runs take a little longer. |

#### Output Options & Limits

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `scrapeCompanyDetails` | boolean | `true` | Also fetch each job's company profile (description, website, employee count, industry). Turn off for faster runs. |
| `maxResults` | integer | `100` | Total jobs to return across all searches and URLs. Several searches **share** this total and run in order, so a later one returns nothing once it is used up. Use `0` for unlimited instead: every search then runs on its own until LinkedIn runs out of jobs (roughly 1,000 each), so 3 searches can return around 3,000 billed results. |

> **Which filters LinkedIn applies to public job data.** Date posted and company work on every search, including inside a pasted URL. Experience level, job type, workplace type, industry and salary do **not** — LinkedIn only applies those for visitors who are signed in to a LinkedIn account, so a URL using them returns a wider set of jobs here than the same link shows in your own browser. This actor reads public data without a login, so it can't apply them. Paste such a URL and the run names the ignored filters in its log rather than quietly returning more than you asked for. Every posting still carries its own `seniorityLevel`, `employmentType`, `jobFunction` and `industries`, so you can filter the export on those columns afterwards.

### Output

Each job posting is one flat row. Here's a representative result with company details enabled:

```json
{
    "id": "3801234567",
    "link": "https://www.linkedin.com/jobs/view/3801234567",
    "title": "Senior Data Engineer",
    "companyName": "Acme Analytics",
    "companyLinkedinUrl": "https://www.linkedin.com/company/acme-analytics",
    "companyLogo": "https://media.licdn.com/dms/image/acme-logo.png",
    "location": "New York, NY",
    "salary": "$140,000 - $180,000",
    "salaryInfo": ["$140,000", "$180,000"],
    "benefits": ["Medical insurance", "Dental insurance", "401(k)"],
    "postedAt": "2026-05-28",
    "applicantsCount": 47,
    "descriptionText": "We are looking for a Senior Data Engineer to build and scale our data platform...",
    "descriptionHtml": "<p>We are looking for a <strong>Senior Data Engineer</strong>...</p>",
    "seniorityLevel": "Mid-Senior level",
    "employmentType": "Full-time",
    "jobFunction": "Engineering and Information Technology",
    "industries": ["Software Development", "Data Infrastructure"],
    "jobPosterName": "Jane Doe",
    "jobPosterTitle": "Technical Recruiter at Acme Analytics",
    "jobPosterPhoto": "https://media.licdn.com/dms/image/jane-doe.jpg",
    "jobPosterProfileUrl": "https://www.linkedin.com/in/jane-doe",
    "companyDescription": "Acme Analytics builds data infrastructure for enterprise teams...",
    "companyWebsite": "https://www.acme-analytics.com/",
    "companyEmployeesCount": 850,
    "companyIndustry": "Software Development"
}
```

#### Core Job Fields

| Field | Type | Description |
|-------|------|-------------|
| `id` | string | LinkedIn job posting ID |
| `link` | string | Canonical job posting URL |
| `title` | string | Job title |
| `location` | string | Job location text |
| `postedAt` | string | Posting date as `YYYY-MM-DD` — sorts and filters correctly in Sheets, Excel, and CSV |
| `applicantsCount` | integer | Number of applicants so far, on the roughly two thirds of postings where LinkedIn publishes a count |

#### Company

| Field | Type | Description |
|-------|------|-------------|
| `companyName` | string | Hiring company name |
| `companyLinkedinUrl` | string | Company LinkedIn page URL |
| `companyLogo` | string | Company logo image URL |
| `companyDescription` | string | Company about text (when `scrapeCompanyDetails` is on) |
| `companyWebsite` | string | Company website (when `scrapeCompanyDetails` is on) |
| `companyEmployeesCount` | integer | Company employee count (when `scrapeCompanyDetails` is on) |
| `companyIndustry` | string | Company industry (when `scrapeCompanyDetails` is on) |

#### Compensation

| Field | Type | Description |
|-------|------|-------------|
| `salary` | string | Listed pay text, on the roughly 1 posting in 5 where the employer publishes it — higher in pay-transparency markets such as New York, lower for executive roles |
| `salaryInfo` | string\[] | Structured pay parts (min and max), populated on exactly the rows that carry a `salary` |
| `benefits` | string\[] | The employer's Featured Benefits list (medical, dental, 401(k), parental leave, and similar). Only a minority of postings publish one, so expect this to be empty on most rows |

#### Description & Classification

| Field | Type | Description |
|-------|------|-------------|
| `descriptionText` | string | Plain-text job description |
| `descriptionHtml` | string | HTML job description (preserves formatting) |
| `seniorityLevel` | string | Seniority **as the employer labelled it** on the posting (Entry level, Mid-Senior level, etc.) |
| `employmentType` | string | Employment type **as the employer labelled it** (Full-time, Part-time, Contract, etc.) |
| `jobFunction` | string | LinkedIn job function |
| `industries` | string\[] | Associated industries |

#### Job Poster

| Field | Type | Description |
|-------|------|-------------|
| `jobPosterName` | string | Recruiter / job poster name. Present on roughly 1 posting in 10 — closer to 1 in 4 for director and executive roles — and blank when LinkedIn does not name a poster |
| `jobPosterTitle` | string | Recruiter title (same rows as `jobPosterName`) |
| `jobPosterPhoto` | string | Recruiter photo URL (same rows as `jobPosterName`) |
| `jobPosterProfileUrl` | string | Recruiter LinkedIn profile URL (same rows as `jobPosterName`) |

### Tips for Best Results

- **Beat the per-search cap by splitting searches** — each LinkedIn search returns at most ~1,000 jobs. To go deeper, run several narrower searches by location, seniority, or date and the actor will deduplicate overlapping results for you.
- **Set `maxResults` to `0` when you run several searches at once** — a number is a single total that the searches share in order, so the last ones can come back empty. `0` gives every search its own run to LinkedIn's ~1,000 ceiling, which is usually what you want for multi-city or multi-role collections. Budget for it: three searches can return around 3,000 results.
- **Build a daily monitor** — pair `sortBy: Most recent` with `datePosted: Past 24 hours` to capture only brand-new postings on a scheduled run.
- **Turn off company details for speed** — set `scrapeCompanyDetails` to false when you only need the job posting itself; company enrichment adds an extra request per unique company.
- **Start small to validate** — set `maxResults` to 25–50 on your first run to confirm the data matches your needs, then scale up.
- **Combine structured filters with URLs** — use the clean keyword/location/filter fields for everyday searches and reserve pasted URLs for filter combinations the fields don't cover.
- **Watch `applicantsCount` for competitiveness** — low applicant counts on recent postings are your best window for candidate outreach or fast applications.
- **Know which columns are guaranteed and which depend on the employer** — `title`, `companyName`, `location`, `link`, `postedAt`, `descriptionText`, `seniorityLevel`, `employmentType`, `jobFunction` and `industries` land on essentially every row. `salary`, `benefits`, `applicantsCount` and the `jobPoster*` fields only exist when the employer or LinkedIn publishes them, so build reports that tolerate blanks there and never read a missing applicant count as zero.
- **Build a remote feed from the posting text, not a filter** — search `remote` as a keyword or set `location` to Remote, then narrow the export on `title` and `descriptionText`. LinkedIn's workplace filter is a signed-in-only feature, so it can't be applied to public job data.
- **Filter seniority and employment type in your spreadsheet, after the run** — every row carries the employer's own `seniorityLevel`, `employmentType`, `jobFunction` and `industries`, so one broad run can be sliced many ways. Expect real-world messiness in those labels: most employers leave seniority as "Not Applicable" and tag almost everything "Full-time", so treat them as a hint and lean on `title` and `descriptionText` when the cut has to be accurate.

### Pricing

**From $0.95 per 1,000 results** — pay-per-result only, with lower rates as you climb Apify's loyalty tiers.

| Results | No discount | Bronze | Silver | Gold |
|---------|-------------|--------|--------|------|
| 100 | $0.11 | $0.11 | $0.10 | $0.095 |
| 1,000 | $1.13 | $1.07 | $1.01 | $0.95 |
| 10,000 | $11.30 | $10.70 | $10.10 | $9.50 |
| 100,000 | $113.00 | $107.00 | $101.00 | $95.00 |

The tiered rates above take effect on 11 September 2026. Until then every run is billed at the Gold rate of $0.95 per 1,000 results, and Gold stays $0.95 after the change.

A "result" is any job posting row in the output dataset. **No compute charges — you only pay per result returned.**

### Integrations

Export data in JSON, CSV, Excel, XML, or RSS. Connect to 1,500+ apps via:

- **Zapier** / **Make** / **n8n** — Workflow automation
- **Google Sheets** — Direct spreadsheet export
- **Slack** / **Email** — Notifications on new postings
- **Webhooks** — Trigger custom APIs on run completion
- **Apify API** — Full programmatic access

### Legal & Ethical Use

This actor collects only public, logged-out LinkedIn job listing data for legitimate recruiting, market research, and analytics. Users are responsible for complying with applicable laws and LinkedIn's Terms of Service. Do not use extracted data for spam, harassment, or any unlawful purpose, and handle any personal data (such as recruiter contact details) in accordance with applicable privacy regulations.

# Actor input Schema

## `keywords` (type: `string`):

Job title, skill, or keyword to search for (e.g., 'Python developer', 'product manager', 'nurse'). Leave empty if you are only using LinkedIn URLs below.

## `location` (type: `string`):

City, region, or country to search in (e.g., 'New York', 'United Kingdom', 'Remote — Germany'). Matches what you would type into LinkedIn's location box.

## `startUrls` (type: `array`):

Full LinkedIn job-search URLs, copied from your browser. Keyword, location, date-posted and company filters in the URL are applied. Experience level, job type, workplace type, industry and salary filters are NOT — LinkedIn only applies those for signed-in visitors, so a URL using them returns a wider set of jobs here than it shows in your browser. The run warns you when it sees one.

## `datePosted` (type: `string`):

Only return jobs posted within this time window.

## `companyUrls` (type: `array`):

Only return jobs from these companies. Paste the address of each company's LinkedIn page, e.g. https://www.linkedin.com/company/microsoft. Leave empty to search every company.

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

Order results by relevance, or by most recently posted. Most recent returns the freshest jobs first, ordered by posting date — it checks a wider pool of listings before picking the newest ones, so those runs take a little longer.

## `scrapeCompanyDetails` (type: `boolean`):

Also fetch each job's company profile (description, website, employee count, industry). This adds an extra request per unique company and makes the run slower. Turn off to keep runs fast and only collect the job posting itself.

## `maxResults` (type: `integer`):

Total number of jobs to return across all searches and URLs. When a run has more than one search (keywords plus URLs, or several URLs), they share this total and run in order, so a later search returns nothing once the total is used up. Use 0 for unlimited instead: every search then runs on its own until LinkedIn runs out of jobs, so a run with 3 searches can return roughly 3,000 jobs and you are billed for all of them. LinkedIn caps each individual search at roughly 1,000 jobs — to go deeper, run several narrower searches (by location, date, or job type).

## Actor input object example

```json
{
  "keywords": "data engineer",
  "location": "United States",
  "startUrls": [],
  "datePosted": "any",
  "companyUrls": [],
  "sortBy": "relevant",
  "scrapeCompanyDetails": true,
  "maxResults": 100
}
```

# Actor output Schema

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

Table of jobs with key fields like title, company, location, posted date, and link.

## `details` (type: `string`):

Full per-job detail rows including description, salary, company profile, the seniority and employment type each employer published, and the job type and experience filters the row was matched against.

# 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 = {
    "keywords": "data engineer",
    "location": "United States",
    "startUrls": [],
    "datePosted": "any",
    "companyUrls": [],
    "sortBy": "relevant",
    "scrapeCompanyDetails": true,
    "maxResults": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("solidcode/linkedin-jobs-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 = {
    "keywords": "data engineer",
    "location": "United States",
    "startUrls": [],
    "datePosted": "any",
    "companyUrls": [],
    "sortBy": "relevant",
    "scrapeCompanyDetails": True,
    "maxResults": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("solidcode/linkedin-jobs-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 '{
  "keywords": "data engineer",
  "location": "United States",
  "startUrls": [],
  "datePosted": "any",
  "companyUrls": [],
  "sortBy": "relevant",
  "scrapeCompanyDetails": true,
  "maxResults": 100
}' |
apify call solidcode/linkedin-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,solidcode/linkedin-jobs-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/dwywCMrdJl963T3I2/builds/clpUducUnrNdYob9c/openapi.json
