# LinkedIn Jobs Scraper From $0.25/1k · No Start Fee · No Cookies (`memo23/linkedin-jobs-scraper`) Actor

\[From $0.25/1k] LinkedIn jobs scraper — no login, no cookies, no start fee. Search by keyword, URL or job ID with 1K-result-cap bypass and expiry monitoring. Salary, seniority, description, recruiter contacts and apply URL per job, plus optional deduped company firmographics.

- **URL**: https://apify.com/memo23/linkedin-jobs-scraper.md
- **Developed by:** [Muhamed Didovic](https://apify.com/memo23) (community)
- **Categories:** Jobs, AI, Agents
- **Stats:** 28 total users, 25 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $0.25 / 1,000 job results

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/platform/actors/running/actors-in-store#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

The fastest way to turn LinkedIn Jobs into structured, spreadsheet-ready data — **without a LinkedIn account, cookies, or login**.
Start from a search URL, a keyword + location, a single job URL, or a plain list of job IDs, and get clean job rows with title, company, salary, full description, apply URL, and recruiter contacts (best-effort) — as JSON or CSV.

**From $0.25 per 1,000 jobs** on higher plans ($0.69/1k on the free plan) — pay only for the jobs that land in your dataset. Filtered, duplicate, and previously-seen jobs cost nothing.

### Why Use This Scraper?

- ✅ **No login, no cookies, no account risk** — reads only LinkedIn's public Jobs surface
- ✅ **Cheapest per-job price in its class** — $0.69/1k free plan → $0.25/1k Gold and above
- ✅ **Recruiter & hiring-team contacts** (best-effort) — name, title, profile URL of the job poster
- ✅ **Bypass LinkedIn's ~1,000-results-per-search cap** — split by city or experience level automatically
- ✅ **Job monitoring built in** — paste job IDs, get back `isExpired` / `verifiedAt` / `verificationStatus` per listing
- ✅ **Free result filters** — title include/exclude, employer blocklist, and salary-required run *before* billing
- ✅ **Recruiter-grade search filters** — under-10-applicants only, Easy Apply only, minimum salary ($40K–$200K+)
- ✅ **🎯 Profile Match** — score every job 0–100 against your CV skills (`resumeKeywords`) and sort by fit
- ✅ **Cross-run dedup** — `onlyNewJobs` removes duplicate jobs across runs; you only ever pay for NEW jobs
- ✅ **Optional company firmographics** — website, size, employees, HQ, industry, follower count

### Overview

The LinkedIn Jobs Scraper is built for recruiters, lead-gen teams, job-market analysts, and job-board builders who need structured job-posting data from LinkedIn without maintaining a logged-in session.

The output is always **job-shaped rows**. Whether you start from a search URL, keyword filters, a single job URL, or a list of job IDs, every dataset item is one job posting with the same ~40-field schema — so your downstream pipeline never has to branch on input type.

Cookie-based LinkedIn scrapers require you to hand over a logged-in session (which gets accounts flagged) or buy session cookies from third parties. This actor reads the same public Jobs pages any anonymous browser can see, so no account is required and none is at risk. The trade-off is honest: a few fields LinkedIn reserves for logged-in viewers (some recruiter cards, some salary ranges) are best-effort.

### Supported Inputs

#### URL types

| URL type | Pattern | What it returns |
|---|---|---|
| Search URL | `linkedin.com/jobs/search?keywords=…&location=…` | Paginated job listings, filters read from the URL |
| Single-job URL | `linkedin.com/jobs/view/4410745146/` | One enriched row per URL, no search phase |
| Slug job URL | `linkedin.com/jobs/view/data-engineer-at-netflix-4350364210` | Same as above |

#### Copy-pasteable startUrls

```json
{
  "startUrls": [
    { "url": "https://www.linkedin.com/jobs/search?keywords=Software%20Engineer&location=United%20States&geoId=103644278" },
    { "url": "https://www.linkedin.com/jobs/view/4410745146/" }
  ]
}
```

#### Job IDs mode — scrape or re-verify exact listings

Paste raw job IDs, view URLs, or any LinkedIn URL carrying `currentJobId=` into the `jobIds` field. Each entry goes straight to the job detail page. Re-run the same list on a schedule to monitor listings — every row includes `isExpired`, `verifiedAt`, and `verificationStatus` (`active` / `closed` / `removed`), so dead listings are flagged instead of silently missing.

```json
{
  "jobIds": ["4410745146", "https://www.linkedin.com/jobs/view/4406118990/"]
}
```

#### Keyword / filter mode

Leave URLs empty and let the actor build the search: keywords × locations × time range × workplace type × experience level × contract type × company names.

```json
{
  "keywords": ["Data Engineer"],
  "location": "United States",
  "timeRange": "r604800",
  "remote": ["2"],
  "experienceLevels": ["4", "5"]
}
```

#### Unsupported inputs

- ❌ URLs behind the LinkedIn login wall (Sales Navigator, Recruiter seats, private postings)
- ❌ LinkedIn profile / company-page URLs (see the dedicated actors in *Explore More Scrapers*)
- ❌ Shortened or redirect URLs (`lnkd.in/…`)

### Use Cases

| Audience | Use case |
|---|---|
| **Recruiters & sourcing teams** | Pull hiring-manager contacts from open roles and reach out before the inbox floods |
| **B2B lead-gen / HR-tech vendors** | Companies actively hiring for X are in-market for your product — build the list daily |
| **Job boards & aggregators** | Backfill listings at $0.25–0.69/1k with cross-run dedup so you only ingest new postings |
| **Market & salary analysts** | Track demand by title, region, seniority, and published salary bands |
| **Sales intelligence teams** | A competitor's job posts reveal their stack, roadmap, and the decision-makers to pitch |
| **Agencies** | Deliver client-ready hiring datasets on a schedule without writing scrapers |

### How It Works

![How It Works](https://muhamed-didovic.github.io/assets/how-it-works-linkedin-jobs.png)

1. **Input** — paste search URLs, job URLs, job IDs, or set keyword + location filters
2. **Route** — search inputs paginate the public Jobs listing; job IDs and view URLs skip straight to the detail page
3. **Filter (free)** — title include/exclude and employer blocklist drop unwanted jobs *before* they're collected or billed
4. **Collect & enrich** — every kept job gets the full ~40-field row; optional company firmographics per unique employer
5. **Output** — JSON or CSV dataset, plus a `RUN_SUMMARY` record explaining exactly how the run ended

### Input Configuration

#### Input fields

| Field | Type | Required | Notes |
|---|---|---|---|
| `startUrls` | `array<{url}>` | one input mode | Mix of search URLs and single-job view URLs |
| `jobIds` | `array<string>` | one input mode | Raw IDs, view URLs, or `currentJobId=` URLs — direct to detail page |
| `keywords` | `array<string>` | one input mode | Used when no URLs are given |
| `location` / `locations` | `string` / `array` | optional | Broad location, or several locations searched in one run |
| `geoId`, `placeIds` | `string`, `array` | optional | LinkedIn geo precision (`geoId`, `f_PP` city-level filters) |
| `timeRange` | enum | optional | `any time`, last 24h (`r86400`), week (`r604800`), month (`r2592000`) |
| `remote` / `jobTypes` | `array` enum | optional | On-site `1` / Remote `2` / Hybrid `3` |
| `experienceLevels` | `array` enum | optional | `1` intern → `6` director |
| `contractType` | `array` enum | optional | `F`/`P`/`C`/`T`/`I`/`V`/`O` |
| `companyNames` | `array<string>` | optional | Only include these employers (query-injected + name-matched) |
| `filterUnder10Applicants` | `boolean` | optional | LinkedIn's own "early applicant" filter — jobs with <10 applicants |
| `filterEasyApply` | `boolean` | optional | Only jobs that support Easy Apply (apply without leaving LinkedIn) |
| `salaryBase` | enum `1`–`9` | optional | LinkedIn's minimum-salary filter, $40K+ → $200K+ (mostly US) |
| `distance` | enum (miles) | optional | Search radius around the location (LinkedIn's own `distance` filter) |
| `titleMustInclude` | `array<string>` | optional | Keep only titles containing at least one term — free, pre-billing |
| `titleExclude` | `array<string>` | optional | Drop titles containing any term — free, pre-billing |
| `excludeCompanies` | `array<string>` | optional | Drop these employers — free, pre-billing |
| `subLocationExclude` | `array<string>` | optional | Drop jobs whose location contains any term (e.g. a city) — free, pre-billing |
| `requireSalaryInfo` | `boolean` | optional | Drop jobs without a published salary — free, pre-billing |
| `resumeKeywords` | `array` | optional | 🎯 Profile Match: score each job 0–100 vs your CV skills (never filters) |
| `scrapeCompanyDetails` | `boolean` | optional | Company firmographics; billed per **unique** company |
| `onlyNewJobs` | `boolean` | optional | Cross-run dedup — only jobs not returned in previous runs |
| `autoSplit` | `boolean` | optional | Bypass ~1K cap by splitting the search across experience levels |
| `splitByLocation` + `splitCountry` | `boolean` + enum | optional | Bypass ~1K cap by fanning out across a country's major cities |
| `maxItems` | `integer` | optional | Hard cap on charged dataset rows |
| `minDelay`/`maxDelay`, `minConcurrency`/`maxConcurrency`, `maxRequestRetries` | `integer` | optional | Crawl pacing |
| `proxy` | `object` | optional | Residential recommended |

#### 🔎 Dynamic filters

Unlike the free filters above (which drop jobs *before* you pay), dynamic filters are evaluated **after** each job — and, for the company-based ones, its company page — is fetched. So every job is still saved and charged; non-matching jobs are stamped **`dynamicFilterMatch: false`** so you can discard them downstream (e.g. keep only `dynamicFilterMatch == true` in your N8N/Make flow). Per dimension, an `*Include` list takes priority over its `*Exclude` sibling. Company-based filters auto-enable company enrichment; companies missing a value always pass.

| Field | Type | Notes |
|---|---|---|
| `jobFunctionInclude` / `jobFunctionExclude` | `array` enum | Keep / flag by LinkedIn job function |
| `jobIndustryInclude` / `jobIndustryExclude` | `array<string>` | Keep / flag by industry name (e.g. `Staffing and Recruiting`) |
| `excludeRecruitingAgencies` | `boolean` | Flag staffing / recruiting-agency postings |
| `companySizeMin` / `companySizeMax` | `integer` | Flag by company employee count |
| `companyOrganizationTypeInclude` / `…Exclude` | `array` enum | Flag by company type (Public Company, Non Profit, Privately Held, …) |
| `companyFoundedDateMin` / `companyFoundedDateMax` | `integer` (year) | Flag by company founding year |
| `companyFollowersCountMin` / `companyFollowersCountMax` | `integer` | Flag by LinkedIn follower count |
| `requireRecruiterProfile` | `boolean` | Flag postings that expose no recruiter / hiring-team profile |

#### Common scenarios

**1. Daily new-jobs monitor (schedule this)**

```json
{
  "keywords": ["DevOps Engineer"],
  "location": "Germany",
  "timeRange": "r86400",
  "onlyNewJobs": true,
  "titleExclude": ["senior", "principal"],
  "maxItems": 200
}
```

**2. Re-verify a list of tracked listings**

```json
{
  "jobIds": ["4410745146", "4406118990", "4350364210"]
}
```

**3. Break the 1,000-result cap for a whole country**

```json
{
  "keywords": ["Nurse"],
  "splitByLocation": true,
  "splitCountry": "GB",
  "maxItems": 5000
}
```

### Output Overview

Each dataset item is one job posting containing:

- **Core fields** — `id`, `title`, `company`, `location`, `postedAt`, `jobUrl`, full `description` (+ `descriptionHtml`)
- **Compensation & criteria** — parsed `salary` (`minAmount` / `maxAmount` / `currency` / `period`), `seniorityLevel`, `employmentType`, `jobFunction`, `industries`, `benefits`
- **Application** — `applyUrl` (external careers site when exposed), `easyApply`, `numberOfApplicants` / `applicantsCount`
- **People** — `hiringTeam[]` and flat `jobPosterName` / `jobPosterTitle` / `jobPosterProfileUrl` (best-effort; empty means LinkedIn hid it from anonymous viewers)
- **Company** — `companyLinkedinUrl` always; with `scrapeCompanyDetails` also website, industry, size, employee count, HQ, logo, follower count, organization type, founded year, specialties, office locations
- **Targeting** — `yearsOfExperience[]` parsed from the description, and `dynamicFilterMatch` (the 🔎 dynamic-filter verdict) on every row
- **Verification** — `isExpired`, `verifiedAt`, `verificationStatus` (`active` / `closed` / `removed`) on every row
- **Provenance** — `scrapedFrom` (`search-results` / `job-view-url` / `job-id-input`), `sourceUrl`

Every run also writes a **`RUN_SUMMARY`** record to the key-value store: requested vs saved counts, pages parsed, jobs dropped per filter, expired jobs found, and a `stopReason` — so "why did I get 400 instead of 1000?" is answered by the run itself.

### Output Samples

#### Search start (trimmed real row)

```jsonc
{
  "id": "4350364210",
  "title": "Data Engineer (L5)",
  "company": "Netflix",
  "companyLinkedinUrl": "https://www.linkedin.com/company/netflix",
  "location": "United States",
  "seniorityLevel": "Not Applicable",
  "employmentType": "Full-time",
  "industries": ["Entertainment Providers"],
  "postedAt": "2026-07-02",
  "postedTimeAgo": "5 days ago",
  "numberOfApplicants": "Over 200 applicants",
  "applicantsCount": 200,
  "easyApply": false,
  "description": "At Netflix, our mission is to entertain the world. Together, we are writing the next episode…",
  "isExpired": false,
  "verifiedAt": "2026-07-08T03:58:41.120Z",
  "verificationStatus": "active",
  "jobUrl": "https://www.linkedin.com/jobs/view/data-engineer-l5-at-netflix-4350364210",
  "scrapedFrom": "search-results"
  /* …plus salary, applyUrl, hiringTeam, jobPoster*, company* fields… */
}
```

#### Job-ID start — removed listing (monitoring)

```jsonc
{
  "id": "3000000001",
  "title": null,
  "company": null,
  "jobUrl": "https://www.linkedin.com/jobs/view/3000000001/",
  "isExpired": true,
  "verifiedAt": "2026-07-08T03:57:33.319Z",
  "verificationStatus": "removed",
  "scrapedFrom": "job-id-input"
}
```

### Key Output Fields

#### Job core

- `id`, `title`, `company`, `location`, `postedAt`, `postedTimeAgo`, `jobUrl`, `sourceUrl`

#### Description & criteria

- `description`, `descriptionHtml`, `criteria[]`, `seniorityLevel`, `employmentType`, `jobFunction`, `industries[]`, `benefits[]`, `workplaceType`
- `yearsOfExperience[]` — structured experience requirements parsed from the description (`{ years: "5+" | "2-5" | "3", context, lang }`); empty when none found
- `dynamicFilterMatch` — `true` when the job passed every active 🔎 dynamic filter (or none were set); `false` flags a non-match (the row is still returned)

#### Compensation & application

- `salary.raw`, `salary.minAmount`, `salary.maxAmount`, `salary.currency`, `salary.period`
- `applyUrl`, `easyApply`, `numberOfApplicants`, `applicantsCount`

#### Recruiter / hiring team (best-effort)

- `hiringTeam[].name`, `hiringTeam[].title`, `hiringTeam[].linkedinUrl`, `hiringTeam[].photo`
- `jobPosterName`, `jobPosterTitle`, `jobPosterProfileUrl`, `jobPosterPhoto`

#### Company (flat fields filled by `scrapeCompanyDetails`)

- `companyLinkedinUrl`, `companyLinkedinSlug`, `companyWebsite`, `companyIndustry`, `companySize`, `companyEmployeesCount`, `companyHeadquarters`, `companyFollowerCount`, `companyLogo`, `companyDetails`
- `companyOrganizationType`, `companyFoundedDate`, `companySpecialties[]`, `companyOfficeLocations[]` (best-effort from the public company page)

#### Profile Match (with `resumeKeywords`)

- `profileMatchScore` (0–100 = % of your skills found in title + description)
- `profileMatch.matchedKeywords[]`, `profileMatch.missingKeywords[]`, `profileMatch.matchCount`, `profileMatch.totalKeywords`

#### Verification & provenance

- `isExpired`, `verifiedAt`, `verificationStatus`, `scrapedFrom`

### FAQ

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

No. The actor reads only the public Jobs surface that any anonymous browser can access. No login, no cookies, no account at risk.

#### Why did I get fewer jobs than `maxItems`?

Check the `RUN_SUMMARY` record in the run's key-value store — it reports pages parsed, candidates seen, how many jobs each filter dropped, and the `stopReason`. The most common causes: the search genuinely has fewer results, LinkedIn's ~1,000-per-search ceiling (enable `autoSplit` or `splitByLocation`), or your title/company filters doing their job.

#### Are the title and company filters really free?

Yes. `titleMustInclude`, `titleExclude`, and `excludeCompanies` run before a job is collected, so filtered jobs are never charged and never consume detail-page requests.

#### Are recruiter contacts always available?

No — best-effort. LinkedIn shows the hiring-team card to anonymous viewers on some jobs and hides it on others. `hiringTeam: []` means LinkedIn hid it, not that the parser failed. Same honesty applies to `salary` and `applyUrl`: they're populated when the employer published them.

#### How do I monitor jobs for expiry?

Put the job IDs (or view URLs) in `jobIds` and run on a schedule. Live listings return full rows with `verificationStatus: "active"`; closed listings are flagged `"closed"`; listings deleted from LinkedIn come back as sparse rows with `"removed"` and `isExpired: true`.

#### How do I get more than 1,000 jobs per search?

LinkedIn caps every guest search at ~1,000 results. Enable `splitByLocation` (fans the search out across a country's major cities) or `autoSplit` (splits across the six experience levels). Results are deduplicated automatically.

#### How do I find jobs with the best odds of a reply?

Turn on `filterUnder10Applicants` — LinkedIn's own "early applicant" filter — so every returned job has fewer than 10 applicants. Combine with `timeRange: "r86400"` (last 24h) and a scheduled daily run with `onlyNewJobs` for a fully automated early-bird pipeline.

#### How does Profile Match (`resumeKeywords`) work?

Add your skills as plain strings or `{ "keyword": "JavaScript", "aliases": ["JS"] }` objects. Every job is scored — `profileMatchScore` is the % of your skills found in its title + description, and `profileMatch` lists exactly which matched and which are missing. It never drops jobs; sort the dataset by `profileMatchScore` descending, or have your N8N/Make workflow act only on scores above a threshold.

#### What does `onlyNewJobs` do?

It remembers the job IDs from your previous runs and skips them, so a scheduled run only returns postings it has never sent you before.

#### Can I scrape private or logged-in-only postings?

No. Anything behind the LinkedIn login wall (Sales Navigator, Recruiter, private postings) is out of scope.

### Support

Found a bug or have a feature request? Open an issue on the actor's [Issues tab](https://apify.com/memo23/linkedin-jobs-scraper/issues) or email me at muhameddidovic@gmail.com.

### Additional Services

Need a custom export shape, an extra field, or a managed daily feed delivered to your database? I do tailored work — drop me a line at muhameddidovic@gmail.com.

### Explore More Scrapers

If you found this useful, you might also like:

- [**LinkedIn Posts Scraper**](https://apify.com/memo23/linkedin-posts-scraper) — posts from profiles and company pages, no cookies
- [**LinkedIn Company Employees Scraper**](https://apify.com/memo23/linkedin-company-people-scraper) — people behind any company page
- [**LinkedIn Ads Scraper**](https://apify.com/memo23/linkedin-ads-scraper) — ad library search and details
- [**LinkedIn Profile Scraper**](https://apify.com/memo23/linkedin-profiles-cheerio) — public profile data at $1/1k

Full list at [apify.com/memo23](https://apify.com/memo23).

***

### ⚠️ Disclaimer

This Actor is an independent tool and is not affiliated with, endorsed by, or sponsored by LinkedIn Corporation or any of its subsidiaries. All trademarks mentioned are the property of their respective owners.

The scraper accesses only publicly available LinkedIn Jobs pages — no authenticated endpoints, paid features, or content behind the linkedin.com login wall. Users are responsible for ensuring their use complies with LinkedIn's Terms of Service, applicable data-protection law (GDPR, CCPA, etc.), and any contractual obligations of their own organization.

***

### SEO Keywords

linkedin jobs scraper, scrape linkedin jobs, linkedin job scraper, linkedin jobs API, linkedin job postings scraper, Apify linkedin jobs, job listings scraper, job board scraper, hiring data scraper, recruiter contact scraper, job monitoring tool, job posting verification, linkedin jobs without login, no-cookie linkedin scraper, job market analysis data, salary benchmark data, recruitment lead generation, hiring manager contacts, employment data extraction, HR tech data, talent sourcing data

# Actor input Schema

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

Paste any mix of LinkedIn Jobs URLs. Examples: search URL <code>https://www.linkedin.com/jobs/search?keywords=…</code>, single-job URL <code>https://www.linkedin.com/jobs/view/4410745146/</code>, or slug-with-id form <code>https://www.linkedin.com/jobs/view/mobile-engineer-at-jll-4410745146</code>. Search URLs extract filters (keywords, location, geoId, f\_PP, workplace type, experience, contract type) from the URL itself. View URLs go straight to the detail page — no search step.

## `jobIds` (type: `array`):

Scrape (or re-verify) exact LinkedIn job listings — one entry per line. Accepts raw job IDs (<code>4410745146</code>), view URLs (<code>https://www.linkedin.com/jobs/view/4410745146/</code>), or any LinkedIn URL carrying <code>currentJobId=…</code>. Each entry goes straight to the job detail page — no search phase. Re-run the same list on a schedule to monitor listings: every row includes <code>isExpired</code>, <code>verifiedAt</code>, and <code>verificationStatus</code> (<code>active</code> / <code>closed</code> / <code>removed</code>), so dead listings are flagged instead of silently missing.

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

Keywords or job titles to search for in the job listings.

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

Broad LinkedIn search location, such as a country or region. Example: `United States`. For city-level filtering, also use `placeIds` or paste a full LinkedIn search URL above.

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

Search several locations in one run. Each location is combined with every keyword above. Example: <code>United States</code>, <code>Germany</code>, <code>Remote</code>. When set, this takes precedence over the single Broad location field. Ignored when Start URLs are provided.

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

Optional LinkedIn geoId for the broad location. Example: `103644278` for United States. This usually works together with the broad location field above.

## `placeIds` (type: `array`):

Optional LinkedIn place filter IDs (`f_PP`) for city-level or sub-location filtering. Example: `104555257` for Santiago. Use one or more IDs copied from a LinkedIn Jobs URL.

## `timeRange` (type: `string`):

Time range to search for in the job listings.

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

Select the types of work arrangements to include in the search.

## `remote` (type: `array`):

Select the workplace type filters to include in the search.

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

Select the experience levels to include in the search.

## `experienceLevel` (type: `array`):

Alias for Experience Levels; select one or more experience codes to include.

## `contractType` (type: `array`):

Select contract types to include in the search.

## `companyNames` (type: `array`):

Only return jobs from these companies. Each name is resolved to its LinkedIn company ID and filtered server-side (exact); if a name can't be resolved it falls back to name matching. <strong>Ignored when a search URL is provided</strong> — that URL defines the search.

## `filterUnder10Applicants` (type: `boolean`):

Only return jobs with fewer than 10 applicants (LinkedIn's own 'early applicant' filter, <code>f\_EA=true</code>). The best odds of a reply — apply before the crowd arrives. Keyword-search only — a pasted search URL runs verbatim (add <code>f\_EA=true</code> to the URL instead).

## `filterEasyApply` (type: `boolean`):

Only return jobs that support LinkedIn Easy Apply (<code>f\_AL=true</code>) — apply directly on LinkedIn without leaving the site. Keyword-search only — a pasted search URL runs verbatim (add <code>f\_AL=true</code> to the URL instead).

## `salaryBase` (type: `string`):

Only return jobs at or above this base salary, using LinkedIn's own salary filter (<code>f\_SB2</code>). LinkedIn applies it where salary data exists — mostly US searches.

## `distance` (type: `string`):

Search radius in miles around the location, using LinkedIn's own <code>distance</code> filter. Applies to keyword searches; for a pasted search URL the URL's own <code>distance</code> value wins.

## `titleMustInclude` (type: `array`):

Keep only jobs whose title contains at least ONE of these terms (case-insensitive). Example: <code>senior</code>, <code>staff</code> keeps "Senior Backend Engineer" and "Staff Engineer" but drops "Junior Developer". Leave empty to keep all titles.

## `titleExclude` (type: `array`):

Drop jobs whose title contains ANY of these terms (case-insensitive). Example: <code>intern</code>, <code>junior</code>, <code>unpaid</code>. Applied after 'Title must include'.

## `excludeCompanies` (type: `array`):

Drop jobs from these companies (case-insensitive name match, so <code>Microsoft</code> also skips "Microsoft Corporation"). Useful for filtering out staffing agencies or your own company's postings.

## `subLocationExclude` (type: `array`):

Drop jobs whose location contains ANY of these terms (case-insensitive substring). Example: <code>Laval</code> skips every "Laval, Quebec, Canada" job. Applied BEFORE the job is fetched, so skipped jobs are never charged. Applies to search results; direct Job IDs / view URLs are always returned.

## `requireSalaryInfo` (type: `boolean`):

Only keep jobs whose posting publishes a salary. Jobs without a visible salary block are dropped for free — they never reach the dataset and are never charged. (Direct Job IDs / view URLs are always returned as requested.)

## `jobFunctionInclude` (type: `array`):

Keep (dynamicFilterMatch:true) only jobs whose LinkedIn job function is one of these. Takes priority over Exclude when both are set.

## `jobFunctionExclude` (type: `array`):

Flag jobs whose LinkedIn job function is any of these as <code>dynamicFilterMatch: false</code>. Ignored when Include job functions is set.

## `jobIndustryInclude` (type: `array`):

Keep (dynamicFilterMatch:true) only jobs whose industry matches ONE of these terms (case-insensitive). Free text — use LinkedIn industry names, e.g. <code>Software Development</code>, <code>Staffing and Recruiting</code>. Takes priority over Exclude industries when both are set.

## `jobIndustryExclude` (type: `array`):

Flag jobs whose industry matches ANY of these terms as <code>dynamicFilterMatch: false</code> (case-insensitive). Ignored when Include industries is set. Example: <code>Staffing and Recruiting</code>.

## `excludeRecruitingAgencies` (type: `boolean`):

Flag jobs posted by staffing and recruiting agencies as <code>dynamicFilterMatch: false</code> (matched on company industry). Automatically enables company enrichment.

## `companySizeMin` (type: `integer`):

Flag jobs from companies with FEWER employees than this as <code>dynamicFilterMatch: false</code>. Companies with no employee count on LinkedIn always pass. Automatically enables company enrichment.

## `companySizeMax` (type: `integer`):

Flag jobs from companies with MORE employees than this as <code>dynamicFilterMatch: false</code>. Companies with no employee count on LinkedIn always pass. Automatically enables company enrichment.

## `companyOrganizationTypeInclude` (type: `array`):

Keep (dynamicFilterMatch:true) only jobs whose company organization type is one of these. Companies with no type available always pass. Takes priority over Exclude when both are set. Automatically enables company enrichment.

## `companyOrganizationTypeExclude` (type: `array`):

Flag jobs whose company organization type is any of these as <code>dynamicFilterMatch: false</code>. Companies with no type available always pass. Ignored when Include is set. Automatically enables company enrichment.

## `companyFoundedDateMin` (type: `integer`):

Keep (dynamicFilterMatch:true) only jobs from companies founded in or after this year. Companies with no founding year always pass. Automatically enables company enrichment.

## `companyFoundedDateMax` (type: `integer`):

Keep (dynamicFilterMatch:true) only jobs from companies founded in or before this year. Companies with no founding year always pass. Automatically enables company enrichment.

## `companyFollowersCountMin` (type: `integer`):

Keep (dynamicFilterMatch:true) only jobs from companies with at least this many LinkedIn followers. Companies with no follower count always pass. Automatically enables company enrichment.

## `companyFollowersCountMax` (type: `integer`):

Keep (dynamicFilterMatch:true) only jobs from companies with at most this many LinkedIn followers. Companies with no follower count always pass. Automatically enables company enrichment.

## `requireRecruiterProfile` (type: `boolean`):

Flag jobs that have NO recruiter/hiring-team profile on the posting as <code>dynamicFilterMatch: false</code>.

## `resumeKeywords` (type: `array`):

⚠️ <strong>This is NOT a search filter</strong> — it does not change which jobs LinkedIn returns. Each job in the output is scored against these skills: <code>profileMatchScore</code> (0–100 = % of your keywords found in the job title + description), plus <code>profileMatch.matchedKeywords</code> / <code>profileMatch.missingKeywords</code>. Entries are plain strings (<code>"TypeScript"</code>) or objects with aliases so abbreviations count as the same skill: <code>{ "keyword": "JavaScript", "aliases": \["JS"] }</code>. Matching is case-insensitive on whole words — <code>Java</code> will not match <code>JavaScript</code>.

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

Enrich each row with company website, industry, size, headquarters, logo, description, and follower count (best-effort, from the public company page) — surfaced as flat <code>company\*</code> fields plus a full <code>companyDetails</code> object. Adds one request per <strong>unique</strong> company (cached within a run) and is <strong>billed as a separate per-company event</strong> on top of the per-result price. Off by default — enable only when you need firmographics.

## `onlyNewJobs` (type: `boolean`):

Remember job IDs across runs and skip any job already returned in a previous run — you only ever receive (and pay for) NEW jobs. Ideal for scheduled daily job-alert runs. Stored in the actor's key-value store.

## `autoSplit` (type: `boolean`):

LinkedIn's guest search caps each query at ~1000 results. When enabled (and no experience level is pinned), the search is split across all six experience levels and the deduplicated results are merged, returning well beyond 1000 jobs per search. This and 'Split by city' are alternative cap-busting strategies — if both are on, 'Split by city' takes precedence (it yields more unique jobs per request).

## `splitByLocation` (type: `boolean`):

LinkedIn caps each search at ~1000 results. When enabled, the search fans out across the major cities of the chosen Split country and merges the deduplicated results — typically returning many times more jobs than a single country query. Raise Max items to capture them, since each city is a separate search. Takes precedence over 'Split by experience' when both are on. Ignored when Start URLs are provided.

## `splitCountry` (type: `string`):

Country whose major cities are fanned out when 'Split by city' is enabled. Pick the country your search targets. Has no effect unless 'Split by city' is on.

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

Maximum number of items that will be scraped.

## `minDelay` (type: `integer`):

Minimum delay to wait before firing another request. This is useful to avoid getting blocked by Linkedin. The delay is in seconds.

## `maxDelay` (type: `integer`):

Maximum delay to wait before firing another request. This is useful to avoid getting blocked by Linkedin. The delay is in seconds.

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

Maximum number of pages that can be processed at the same time.

## `minConcurrency` (type: `integer`):

Minimum number of pages that will be processed at the same time.

## `maxRequestRetries` (type: `integer`):

Number of times the crawler will retry a failed request before giving up.

## `proxy` (type: `object`):

Leave empty — the actor already routes all traffic through its own built-in residential proxy at no extra cost to you. Only set this if you want to use your own proxies.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.linkedin.com/jobs/search?keywords=Software%20Engineer&location=United%20States&geoId=103644278"
    }
  ],
  "jobIds": [],
  "keywords": [
    "Software Engineer"
  ],
  "location": "United States",
  "locations": [
    "United States",
    "United Kingdom"
  ],
  "geoId": "103644278",
  "placeIds": [
    "104555257"
  ],
  "timeRange": "any time",
  "jobTypes": [
    "1",
    "3"
  ],
  "remote": [
    "2"
  ],
  "experienceLevels": [
    "4",
    "5",
    "6"
  ],
  "experienceLevel": [
    "5"
  ],
  "contractType": [
    "F"
  ],
  "companyNames": [],
  "filterUnder10Applicants": false,
  "filterEasyApply": false,
  "salaryBase": "",
  "distance": "",
  "titleMustInclude": [],
  "titleExclude": [],
  "excludeCompanies": [],
  "subLocationExclude": [],
  "requireSalaryInfo": false,
  "jobFunctionInclude": [],
  "jobFunctionExclude": [],
  "jobIndustryInclude": [],
  "jobIndustryExclude": [],
  "excludeRecruitingAgencies": false,
  "companyOrganizationTypeInclude": [],
  "companyOrganizationTypeExclude": [],
  "requireRecruiterProfile": false,
  "resumeKeywords": [
    {
      "keyword": "TypeScript",
      "aliases": [
        "TS"
      ]
    },
    {
      "keyword": "React"
    },
    {
      "keyword": "Node.js",
      "aliases": [
        "NodeJS",
        "Node"
      ]
    }
  ],
  "scrapeCompanyDetails": false,
  "onlyNewJobs": false,
  "autoSplit": false,
  "splitByLocation": false,
  "splitCountry": "US",
  "maxItems": 1000,
  "minDelay": 5,
  "maxDelay": 10,
  "maxConcurrency": 2,
  "minConcurrency": 1,
  "maxRequestRetries": 8
}
```

# 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 = {
    "startUrls": [
        {
            "url": "https://www.linkedin.com/jobs/search?keywords=Software%20Engineer&location=United%20States&geoId=103644278"
        }
    ],
    "keywords": [
        "Software Engineer"
    ],
    "location": "United States",
    "locations": [
        "United States",
        "United Kingdom"
    ],
    "geoId": "103644278",
    "placeIds": [
        "104555257"
    ],
    "jobTypes": [
        "1",
        "3"
    ],
    "remote": [
        "2"
    ],
    "experienceLevels": [
        "4",
        "5",
        "6"
    ],
    "experienceLevel": [
        "5"
    ],
    "contractType": [
        "F"
    ],
    "resumeKeywords": [
        {
            "keyword": "TypeScript",
            "aliases": [
                "TS"
            ]
        },
        {
            "keyword": "React"
        },
        {
            "keyword": "Node.js",
            "aliases": [
                "NodeJS",
                "Node"
            ]
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("memo23/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 = {
    "startUrls": [{ "url": "https://www.linkedin.com/jobs/search?keywords=Software%20Engineer&location=United%20States&geoId=103644278" }],
    "keywords": ["Software Engineer"],
    "location": "United States",
    "locations": [
        "United States",
        "United Kingdom",
    ],
    "geoId": "103644278",
    "placeIds": ["104555257"],
    "jobTypes": [
        "1",
        "3",
    ],
    "remote": ["2"],
    "experienceLevels": [
        "4",
        "5",
        "6",
    ],
    "experienceLevel": ["5"],
    "contractType": ["F"],
    "resumeKeywords": [
        {
            "keyword": "TypeScript",
            "aliases": ["TS"],
        },
        { "keyword": "React" },
        {
            "keyword": "Node.js",
            "aliases": [
                "NodeJS",
                "Node",
            ],
        },
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("memo23/linkedin-jobs-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "startUrls": [
    {
      "url": "https://www.linkedin.com/jobs/search?keywords=Software%20Engineer&location=United%20States&geoId=103644278"
    }
  ],
  "keywords": [
    "Software Engineer"
  ],
  "location": "United States",
  "locations": [
    "United States",
    "United Kingdom"
  ],
  "geoId": "103644278",
  "placeIds": [
    "104555257"
  ],
  "jobTypes": [
    "1",
    "3"
  ],
  "remote": [
    "2"
  ],
  "experienceLevels": [
    "4",
    "5",
    "6"
  ],
  "experienceLevel": [
    "5"
  ],
  "contractType": [
    "F"
  ],
  "resumeKeywords": [
    {
      "keyword": "TypeScript",
      "aliases": [
        "TS"
      ]
    },
    {
      "keyword": "React"
    },
    {
      "keyword": "Node.js",
      "aliases": [
        "NodeJS",
        "Node"
      ]
    }
  ]
}' |
apify call memo23/linkedin-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=memo23/linkedin-jobs-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/HmaXqAAARLMd99M5f/builds/MLxCboax9U7PYtyuh/openapi.json
