# LinkedIn Jobs Scraper | No Login, from $0.05/1K Jobs (`alexmorain/linkedin-jobs-scraper`) Actor

Scrape LinkedIn job postings without login or cookies: title, company, location, salary, applicants, seniority, job type, full description. Job type and experience filters are checked on every job and duplicates are removed before you pay. New-jobs-only mode and webhook for daily pipelines.

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

## Pricing

from $0.05 / 1,000 jobs

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

### What does LinkedIn Jobs Scraper do?

LinkedIn Jobs Scraper collects **job postings from [LinkedIn Jobs](https://www.linkedin.com/jobs/)** for any job title and location: title, company, location, posting date, **salary range** when the employer publishes it, **applicants**, seniority, **job type**, industry and the **full description**. It needs **no LinkedIn account, no cookies and no browser**, so there is no account to get banned. It works as a **LinkedIn Jobs API** alternative, from the Apify console or from your own code.

It also fixes what LinkedIn does to logged-out searches. LinkedIn accepts the job type (`f_JT`) and experience (`f_E`) filters in a search URL, then **ignores them** when you are not signed in, and returns the same jobs as without the filter. We measured it on 21 October 2026, same query a second apart:

| Search: software engineer, United States | First jobs returned |
|---|---|
| No filter | 4419969671, 4479305237, 4479196690 |
| Job type: Contract (`f_JT=C`) | 4419969671, 4479305237, 4479196690 |
| Experience: Entry level (`f_E=1`) | 4419969671, 4479305237, 4479196690 |
| Remote (`f_WT=2`) | 4419969671, 4479305237, 4479196690 |
| Posted in the past 24 hours (`f_TPR`) | 4482067895, 4464920458, 4482036357 |

A scraper that passes your filters to LinkedIn and trusts the answer bills you for jobs outside your filter. This Actor reads the job type and seniority on **every job page** and only returns, and charges, the jobs that match. On one test, none of the 1,000 jobs LinkedIn returned for a "Contract" software engineer search in the United States was a contract job: this Actor returned none of them and charged nothing for them.

### Why use LinkedIn Jobs Scraper?

**Job market and salary research.** Pull every posting for a role across countries or cities, with salary ranges, applicants and seniority, and analyze it in a spreadsheet or a BI tool.

**Lead generation for recruiters and B2B sales.** A company hiring a "Head of Data" or ten sales reps is a company with a budget. Export hiring companies with their LinkedIn page, location and the exact roles they open.

**Job boards and alert pipelines.** Turn on **New jobs only**, schedule a daily run with **Posted within: Past 24 hours**, and each run returns only jobs you never received before. A webhook can ping Slack, Make, Zapier or your own endpoint when new jobs arrive.

What you get on top of the data:

- **duplicates removed before you pay**: LinkedIn repeats jobs across pages (150 repeats in 1,000 results on a wide Paris search), they are charged once;
- an empty page in the middle of a search is retried from other IPs before the search counts as finished, so a hiccup at LinkedIn does not cut your results short;
- LinkedIn's limit of **1,000 jobs per search** is reported in the run status, not hidden;
- the cheapest price per job we know of on Apify Store, with volume discounts;
- Apify scheduling, API access, integrations, proxy rotation and monitoring.

### What data can LinkedIn Jobs Scraper extract?

| Field | Description |
|---|---|
| `title`, `jobUrl`, `jobId` | Job title, LinkedIn job page and ID |
| `companyName`, `companyLinkedinUrl`, `companyLogoUrl` | Hiring company and its LinkedIn page |
| `location` | Location as LinkedIn shows it |
| `postedDate`, `postedTimeAgo` | Posting date (YYYY-MM-DD) and age |
| `salaryText`, `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod` | Salary range when published (about 1 job in 9 in our tests, more in the US and UK) |
| `applicantsText`, `applicantsCount` | Applicants so far |
| `employmentType`, `employmentTypeCode` | Full-time, Part-time, Contract, Temporary, Internship, Volunteer, Other |
| `seniorityLevel`, `seniorityLevelCode` | Internship, Entry level, Associate, Mid-Senior level, Director, Executive, Not applicable |
| `industries` | Industry of the job |
| `easyApply` | True for LinkedIn Easy Apply, false when you apply on the employer's site |
| `descriptionText`, `descriptionHtml` | Full job description, plain text and HTML |
| `benefits` | Badges such as "Be an early applicant" |
| `searchKeywords`, `searchLocation`, `searchUrl`, `rank` | Which search found the job, and its position |
| `detailsIncluded`, `scrapedAt` | Whether the job page was read (detailed job), and when the job was collected |

### How to scrape LinkedIn jobs

1. Click **Try for free** and open the Input tab.
2. Type one or more **job titles or keywords** (`software engineer`, `registered nurse`).
3. Type one or more **locations** (`United States`, `London`, `Germany`). Each keyword is searched in each location.
4. Optionally pick **Posted within**, **Job types** and **Experience levels**.
5. Set **Max jobs** and click **Start**.
6. Download the jobs as JSON, CSV or Excel, or read them through the API.

Already have a search open on LinkedIn? Paste its URL into **LinkedIn search URLs** instead. To refresh specific jobs, paste their URLs or IDs into **Job URLs or IDs**.

### How much does it cost to scrape LinkedIn jobs?

You pay per job returned, nothing for duplicates, already delivered jobs or jobs outside your filters, plus $0.001 per run.

| Event | Free plan | Bronze | Silver | Gold and above |
|---|---|---|---|---|
| Job (search card only) | $0.08 / 1,000 | $0.07 / 1,000 | $0.06 / 1,000 | $0.05 / 1,000 |
| Detailed job (with job page) | $0.25 / 1,000 | $0.22 / 1,000 | $0.20 / 1,000 | $0.15 / 1,000 |

So 10,000 detailed jobs cost $2.50 on the Free plan and $1.50 on Gold. The $5 of monthly credit on the Free plan covers about 20,000 detailed jobs. Platform usage (compute, proxy) is included in the price.

### Input

See the Input tab for every option. A typical input:

```json
{
    "keywords": ["data analyst", "data engineer"],
    "locations": ["United States", "United Kingdom"],
    "postedWithin": "past-week",
    "jobTypes": ["full-time", "contract"],
    "maxItems": 2000
}
```

Fields that need a word of explanation:

- **Job types** and **Experience levels** are checked on each job page. LinkedIn leaves most jobs at "Not applicable" seniority (9 in 10 in our tests), so add *Not applicable* if you do not want to lose them.
- **Include job details** (on by default) reads each job page. Off, you get the search cards only: title, company, location and date, at the lowest price, about 1,000 jobs a minute.
- **Drop unrelated jobs** (off by default). When a search has few real matches, LinkedIn fills the rest with random jobs: a made-up word searched in France returns hundreds of unrelated French jobs. On, a job whose title and description contain none of your keywords is dropped and not charged. It stays off by default because LinkedIn also translates your keywords: `accountant` in Germany rightly returns *Bilanzbuchhalter* jobs that never say "accountant".
- **Remote, salary and industry filters** cannot be applied without a LinkedIn login: LinkedIn ignores them and job pages do not show the workplace type to logged-out visitors. A search URL that contains them still runs, and the log says the filter was not applied.

### Output

You can download the dataset in various formats such as JSON, HTML, CSV, or Excel. One item per job:

```json
{
    "jobId": "4480470169",
    "jobUrl": "https://www.linkedin.com/jobs/view/4480470169",
    "title": "Staff Nurse - Oncology",
    "companyName": "Kaiser Permanente",
    "companyLinkedinUrl": "https://www.linkedin.com/company/kaiser-permanente",
    "location": "Honolulu, HI",
    "postedDate": "2026-10-20",
    "postedTimeAgo": "1 day ago",
    "salaryText": "$54.05/hr - $77.21/hr",
    "salaryMin": 54.05,
    "salaryMax": 77.21,
    "salaryCurrency": "USD",
    "salaryPeriod": "hour",
    "applicantsText": "Be among the first 25 applicants",
    "applicantsCount": null,
    "seniorityLevel": "Not Applicable",
    "employmentType": "Full-time",
    "employmentTypeCode": "full-time",
    "industries": "Hospitals and Health Care",
    "easyApply": false,
    "descriptionText": "This position is a Hawaii Nurses and Healthcare Professionals Bargaining Unit represented position...",
    "searchKeywords": "registered nurse",
    "searchLocation": "United States",
    "rank": 3,
    "detailsIncluded": true
}
```

Each run also writes a `SUMMARY` record to its key-value store: for each search, the pages read, duplicates skipped, jobs dropped by each filter, jobs returned, and why the search stopped: `end-of-results`, `linkedin-1000-cap`, `max-items`, `max-items-per-search`, `charge-limit` (your maximum charge was reached), `only-unrelated-jobs-left`, `timeout` or `blocked` (LinkedIn refused, nothing charged).

### Tips for scraping more LinkedIn jobs, faster

- **Beat the 1,000-job limit.** LinkedIn shows at most 1,000 jobs per search. Search cities instead of a country, or add keywords: jobs found by several searches are returned and charged once.
- **Daily feeds.** New jobs only plus Posted within: Past 24 hours keeps each run small and cheap. Give the monitor a name to keep its memory when you edit the searches.
- **Speed.** About 1,000 detailed jobs in 5 to 7 minutes, and 1,000 search cards in about a minute. Posted within makes LinkedIn return fewer jobs, so runs finish sooner.
- **From code.** Call the Actor through the [Apify API](https://docs.apify.com/api/v2) or the `apify-client` package for Python and JavaScript, and read the dataset when the run finishes.

### FAQ, disclaimers and support

#### Do I need a LinkedIn account or cookies?

No. The Actor reads the job pages LinkedIn shows to logged-out visitors. Nothing is done with an account, so nothing can get banned.

#### Why do I get fewer jobs than LinkedIn shows when I am signed in?

Signed-out LinkedIn shows at most 1,000 jobs per search, and some filters only work when signed in. Split wide searches by city to get more.

#### Can I filter remote jobs?

Not reliably. LinkedIn ignores the remote filter for logged-out searches and does not show the workplace type on the job page. Adding `remote` to your keywords is the closest workaround: it finds jobs whose ad mentions remote work.

#### Is it legal to scrape LinkedIn jobs?

Our Actors are ethical and do not extract any private user data, such as email addresses, gender, or location. They only extract what the user has chosen to share publicly. We therefore believe that our Actors, when used for ethical purposes by Apify users, are safe. However, you should be aware that your results could contain personal data. Personal data is protected by the GDPR in the European Union and by other regulations around the world. You should not scrape personal data unless you have a legitimate reason to do so. If you're unsure whether your reason is legitimate, consult your lawyers.

#### Something is wrong or missing?

Open an issue in the Issues tab: it gets read and answered quickly. For programmatic access, see the API tab.

# Actor input Schema

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

One search per line, as you would type it in LinkedIn's job search box: <code>software engineer</code>, <code>registered nurse</code>, <code>data analyst python</code>. Each line is searched in each location.

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

Countries, regions or cities, one per line: <code>United States</code>, <code>London</code>, <code>Germany</code>, <code>Paris, Île-de-France, France</code>. Empty means <code>Worldwide</code>.

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

Only jobs posted in this window. LinkedIn applies this filter itself, so it also makes runs faster.

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

Keep only these employment types. Checked on each job page, since LinkedIn ignores this filter for logged-out searches. Empty keeps all.

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

Keep only these seniority levels. Checked on each job page, since LinkedIn ignores this filter for logged-out searches. Empty keeps all. Note: LinkedIn leaves most jobs at <code>Not applicable</code> (9 in 10 in our tests on nurse and analyst jobs), so add <i>Not applicable</i> if you do not want to lose those.

## `easyApplyOnly` (type: `boolean`):

Only jobs you apply to on LinkedIn itself (Easy Apply).

## `fewApplicantsOnly` (type: `boolean`):

Only jobs LinkedIn flags as having few applicants so far.

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

Reads each job page: full description (text and HTML), seniority, job type, industry, applicants, salary range when published, Easy Apply or external. Charged as a detailed job instead of a job. Off returns the search cards only (title, company, location, date), the cheapest option for large lists.

## `dropUnrelatedJobs` (type: `boolean`):

When a search has few real matches, LinkedIn fills the rest with unrelated jobs instead of stopping: a search for a made-up word in France returns hundreds of random French jobs. With this on, a job whose title and description contain none of your keywords is dropped and not charged, and a search stops after two pages without any related job. Needs job details.<br><br>Off by default because LinkedIn also translates and widens your keywords: <code>accountant</code> in Germany rightly returns many <i>Bilanzbuchhalter</i> jobs that never say "accountant". Turn it on for English keywords in English-speaking markets, for rare titles, or whenever you would rather miss a few jobs than pay for unrelated ones.

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

Optional. Paste search URLs from <code>linkedin.com/jobs/search</code>. Keywords, location, date, company and Easy Apply are read from the URL; job type (<code>f_JT</code>) and experience (<code>f_E</code>) are checked on each job page. Remote, salary and industry filters in a URL cannot be applied without a LinkedIn login, the log says so.

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

Optional. Fetch specific jobs directly: <code>https://www.linkedin.com/jobs/view/4480470169</code> or just <code>4480470169</code>. Useful to refresh jobs you already have, or one job at a time from a pipeline. Expired jobs are listed in the SUMMARY record and not charged.

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

Total jobs returned by the run, across all searches.

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

LinkedIn shows at most 1,000 jobs per search. To collect more, add locations (cities instead of a country) or keywords: duplicates across searches are removed and charged once.

## `newOnly` (type: `boolean`):

Remembers the jobs returned by earlier runs and returns only jobs not delivered before. The first run returns everything that matches. Schedule it daily with <b>Posted within: Past 24 hours</b> for a feed of fresh jobs that never repeats.

## `monitorName` (type: `string`):

Without a name, the memory of delivered jobs belongs to this exact set of searches. With a name, every run using that name shares it, even if you change the searches.

## `webhookUrl` (type: `string`):

If set, the Actor POSTs a small JSON summary to this URL at the end of the run, <b>only when at least one job was returned</b>: <code>actor</code>, <code>runId</code>, <code>datasetId</code>, <code>jobs</code>, <code>newOnly</code> and one line per search. Use the final URL: a redirect is not followed, except a 307 or 308. An unreachable webhook is logged and never fails the run. Stored encrypted.

## Actor input object example

```json
{
  "keywords": [
    "software engineer"
  ],
  "locations": [
    "United States"
  ],
  "postedWithin": "any",
  "easyApplyOnly": false,
  "fewApplicantsOnly": false,
  "includeDetails": true,
  "dropUnrelatedJobs": false,
  "maxItems": 50,
  "maxItemsPerSearch": 1000,
  "newOnly": false
}
```

# Actor output Schema

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

All jobs in the default dataset.

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

For each search: pages read, duplicates skipped, jobs dropped by the job type, experience and keyword checks, jobs returned, and why the search stopped (end of results, LinkedIn's 1,000-job limit, max jobs).

# 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": [
        "software engineer"
    ],
    "locations": [
        "United States"
    ],
    "maxItems": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("alexmorain/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": ["software engineer"],
    "locations": ["United States"],
    "maxItems": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("alexmorain/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": [
    "software engineer"
  ],
  "locations": [
    "United States"
  ],
  "maxItems": 50
}' |
apify call alexmorain/linkedin-jobs-scraper --silent --output-dataset

```

## MCP server setup

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