# LinkedIn Jobs Scraper: Jobs, Salaries and Descriptions (`oski/linkedin-jobs-scraper`) Actor

Export LinkedIn job listings by keyword and location: title, company, salary range, full description, seniority, employment type, applicants and posted date. No login, no cookies.

- **URL**: https://apify.com/oski/linkedin-jobs-scraper.md
- **Developed by:** [Oski](https://apify.com/oski) (community)
- **Categories:** Jobs, Lead generation, Social media
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $0.97 / 1,000 job with full details

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 Jobs Scraper: Jobs, Salaries and Descriptions

Export LinkedIn job listings by keyword and location into clean data: job title, company, location, salary range when the employer lists one, the full job description, seniority, employment type, job function, industries, applicant count and the date it was posted. It reads the job pages LinkedIn shows to anyone who is not logged in, so there is **no login, no cookies and no account to get banned**.

A search for "python developer" in London returns jobs like this one in about a second each, salary included when the employer publishes it: Senior Python Backend Software Developer at Xelix, £70,000 to £95,000 a year, full-time, posted 16 hours ago.

> **Also from Oski:** [LinkedIn Ad Library Scraper](https://apify.com/oski/linkedin-ad-library-scraper) for the ads a company runs on LinkedIn, and [UK Decision Maker Finder](https://apify.com/oski/uk-decision-maker-finder) to name the director behind a hiring company.

### What you can use it for

- **Recruitment agencies** finding companies that are hiring right now, with the role, salary and location, to pitch candidates.
- **Job boards and aggregators** filling listings for a niche or region, refreshed daily.
- **Salary researchers and HR teams** benchmarking pay ranges for a role across cities.
- **Sales teams** using hiring as a buying signal: a company hiring five data engineers is likely buying data tools.
- **Job seekers and career coaches** tracking every new opening for a role in one spreadsheet.
- **Analysts** measuring demand for a skill over time, by location and seniority.

### How to use it

1. Under **Job titles or keywords**, add what to search, such as `data analyst`. Separate several with new lines or commas.
2. Under **Locations**, add places such as `London` or `United States`, or leave it empty for worldwide.
3. Optionally set filters (date posted, remote, job type, experience) and click **Start**.
4. When it finishes, open the **Output** tab and download as CSV, Excel or JSON.

While it runs, the status line at the top of the run page shows progress (for example "Search 1 of 4: data analyst | London. 250 jobs so far."). When it finishes, the same line says what was found and explains anything that was skipped, so you never have to read the log.

### How much does it cost?

A $0.002 start fee per run, then **$0.001 per job with full details**, or $0.0005 per job if you switch full details off. Jobs that LinkedIn has removed by the time they are opened are returned for your records and **not charged**; every row has a `charged` field so you can see exactly what you paid for.

| Jobs with full details | Maths | Cost |
|---|---|---|
| 100 | $0.002 + 100 x $0.001 | about $0.10 |
| 1,000 | $0.002 + 1,000 x $0.001 | about $1.00 |
| 10,000 | $0.002 + 10,000 x $0.001 | about $10.00 |

A full 1,000-job export with descriptions and salaries costs about a dollar, so there is little reason to stop at 100.

### Input

| Field | What it does |
|---|---|
| `keywords` | Job titles, skills or company names. One per line, or separated by commas. Each is searched in every location. |
| `locations` | Cities, regions or countries, as you would type them on LinkedIn. Empty means worldwide. |
| `searchUrls` | Optional. LinkedIn jobs search URLs pasted from your browser, with all their filters. |
| `maxJobsPerSearch` | Jobs per keyword and location search, up to 1,000 (LinkedIn's limit). Default 1,000. |
| `postedWithin` | `any`, `past24h`, `pastWeek` or `pastMonth`. |
| `workplaceTypes` | Any of `onsite`, `remote`, `hybrid`. |
| `jobTypes` | Any of `fulltime`, `parttime`, `contract`, `temporary`, `internship`, `volunteer`, `other`. |
| `experienceLevels` | Any of `internship`, `entry`, `associate`, `midsenior`, `director`, `executive`. |
| `sortBy` | `relevance` or `recent`. |
| `includeDetails` | On by default. Adds description, salary, criteria and applicants. Off gives a cheaper list. |
| `maxJobs` | Advanced. Optional cap across the whole run. |
| `proxyConfiguration` | Advanced. Rotating proxy sessions keep large runs moving. |

A realistic job, every new remote data role in the UK and US from the past week:

```json
{
  "keywords": ["data analyst", "data engineer"],
  "locations": ["United Kingdom", "United States"],
  "postedWithin": "pastWeek",
  "workplaceTypes": ["remote"],
  "maxJobsPerSearch": 1000
}
```

### Output

One row per job:

```json
{
  "job_id": "4468683827",
  "title": "Senior Python Backend Software Developer",
  "company_name": "Xelix",
  "company_url": "https://uk.linkedin.com/company/xelix.",
  "location": "London, England, United Kingdom",
  "posted_date": "2026-09-17",
  "posted_ago": "16 hours ago",
  "salary_text": "£70,000.00/yr - £95,000.00/yr",
  "salary_min": 70000.0,
  "salary_max": 95000.0,
  "salary_currency": "GBP",
  "salary_period": "year",
  "seniority_level": "Not Applicable",
  "employment_type": "Full-time",
  "job_function": "Engineering and Information Technology",
  "industries": "Software Development",
  "applicants_text": "Be among the first 25 applicants",
  "applicants": null,
  "early_applicant": true,
  "apply_type": "offsite",
  "description_text": "About Us At Xelix, we work with some of the world's largest companies...",
  "job_url": "https://www.linkedin.com/jobs/view/4468683827",
  "search": "python developer | London",
  "details_status": "ok",
  "charged": true
}
```

Rows also include `description_html`, `company_logo` and `benefit_badge`. Salary fields are filled only when the employer publishes a range on LinkedIn; nothing is estimated. `applicants` is a number when LinkedIn shows one ("41 applicants"); when it only says "Be among the first 25", the number is left blank and `early_applicant` is true.

### Tips for bigger runs

- **LinkedIn caps every search at 1,000 jobs.** To go further, add keywords (job title variations) or split a country into cities. Duplicates across searches are removed automatically, so overlap costs nothing.
- **Use Date posted: Past 24 hours on a daily schedule** to collect only new jobs every day.
- **Switch full details off** for a quick market scan of titles, companies and locations at half the price, then run details only on the jobs you care about.
- **Paste a search URL** when you have already tuned filters on LinkedIn, such as a specific company or industry.
- **Very vague keywords are topped up by LinkedIn** with loosely related jobs; use specific titles for clean results.

### Scheduling and integrations

Save a task with your searches and run it daily or weekly on an Apify schedule, then send each run to Google Sheets, a webhook, Make, Zapier or your ATS through Apify's integrations. From your own code:

```bash
curl -X POST "https://api.apify.com/v2/acts/oski~linkedin-jobs-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"keywords": ["data analyst"], "locations": ["London"], "maxJobsPerSearch": 100}'
```

The sync endpoint waits up to 5 minutes. For big runs, start the run with `/runs` and read the dataset when it finishes.

### If something looks wrong

The status line at the end of every run explains anything that was skipped or came back empty. The common messages:

| Message | What to do |
|---|---|
| Input problem: ... | Something in the input needs changing, and the message says what. The run stopped before anything was charged. |
| No jobs for "..." | Check the spelling or loosen the filters (a longer date range, any job type). |
| Skipped a link that is not a LinkedIn jobs search | Run the search on linkedin.com/jobs, then copy the address bar. |
| N listed jobs had been removed by LinkedIn | Those jobs closed before they could be opened. They are returned for your records and not charged. |

Still stuck? Open an issue on the actor page with the run link and it gets looked at quickly.

### FAQ

**Is it legal to scrape LinkedIn jobs?**
The actor only reads job listings that LinkedIn publishes to anyone without logging in. It does not log in, does not use cookies and does not collect people's profiles. You are responsible for how you use the data, including respecting privacy law and LinkedIn's terms.

**Do I need a LinkedIn account?**
No. Nothing is signed in to, so there is no account to get restricted.

**Why did I get fewer jobs than I asked for?**
The search may simply have fewer matching jobs, your filters may be narrow, or a keyword and location pair hit LinkedIn's 1,000-job limit. Add more searches to go further.

**Why are some salaries empty?**
Most employers do not publish pay on LinkedIn. Salary fields are filled only when a range is shown.

**How fresh is the data?**
Live. Every run reads LinkedIn at the moment it runs.

**What if it breaks?**
Open an issue on the actor page and it gets fixed fast. Runs retry and rotate proxy sessions when LinkedIn rate-limits, so a busy moment slows a run down rather than failing it.

### Other scrapers from Oski

- [LinkedIn Ad Library Scraper](https://apify.com/oski/linkedin-ad-library-scraper): every ad a company runs on LinkedIn.
- [UK Decision Maker Finder](https://apify.com/oski/uk-decision-maker-finder): named directors of UK companies from Companies House.
- [Website Contact Finder](https://apify.com/oski/website-contact-finder): emails and phones from a hiring company's website.
- [Google Ads Transparency Scraper](https://apify.com/oski/google-ads-transparency-scraper) and [Facebook Ad Library Scraper](https://apify.com/oski/facebook-ad-library-scraper): what a company is advertising.

# Actor input Schema

## `keywords` (type: `array`):

What to search for, for example a job title, skill or company name. Each keyword is searched in every location below.

## `locations` (type: `array`):

Where to search: a city, region or country, written as you would on LinkedIn. Each location is searched for every keyword. Leave empty to search worldwide.

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

Paste a jobs search from your browser to reuse all its filters. Only the search parameters are used; tracking parts of the URL are ignored.

## `maxJobsPerSearch` (type: `integer`):

Stop each keyword and location search after this many jobs. LinkedIn shows at most 1,000 jobs per search, so add more keywords or locations to go further. About $1 for 1,000 jobs with full details.

## `postedWithin` (type: `string`):

Only jobs posted within this window. Past 24 hours is ideal for a daily scheduled run.

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

Only jobs with these workplace types. Leave empty for all.

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

Only these employment types. Leave empty for all.

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

Only jobs at these experience levels. Leave empty for all.

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

Most recent is best for monitoring new jobs on a schedule.

## `includeDetails` (type: `boolean`):

Open every job to add the full description, salary range, seniority, employment type, job function, industries and applicant count. Turn off for a faster, cheaper list of titles, companies and locations only ($0.0005 per job instead of $0.001).

## `maxJobs` (type: `integer`):

Optional hard cap across all searches in the run. 0 means no cap beyond the per-search limit.

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

LinkedIn rate-limits one IP after about 100 fast requests, so the actor rotates proxy sessions. The default datacenter proxy works well.

## Actor input object example

```json
{
  "keywords": [
    "data analyst",
    "python developer"
  ],
  "locations": [
    "London",
    "United Kingdom",
    "New York"
  ],
  "searchUrls": [
    "https://www.linkedin.com/jobs/search/?keywords=nurse&location=Leeds&f_TPR=r604800"
  ],
  "maxJobsPerSearch": 100,
  "postedWithin": "any",
  "sortBy": "relevance",
  "includeDetails": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

One row per job: title, company, location, salary, type, seniority, posted date, applicants and link.

## `allFields` (type: `string`):

Every field for each job, including the full description in text and HTML, as raw JSON.

# 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 analyst"
    ],
    "locations": [
        "London"
    ],
    "maxJobsPerSearch": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("oski/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 analyst"],
    "locations": ["London"],
    "maxJobsPerSearch": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("oski/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 analyst"
  ],
  "locations": [
    "London"
  ],
  "maxJobsPerSearch": 100
}' |
apify call oski/linkedin-jobs-scraper --silent --output-dataset

```

## MCP server setup

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