# LinkedIn Jobs Scraper – No Login, Full Details & Salary Data (`nourishing_courier/linkedin-jobs-scraper`) Actor

LinkedIn Jobs Scraper with no login, cookies or proxy. Search by keyword and location or paste LinkedIn job URLs; get title, company, location, posted date, salary range, seniority, employment type, applicants and the full description as flat rows. Pay only per job delivered.

- **URL**: https://apify.com/nourishing\_courier/linkedin-jobs-scraper.md
- **Developed by:** [Ani Björkström](https://apify.com/nourishing_courier) (community)
- **Categories:** Jobs, Lead generation, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.50 / 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 – No Login, Full Details & Salary Data

**LinkedIn Jobs Scraper** collects public LinkedIn job postings by keyword and location, or from any LinkedIn job search URL you paste, and returns one clean row per job. **No LinkedIn login, no cookies, no proxy** are needed: it reads the same public guest listings a logged-out visitor sees. Every job comes back with the **full description, seniority level, employment type, job function, industries, applicant count, salary range** (when the employer publishes one) and a direct link. You **pay only per job delivered**: searches that return nothing cost nothing.

This LinkedIn jobs scraper is built for people who need LinkedIn jobs data in bulk without maintaining their own scraper: recruiters and sourcing agencies, job boards, HR analytics teams doing salary benchmarking, sales teams treating "companies hiring" as a buying signal, and AI/RAG builders who want structured, flat JSON that drops straight into an agent or a vector store.

### What you get

One flat, camelCase row per job. Nothing is nested, so CSV, Google Sheets, n8n and LLM prompts all work without a transform step.

| Field | Description |
|---|---|
| `id` | LinkedIn job posting id (`4468253955`) |
| `title` | Job title |
| `company`, `companyUrl`, `companyLogo` | Employer name, LinkedIn company page, logo image |
| `location` | As written on the posting (`Stockholm, Stockholm County, Sweden`, `Denver Metropolitan Area`) |
| `workType` | `remote`, `hybrid` or `onsite` when you filtered on work type, or when the listing itself says remote/hybrid; otherwise `null` (LinkedIn does not print it on public listings) |
| `postedAt` | Posting date, ISO 8601 (`2026-09-18`) |
| `postedText` | LinkedIn's relative wording (`4 days ago`) |
| `salary` | Salary text exactly as published (`$113,000.00/yr - $143,000.00/yr`) |
| `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod` | Parsed from `salary`: numbers, ISO currency code (`USD`, `EUR`, `GBP`…) and `year` / `month` / `hour`; `null` when no salary is published |
| `seniorityLevel` | `Entry level`, `Associate`, `Mid-Senior level`, `Director`, `Executive`, `Internship`, `Not Applicable` |
| `employmentType` | `Full-time`, `Part-time`, `Contract`, `Temporary`, `Internship`, `Volunteer` |
| `jobFunction`, `industries` | LinkedIn's own classification (`Analyst and Finance`, `Financial Services`) |
| `applicantsText`, `applicantsCount` | `140 applicants` and the integer `140`; `Over 200 applicants` becomes `200`; "Be among the first 25" stays text only |
| `description` | Full job description as readable plain text with paragraphs and `- ` bullets, ready for LLMs and full-text search |
| `descriptionHtml` | The same description with the original HTML formatting |
| `applyUrl` | The employer's external apply URL when LinkedIn exposes it to logged-out visitors; `null` otherwise (see Limits) |
| `url` | Canonical LinkedIn job URL, tracking parameters removed |
| `query`, `searchLocation` | The keyword (or pasted URL) and location that found this job |
| `scrapedAt` | ISO 8601 UTC timestamp of the run |

The LinkedIn Jobs Scraper de-duplicates jobs across all your keywords in a run, so one posting that matches "data analyst" and "business analyst" appears once.

### Input

The simplest way to scrape LinkedIn jobs is one keyword and one location. Everything else has sensible defaults (past week, sorted by date, 50 jobs per keyword, full details on).

```json
{
  "searchQueries": ["data analyst", "financial analyst"],
  "location": "United States",
  "datePosted": "pastWeek",
  "workType": "remote",
  "experienceLevel": "midSenior",
  "jobType": "fullTime",
  "sortBy": "date",
  "maxJobsPerQuery": 200,
  "fetchJobDetails": true
}
```

Or paste LinkedIn URLs. Search URLs are parsed into the same filters (the URL's own `f_WT`, `f_TPR`, `f_E`, `f_JT`, `geoId`, `sortBy` win over the form fields), and single job URLs fetch just that job:

```json
{
  "startUrls": [
    { "url": "https://www.linkedin.com/jobs/search/?keywords=python%20developer&location=Stockholm%2C%20Sweden&f_WT=2&f_TPR=r2592000" },
    { "url": "https://www.linkedin.com/jobs/view/4468253955" }
  ]
}
```

When `startUrls` is non-empty it is used instead of the keyword list.

| Option | What it does |
|---|---|
| **Search keywords** | One search per line: titles, skills or company names, exactly as you would type them into LinkedIn. |
| **Location** | City, region or country in LinkedIn's spelling, e.g. `London, England, United Kingdom`, `Germany`, `Remote`. |
| **Date posted** | Any time / past month / past week / past 24 hours (LinkedIn's own filter). |
| **Work type** | Any / on-site / remote / hybrid. |
| **Experience level** | Internship, entry level, associate, mid-senior, director, executive. |
| **Job type** | Full-time, part-time, contract, temporary, internship, volunteer. |
| **Sort by** | Most recent (best for monitoring) or most relevant. |
| **Maximum jobs per search** | 1 to 1,000 per keyword or URL. |
| **Fetch full job details** | On: opens every job for description, seniority, salary, applicants. Off: listing-only rows in a fraction of the time. |
| **Parallel requests** | 3 by default. Lower it if the log shows rate-limit warnings. |

### Use cases

- **Recruiters and sourcing agencies**: pull every "Head of FP\&A" opening in the Nordics posted this week, with applicant counts, and prioritise the ones with few applicants.
- **Job boards and aggregators**: keep a niche board fresh by running the LinkedIn Jobs Scraper for your vertical every morning, deduplicated by `id`, straight into your database.
- **HR analytics and salary benchmarking**: the parsed `salaryMin` / `salaryMax` / `salaryCurrency` fields turn published ranges into a dataset you can chart by title, seniority and city.
- **Sales and lead generation**: a company hiring three SDRs or a Salesforce admin is a buying signal. Group rows by `company`, join `companyUrl` to your CRM, and route to the right rep.
- **AI agents and RAG**: `description` is plain text with paragraphs and bullets, `descriptionHtml` keeps formatting, and rows are flat, so the output drops into a vector store, an MCP tool result or an LLM prompt without cleanup.
- **Market and labour researchers**: track demand for a skill ("Snowflake", "IFRS 17", "Rust") across countries and weeks; `postedAt` and `scrapedAt` make time series easy.
- **Job seekers and career coaches**: one run a day with `datePosted: past24h` and your filters, pushed to Slack or email through an Apify integration.

### Pricing

Pay per event: one `job` event per job row written to the dataset. Searches that return no jobs, unreachable job pages and error rows are free. There is no actor-start fee and no feature is gated: full details, salary parsing, URL input and filters are all included at the same per-job price.

A run with `fetchJobDetails` on costs the same per job as a listing-only run; it just takes longer because every job page is opened.

### Integrations

Every LinkedIn Jobs Scraper run's dataset is available as JSON, CSV, Excel or XML through the Apify API the moment the run finishes, and the actor works with every Apify integration: **n8n, Make, Zapier, Google Sheets, Slack, webhooks, MCP** and the Python / JavaScript SDKs. Typical patterns: a daily schedule that appends new remote jobs to a Google Sheet, an n8n workflow that scores each `description` with an LLM and posts matches to Slack, or an AI agent that calls this actor through the Apify MCP server to answer "who is hiring data engineers in Berlin this week?".

**Python**

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("nourishing_courier/linkedin-jobs-scraper").call(run_input={
    "searchQueries": ["fp&a analyst"],
    "location": "Stockholm, Sweden",
    "workType": "remote",
    "maxJobsPerQuery": 100,
})
for job in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(job["title"], "|", job["company"], "|", job["salary"], "|", job["url"])
```

**JavaScript**

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('nourishing_courier/linkedin-jobs-scraper').call({
    searchQueries: ['python developer'],
    location: 'Germany',
    datePosted: 'past24h',
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map(j => `${j.title} @ ${j.company} (${j.location})`));
```

**curl**

```bash
curl -X POST "https://api.apify.com/v2/acts/nourishing_courier~linkedin-jobs-scraper/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>&format=csv" \
  -H "Content-Type: application/json" \
  -d '{"searchQueries": ["data engineer"], "location": "United Kingdom", "maxJobsPerQuery": 50}'
```

In **n8n** or **Make**, use the Apify node / module with actor `nourishing_courier/linkedin-jobs-scraper`, pass the JSON input above, and read the dataset items in the next step. To export LinkedIn jobs to Google Sheets, add the Apify → Google Sheets integration in Console and it runs on every schedule.

### Limits, fair use and legality

- **Public data only.** This LinkedIn job scraper reads job listings that LinkedIn serves to visitors who are not logged in. It never uses your account, cookies or session, so there is nothing to get banned, and it does not touch profiles or private data. Job postings are published by employers to be found; still, use the data responsibly and respect employers' and LinkedIn's terms for your use case.
- **About 1,000 jobs per search.** LinkedIn stops paginating a search at roughly 1,000 results. For bigger pulls, split by location, date window, work type or seniority: ten narrower searches return far more unique jobs than one broad one.
- **Rate limits from one IP.** LinkedIn answers bursts with HTTP 429. The actor retries with exponential backoff, slows every worker down when one is throttled, and keeps a small random delay between job pages. A 50-job run with details takes about a minute. If you run many searches at once and see repeated throttling warnings, lower **Parallel requests** or enable a proxy in Advanced.
- **Apply URL.** LinkedIn currently hides most employers' external apply links behind a sign-in prompt for logged-out visitors. `applyUrl` is filled in whenever the public page exposes it and is `null` otherwise; `url` always takes you to the posting.
- **Work type.** Public listings do not print on-site/remote/hybrid, so `workType` is set from your filter (or from "Remote"/"Hybrid" in the title or location) rather than from a field LinkedIn does not expose.
- **Empty searches are reported, not hidden.** A keyword that finds nothing gets one row with an `error` explaining why, and a run that delivers no jobs at all fails, so a scheduled workflow notices.

### FAQ

#### Can I scrape LinkedIn jobs without logging in?

Yes. This actor only uses LinkedIn's public job listings, the pages any visitor can open without an account. You do not provide credentials, cookies or a session, and nothing runs under your identity.

#### How many jobs can the LinkedIn Jobs Scraper return per run?

Up to 1,000 per keyword or pasted URL, which is LinkedIn's own ceiling for one search. Add as many keywords as you like in one run; results are deduplicated across them. Splitting a broad search by location or date is the way to collect tens of thousands of jobs.

#### Does it return the full job description and salary?

Yes. With **Fetch full job details** on (the default), every job is opened and you get the full description as plain text and HTML, seniority level, employment type, job function, industries, applicant count and the published salary range, parsed into `salaryMin`, `salaryMax`, `salaryCurrency` and `salaryPeriod`. Only employers who publish a range have salary data; the rest are `null`, never guessed.

#### Can I scrape remote jobs only?

Set **Work type** to remote, or paste a LinkedIn search URL that contains `f_WT=2`. Combine it with a country or "Worldwide" as the location to build a remote jobs feed.

#### Can I paste a LinkedIn job search URL?

Yes. Any `linkedin.com/jobs/search/?...` URL is understood, including its filters (`f_TPR`, `f_WT`, `f_E`, `f_JT`, `geoId`, `sortBy`). Single job URLs (`linkedin.com/jobs/view/<id>`) fetch that one job with full details.

#### Does it work with n8n, Make, Zapier and Google Sheets?

Yes. Use the Apify node or module with this actor, or the run-sync API endpoint shown above, and the flat rows map straight onto sheet columns and workflow fields. Schedules in Apify Console handle daily or hourly refreshes.

#### Can an AI agent use it through MCP?

Yes. Apify's MCP server exposes any actor as a tool; point your agent at this actor and it can search LinkedIn jobs on demand and receive the flat JSON rows as the tool result.

#### How is the LinkedIn Jobs Scraper priced?

The LinkedIn Jobs Scraper charges per job delivered, with no actor-start fee. Error rows, empty searches and pages that could not be fetched are not charged.

#### What happens when LinkedIn rate-limits the run?

The actor backs off exponentially and retries each request several times. If a page still fails, the job keeps its listing data and gets a `warning`; if a whole search is blocked you get an explanatory `error` row and the log tells you to lower parallelism or wait a few minutes.

#### Is scraping LinkedIn jobs legal?

The LinkedIn jobs scraper accesses only publicly available job postings, without login, the way a search engine does. How you use the data is your responsibility: check LinkedIn's terms and your local law, especially if you republish postings or combine them with personal data.

# Actor input Schema

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

One search per line, exactly what you would type into the LinkedIn jobs search box: job titles, skills or company names, e.g. "data analyst", "python developer", "fp\&a analyst". Each keyword is searched in the location below with the filters below.

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

City, region or country as LinkedIn spells it, e.g. "United States", "London, England, United Kingdom", "Stockholm, Sweden", "Remote". Applied to every keyword above.

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

Only jobs posted within this window. LinkedIn's own filter, applied server-side.

## `workType` (type: `string`):

On-site, remote or hybrid, as tagged by the employer on LinkedIn.

## `experienceLevel` (type: `string`):

LinkedIn's seniority filter.

## `jobType` (type: `string`):

Employment type filter.

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

Most recent first is best for monitoring and alerts; relevance mirrors what LinkedIn shows a visitor.

## `maxJobsPerQuery` (type: `integer`):

Cap per keyword (or per pasted search URL). LinkedIn itself stops serving results at roughly 1,000 per search, so split broad searches by location or date to get past that.

## `fetchJobDetails` (type: `boolean`):

Open every job page to add the full description, seniority level, employment type, job function, industries, applicant count, salary range and apply URL. Turn off for a faster listing-only run (title, company, location, date, link).

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

Paste LinkedIn job search URLs (https://www.linkedin.com/jobs/search/?keywords=...\&location=...\&f\_WT=2) or single job URLs (https://www.linkedin.com/jobs/view/4468253955). When this list is non-empty it is used INSTEAD of the keywords above; filters inside the URL win over the filter fields, which only fill in what the URL leaves out.

## `concurrency` (type: `integer`):

How many LinkedIn requests to run at once. 3 is safe from a single IP; lower it if you see rate-limit warnings in the log.

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

Optional. The public job listings do not need a proxy at normal volumes; switch one on only for very large runs that start getting HTTP 429 from LinkedIn.

## Actor input object example

```json
{
  "searchQueries": [
    "data analyst"
  ],
  "location": "United States",
  "datePosted": "pastWeek",
  "workType": "any",
  "experienceLevel": "any",
  "jobType": "any",
  "sortBy": "date",
  "maxJobsPerQuery": 50,
  "fetchJobDetails": true,
  "concurrency": 3,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

Every job with its details as flat JSON.

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

The same records as a spreadsheet-ready CSV file.

## `consoleView` (type: `string`):

Open the run's dataset in Apify Console.

# 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": [
        "data analyst"
    ]
};

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

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

```

## MCP server setup

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