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

Scrape LinkedIn job postings with the full job description, seniority, employment type, job function, industry and applicant count. Search by keyword, paste a search URL, or pass job links. No login, no proxy needed. Job postings only, no personal data. Charged per job delivered.

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

## Pricing

$0.80 / 1,000 job delivereds

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?

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

Scrape LinkedIn job postings into flat rows — title, company, location, date posted, **the full job description**, seniority level, employment type, job function, industry and applicant count.

Give it keywords and a location, paste a LinkedIn job-search URL straight from your browser, or hand it individual job links. No login, no cookies, no browser, and it runs without a proxy.

**You are charged per job delivered.** Failed lookups and duplicates cost nothing.

### What you can scrape

| Input field | What you get |
|---|---|
| Search keywords + location | Every posting LinkedIn returns for that search, up to your limit |
| LinkedIn search URLs | Paste the URL from your browser — every filter in it is kept, including ones this actor has no field for |
| Individual job URLs or IDs | Specific postings, without running a search |

All three can be mixed in one run. Job URLs, bare job IDs and `urn:li:jobPosting:` values are all accepted.

### Filters

Date posted (24 hours / week / month), workplace type (on-site, remote, hybrid), job type (full-time, part-time, contract, temporary, internship, volunteer), experience level (internship → executive), sort by relevance or date, and an optional LinkedIn `geoId` when a place name is ambiguous.

Filters apply to the keyword searches. Pasted search URLs carry their own.

### Output

One flat row per posting — ready for CSV, Google Sheets or a database.

```json
{
  "jobId": "4457722754",
  "title": "Software Engineer (Python Automation)",
  "companyName": "Nexus Corporation",
  "companyUrl": "https://jp.linkedin.com/company/nexusconsulting-global",
  "companyLogo": "https://media.licdn.com/dms/image/…",
  "location": "Minato, Tokyo, Japan",
  "postedAt": "2026-09-20",
  "postedText": "5 hours ago",
  "salary": null,
  "benefits": "Actively Hiring",
  "applicants": 35,
  "applicantsText": "35 applicants",
  "seniorityLevel": "Not Applicable",
  "employmentType": "Full-time",
  "jobFunction": "Engineering and Information Technology",
  "industries": "Staffing and Recruiting",
  "description": "Responsibilities:\n- Design, implement, and maintain Python-based systems…",
  "descriptionHtml": "<ul><li>Design, implement…</li></ul>",
  "jobUrl": "https://jp.linkedin.com/jobs/view/software-engineer-python-automation-at-nexus-corporation-4457722754",
  "detailScraped": true,
  "searchInput": "python developer",
  "scrapedAt": "2026-09-20T07:41:02.118Z"
}
```

Turn **Get the full job description** off to keep only what the results list shows (title, company, location, date, sometimes salary). That skips one request per job, so the run is several times faster.

### What it cannot do

- **Job postings only.** No candidate profiles, no applicant lists, no names or contact details of people. That data is not collected at any setting.
- **No salary on most postings.** LinkedIn shows a salary range only where the poster provided one, and it appears in the results list rather than on the posting page. Most rows will have `salary: null`. It is not inferred or estimated.
- **Roughly 1,000 postings per search.** That is LinkedIn's own ceiling for a single logged-out search, not a limit of this actor. Split a wide search by location, by date posted, or by narrower keywords to go past it.
- **No Easy Apply detail, no company-size or employee-count fields, no recruiter contact.** Those need a logged-in session.
- **Job descriptions are as the employer wrote them.** They are not translated, summarised or cleaned beyond stripping HTML into `description` (`descriptionHtml` keeps the original markup).
- Postings that were removed between the search and the lookup are delivered with whatever the results list showed and a `detailError` field. They are not charged for the missing description.
- Results reflect what LinkedIn serves to a logged-out visitor. A logged-in member sees a slightly different, personalised ranking.

### Tips

- **`maxItems` caps the whole run** — the safest way to cap what you spend.
- **Parallel requests above 8 is not faster.** LinkedIn starts returning 429 and the actor pauses every worker until it clears. 4 is the sweet spot; 8 works and is about 30% faster in wall-clock time.
- Use **Most recent** sorting plus **Past 24 hours** for a daily job feed, and run it on a schedule.
- No proxy is needed. Turn one on only for very large repeated runs, or when you need results as a specific country sees them.

### Legal

This actor reads job postings that LinkedIn publishes to logged-out visitors. It does not log in, does not use cookies, and does not collect personal data about individuals. You are responsible for how you use the output, including GDPR/CCPA obligations where they apply.

# Actor input Schema

## `searchQueries` (type: `array`):

Job titles or keywords to search for. Each one is searched separately with the filters below. Leave empty if you are pasting search URLs instead.

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

City, region or country the keywords above are searched in, exactly as you would type it on LinkedIn, for example "Berlin, Germany" or "United States". Leave empty to search worldwide.

## `searchUrls` (type: `array`):

Paste LinkedIn job-search URLs straight from your browser. Every filter already in the URL is kept, so you do not have to rebuild it here. Filters below do not apply to these.

## `jobUrls` (type: `array`):

Scrape specific postings without searching. Accepts a job URL, a job ID, or a urn:li:jobPosting: value.

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

Only applies to the search keywords above.

## `workplaceTypes` (type: `array`):

Leave empty for all.

## `jobTypes` (type: `array`):

Leave empty for all.

## `experienceLevels` (type: `array`):

Leave empty for all.

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

How LinkedIn orders the results. "Most recent" is the one you want for a daily feed.

## `geoId` (type: `string`):

Optional. LinkedIn's internal location ID, taken from a search URL. More precise than the location text when a place name is ambiguous.

## `scrapeDetails` (type: `boolean`):

On (default): opens every posting for the description text, seniority, employment type, job function, industries and applicant count. Off: keeps only what the results list shows (title, company, location, date, sometimes salary), which is faster.

## `maxItemsPerSearch` (type: `integer`):

LinkedIn stops serving results for a single search at roughly 1,000 postings. Split a wide search by location or date to go past that.

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

Hard ceiling across all searches. 0 means no ceiling. Only delivered jobs count.

## `maxConcurrency` (type: `integer`):

How many job pages are opened at once. Raise it for speed, lower it if you start seeing jobs without a description.

## `delayMs` (type: `integer`):

A pause between requests from each parallel worker. Leave it at 250 unless you are raising the parallel count.

## `proxyConfiguration` (type: `object`):

Optional. The actor works without a proxy. Turn one on if you run large jobs repeatedly, or if you need results as seen from a specific country.

## Actor input object example

```json
{
  "searchQueries": [
    "data engineer",
    "registered nurse"
  ],
  "location": "United States",
  "searchUrls": [
    "https://www.linkedin.com/jobs/search/?keywords=product%20manager&location=London&f_TPR=r604800"
  ],
  "jobUrls": [
    "https://www.linkedin.com/jobs/view/4457722754"
  ],
  "datePosted": "any",
  "workplaceTypes": [],
  "jobTypes": [],
  "experienceLevels": [],
  "sortBy": "relevance",
  "scrapeDetails": true,
  "maxItemsPerSearch": 100,
  "maxItems": 0,
  "maxConcurrency": 4,
  "delayMs": 250,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `jobs` (type: `string`):

Every field, including the description as text and as HTML.

## `jobsCsv` (type: `string`):

Title, company, location, date, employment type and link. Ready for a spreadsheet.

## `descriptions` (type: `string`):

Title, company and the full description text, for feeding into a model or a search index.

# 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 = {
    "searchQueries": [
        "python developer"
    ],
    "location": "United States"
};

// Run the Actor and wait for it to finish
const run = await client.actor("munin/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 = {
    "searchQueries": ["python developer"],
    "location": "United States",
}

# Run the Actor and wait for it to finish
run = client.actor("munin/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 '{
  "searchQueries": [
    "python developer"
  ],
  "location": "United States"
}' |
apify call munin/linkedin-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,munin/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/StBem1ZiksRYUcG1e/builds/2hdYs9xF4eDsmG3Up/openapi.json
