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

Scrape LinkedIn job listings without login, API key, or browser. Get titles, companies, salaries, applicant counts, and full job descriptions for any role and city. Built for job market research, recruiting, and hiring-signal lead generation.

- **URL**: https://apify.com/labrat011/linkedin-jobs-scraper.md
- **Developed by:** [mick\_](https://apify.com/labrat011) (community)
- **Categories:** Jobs, Lead generation, Social media
- **Features:** Limited permissions
- **Stats:** 145 total users, 7 monthly users, 100.0% runs succeeded, 4 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 results

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

<img src="https://apify-image-uploads-prod.s3.us-east-1.amazonaws.com/wCP1WauwRX2Gr3Gir-actor-M6raT3nAnZ4orKQho-NpGHePwS1J-linkedin-jobs-scraper.png" alt="Linkedin Jobs Scraper logo" width="120">

## LinkedIn Jobs Scraper

Scrape LinkedIn job listings and job postings at scale. **No API key. No login. No browser. No cookies.** Pure HTTP: fast, cheap, and reliable. Get the job title, company, location, salary, applicant count, and full job description for any role in any city. Built for job market research, recruiting and talent sourcing, hiring-signal lead generation, and AI agents.

***

### What it does

This actor scrapes job listings from LinkedIn's public job search pages and returns clean, structured JSON. Give it keywords and a location, a batch of both, or just paste the search URL off linkedin.com. A country-wide search can be split into per-city searches to get past LinkedIn's 1,000-result ceiling.

**Key data extracted:**

- Job title, company, location, posted date, salary
- Full job description (plain text + HTML)
- Seniority level, employment type, job function, industries
- Applicant count
- Company size (employee count) and industry, when LinkedIn shows them

***

### What's new

#### 🔗 Paste a LinkedIn search URL

Build the search on linkedin.com the way you normally would, copy the address bar, drop it into `searchUrls`. Keywords, location, geo ID, radius and date range are read straight out of it, nothing to retype.

Paste several and each becomes its own search, keeping its own city and radius, so one run can cover Boise at 5 miles and Denver at 100. If the URL carries filters LinkedIn discards for logged-out clients (`f_WT`, `f_E`, `f_JT`), the run log names each one instead of letting you assume it applied.

#### 🏙️ Split a country into cities, past the 1,000 cap

LinkedIn returns at most 1,000 results for any one search, so a country-wide query cannot go deeper however many jobs exist. Set `splitByCity: true` and a country becomes one search per major city.

This is worth the extra searches because cities return **genuinely different jobs**, not the same ones relabelled. Measured on `registered nurse`, serially, with no blocked responses:

| | unique jobs | absent from a 500-job US walk |
|---|---|---|
| New York | 150 | 145 (97%) |
| Houston | 150 | 146 (97%) |
| Phoenix | 150 | 148 (99%) |

Those three cities shared **nothing at all** with one another, and together took reachable inventory from 500 to 939. Ten cities scale roughly the same way.

Supported: United States (30 cities), United Kingdom (12), Canada (10), Australia (8), Germany (8), India (8). `maxCities` controls how many are used, largest markets first. A location that is already a city, or a country not on that list, is searched exactly as you typed it.

#### 📏 Radius, in miles

`distance` narrows or widens the search around your location, a real filter, verified before shipping rather than assumed. Around Boise, `distance: 5` returns only Boise postings while `distance: 100` also returns Caldwell, Gooding, Hailey, McCall and Meridian. An identical request run twice returns an identical set, so that difference is the filter working, not result churn.

#### 🛡️ A throttled run now says it was throttled

When an offset genuinely has no results, LinkedIn answers with a 26-byte stub. When it is rate-limiting you, it answers with a full page carrying no listings. Earlier versions treated both as "no more results", stopped early, and still reported success, so a truncated run was indistinguishable from a complete one.

Such a page is now retried once. If it stays empty the run says the results may be incomplete, rather than implying that was everything LinkedIn had.

#### ⚡ Faster runs, parallel pagination

Search pages are now fetched in concurrent batches instead of one at a time, cutting page-walk time **up to ~4x** on large or heavily-filtered searches. Same results, same order, same low cost, just faster. Smart early-stop still kicks in the moment a search is exhausted, so you never pay for pages you don't need.

#### 🎯 Title-only filtering

Set `titleOnly: true` to keep only jobs whose **title** contains your keyword, perfect for precise role targeting (e.g. only actual "Product Analyst" titles, not every job that mentions "product" in the description). See the note in [Input reference](#input-reference) on using plain terms vs. Boolean syntax.

#### Batch Search

Run multiple keywords and locations in a single actor run. Use `keywordsList` and `locationsList`, the actor runs all combinations automatically and deduplicates results.

**Example:** `['python developer', 'data engineer'] × ['New York', 'Remote']` = 4 searches, one run.

#### Full Job Details

Enable `fetchJobDetails` to load each job's detail page and extract the full description (text + HTML), seniority level, employment type, job function, industry, applicant count, and salary when LinkedIn shows it.

#### Company Filter

Only want jobs from specific companies? Pass a list of company names or LinkedIn slugs and the actor filters automatically, no post-processing needed.

#### Description HTML

Both plain text and raw HTML of the job description are now included in the output, useful for agents that need to parse or render the content.

***

### Use cases

#### Lead Generation

Turn LinkedIn hiring activity into a prospect pipeline:

- Find companies actively hiring in your target space
- Use company employee count and industry to qualify leads
- Feed into Clay, HubSpot, or Salesforce via Apify integrations

#### AI Agent Integration (MCP)

Use this actor as a live data source for AI agents:

- Search LinkedIn jobs in real time from Claude, GPT, or any MCP-compatible agent
- Pull structured job data for matching, gap analysis, or market research
- Batch search multiple roles and locations from a single agent prompt
- No authentication required, works out of the box with Apify's hosted MCP server

#### Job Market Research

- Track hiring trends across roles, locations, and industries
- Monitor which companies are expanding (by volume of postings)
- Analyze salary ranges and experience requirements over time
- Compare seniority and employment-type mix across markets

#### Recruitment & Talent Intelligence

- Build sourcing pipelines across multiple job titles and cities at once
- Identify which companies are hiring for specific roles
- Track applicant counts to gauge competition for specific roles

***

### Example tasks

Ready-to-run configurations for common jobs. Run one as-is, then swap in
your own keywords, locations, or companies.

#### 🧑‍💻 Scrape Software Engineer Jobs Posted Today (US)

Software engineer roles posted on LinkedIn in the last 24 hours, built for a
daily feed. No login or API key. Add `"keywords": "remote software engineer"`
if you want remote in the search text, LinkedIn exposes no work-arrangement
filter to logged-out clients (see [On remote / hybrid / on-site](#on-remote--hybrid--on-site)).

```json
{
  "keywords": "software engineer",
  "location": "United States",
  "datePosted": "past_24_hours",
  "maxResults": 100
}
```

#### 📈 Find New Sales Roles Companies Are Hiring for This Week

New Account Executive, SDR, and BDR openings from the past week. A company
staffing up sales has budget: turn it into a prospect list.

```json
{
  "keywordsList": ["account executive", "sales development representative", "business development representative"],
  "location": "United States",
  "datePosted": "past_week",
  "fetchJobDetails": true,
  "maxResults": 150,
  "maxResultsPerSearch": 50
}
```

#### 🎯 Track Director Openings at Companies You're Watching

Director openings at a named list of companies. Put each company name in the
search so LinkedIn surfaces its jobs, then filter to Director level. Use
distinctive company names, since `companyFilter` matches by substring.

```json
{
  "keywordsList": ["Stripe", "Plaid", "Brex", "Marqeta", "Adyen"],
  "location": "United States",
  "companyFilter": ["stripe", "plaid", "brex", "marqeta", "adyen"],
  "experienceLevel": "5",
  "fetchJobDetails": true,
  "maxResults": 500,
  "maxResultsPerSearch": 100
}
```

#### 📊 Scrape Data and AI Jobs Across Top US Tech Cities

Three data and AI roles across four US tech hubs in one batch run,
deduplicated and tagged by the search that found each job.

```json
{
  "keywordsList": ["data engineer", "data scientist", "machine learning engineer"],
  "locationsList": ["New York, NY", "San Francisco, CA", "Austin, TX", "Seattle, WA"],
  "datePosted": "past_month",
  "maxResults": 600,
  "maxResultsPerSearch": 50
}
```

#### 🕵️ Monitor One Company's New Job Postings Every Week

One company's new postings from the past week. Put the company name in the
search, filter to it, and diff run over run to read where it is scaling.

```json
{
  "keywords": "Stripe",
  "location": "United States",
  "datePosted": "past_week",
  "companyFilter": ["stripe"],
  "maxResults": 100
}
```

#### 🏙️ Go Deeper Than 1,000 Results on a Nationwide Search

A country-wide search stops at LinkedIn's 1,000-per-query ceiling. Split it
into the 15 largest US metros and each city gets its own 200, tagged by the
city that found it. Note `maxResults` covers 15 x 200.

```json
{
  "keywords": "registered nurse",
  "location": "United States",
  "splitByCity": true,
  "maxCities": 15,
  "maxResults": 3000,
  "maxResultsPerSearch": 200
}
```

#### 🔗 Run a Search You Already Built on LinkedIn

Set the filters up on linkedin.com, copy the address bar, paste it in. Each
URL keeps its own city and radius, so these two run as different searches.

```json
{
  "searchUrls": [
    "https://www.linkedin.com/jobs/search/?keywords=data%20engineer&location=Boise%2C%20Idaho&distance=100",
    "https://www.linkedin.com/jobs/search/?keywords=data%20engineer&location=Denver%2C%20Colorado&distance=5"
  ],
  "maxResults": 200,
  "maxResultsPerSearch": 100
}
```

#### 🏥 Build a Weekly Nursing Job Feed for Any US Metro

Registered nurse and nurse practitioner roles posted this week across major
US metros, tagged by city so rows route to the right recruiter.

```json
{
  "keywordsList": ["registered nurse", "nurse practitioner"],
  "locationsList": ["New York, NY", "Los Angeles, CA", "Chicago, IL", "Houston, TX"],
  "datePosted": "past_week",
  "maxResults": 400,
  "maxResultsPerSearch": 50
}
```

***

### Key features

- **No API key, login, or cookies**: scrapes public pages only
- **No browser / no Playwright**: pure HTTP, lower cost, faster execution
- **Paste a search URL**: copy it off linkedin.com, no retyping into fields
- **City split**: break a country into per-city searches to get past LinkedIn's 1,000-per-query ceiling
- **Radius search**: `distance` in miles around your location, verified server-side
- **Parallel pagination**: search pages fetched concurrently for up to ~4x faster runs
- **Batch search**: multiple keywords × locations in one run
- **Throttle-aware**: tells a rate-limited page apart from an exhausted one, and says so when a run may be short
- **Full job details**: description, seniority, employment type, job function, industry, applicant count
- **Company info**: employee count and industry, when LinkedIn shows them
- **Title-only filter**: keep only jobs whose title matches your keyword
- **Description HTML**: raw HTML alongside plain text
- **Deduplication**: jobId-based dedup across all batch searches
- **Company filter**: whitelist specific companies by name or slug
- **Smart early-stop**: abandons a search the moment its result pool is exhausted, saving compute and proxy cost
- **MCP-ready**: works as an AI agent tool via Apify's hosted MCP server
- **Resume on migration**: Apify state survives actor migrations mid-run

***

### Output format

Each job returns a JSON object:

```json
{
  "jobId": "3812345678",
  "title": "Senior Data Engineer",
  "company": "Stripe",
  "companyUrl": "https://www.linkedin.com/company/stripe",
  "location": "San Francisco, CA",
  "postedDate": "2 days ago",
  "postedDateTimestamp": "2026-03-18",
  "salary": "USD 180,000 - 240,000 / YEAR",
  "url": "https://www.linkedin.com/jobs/view/3812345678",

  "searchKeywords": "data engineer",
  "searchLocation": "United States",

  "description": "We are looking for a Senior Data Engineer...",
  "descriptionHtml": "<div class=\"show-more-less-html__markup\">...</div>",
  "seniorityLevel": "Mid-Senior level",
  "employmentType": "Full-time",
  "jobFunction": "Engineering and Information Technology",
  "industries": "Financial Services",
  "applicantCount": "Over 200 applicants",

  "companyEmployeeCount": "1,001-5,000 employees",
  "companyIndustry": "Financial Services"
}
```

Fields `description`, `descriptionHtml`, `seniorityLevel`, `employmentType`, `jobFunction`, `industries`, `applicantCount`, and `companyIndustry` require `fetchJobDetails: true`. `companyEmployeeCount` requires `fetchCompanyDetails: true`. `companyEmployeeCount` and `companyIndustry` appear only when LinkedIn shows them on the job or company page. `salary` is returned without `fetchJobDetails`, but is filled in far more often with it, see [On `salary`](#on-salary).

#### On `salary`

`salary` comes from the search card when LinkedIn prints a range there, which
is uncommon, most cards carry no pay data at all. Enabling
`fetchJobDetails: true` fills the gap: the job page is checked for a
JSON-LD `baseSalary` block and then for a visible pay-range element, and
either one is used when the card came back empty. A card value is never
overwritten.

In a 3-row sample, listing-only returned no salary on any row while detail
mode recovered a range on 2 of 3. If pay data matters to your use case, run
with `fetchJobDetails: true`, it costs one extra request per job. Coverage
still depends on the employer publishing a range at all, so expect gaps.

#### On remote / hybrid / on-site

**This actor cannot filter by work arrangement, and does not pretend to.**

LinkedIn's logged-out endpoints accept a work-arrangement filter and then
ignore it. Measured on queries small enough to show an exact total:

| query | no filter | remote | on-site | hybrid |
|---|---|---|---|---|
| prompt engineer @ Boise, ID | 77 | 77 | 77 | 77 |
| veterinary technician @ Missoula, MT | 89 | 89 | 89 | 89 |

On-site and remote cannot both equal the unfiltered total if the filter runs.
The returned job IDs are identical too, in the same order.

Nor is the arrangement readable per job: the guest search card doesn't carry
it, and the guest detail page ships no JSON-LD and lists only Seniority level,
Employment type, Job function and Industries. The `Hybrid` / `Remote` chip you
see on linkedin.com is rendered only for logged-in users.

Earlier versions had a `workType` input and a `workplaceType` output column.
Both were removed in `0.0.34`: the column was derived from the requested
filter, not from the job, so every row of a remote-filtered run was labelled
remote whatever the job actually was. Reporting a value we never observed was
worse than reporting nothing.

If you need this signal, enable `fetchJobDetails` and post-filter `description`
for phrases like "hybrid", "on-site", or "days in office". That is a heuristic
over employer prose, so treat it as a hint rather than a fact.

***

### Input reference

| Field | Type | Default | Description |
|---|---|---|---|
| `searchUrls` | string\[] | - | Pasted linkedin.com search URLs. Each becomes one search, carrying its own keywords, location, geo ID, radius and date range. Overrides every field below |
| `keywords` | string | - | Single keyword search |
| `location` | string | - | Single location search |
| `keywordsList` | string\[] | - | Batch keywords (overrides `keywords`) |
| `locationsList` | string\[] | - | Batch locations (overrides `location`) |
| `geoId` | string | - | LinkedIn geo ID for precise location |
| `distance` | integer | LinkedIn default | Radius in miles around the location (0-100) |
| `splitByCity` | boolean | `false` | Expand a country location into one search per major city, to get past the 1,000-per-query cap (see note) |
| `maxCities` | integer | 10 | Cities per country when splitting, largest markets first (1-30) |
| `companyFilter` | string\[] | - | Whitelist companies by name or slug |
| `titleOnly` | boolean | `false` | Keep only jobs whose **title** contains the keyword (see note below) |
| `datePosted` | select | any | `past_24_hours`, `past_week`, `past_month` |
| `jobType` | select | any | Full-time, Part-time, Contract, etc., verified per job (see note) |
| `experienceLevel` | select | any | Entry, Associate, Mid-Senior, Director, etc., verified per job (see note) |
| `fetchJobDetails` | boolean | `false` | Load full detail page per job (description, criteria, applicants) |
| `fetchCompanyDetails` | boolean | `false` | Also fetch each company's public page for employee count (one request per unique company, cached) |
| `maxResults` | integer | 100 | Cap on the **whole run**, across every search (max 10,000) |
| `maxResultsPerSearch` | integer | 100 | Cap per keyword/location combo; LinkedIn's own 1,000-per-query limit applies here (max 1,000) |
| `proxyConfiguration` | object | RESIDENTIAL | Proxy settings, residential recommended |

> **Using `titleOnly`?** Use **plain keywords** (e.g. `product analyst`, `growth analyst`), not Boolean strings. The title filter matches your text against the job title directly, it does **not** interpret LinkedIn Boolean operators like `AND`/`OR` or quotation marks. A keyword like `"product" AND "analyst"` with `titleOnly: true` will match nothing, because no job title literally contains that operator text. If you want Boolean search, set `titleOnly: false` and let LinkedIn's search engine handle the operators.

> **Batch runs: set `maxResults` to cover the batch.** The two caps are different things. `maxResultsPerSearch` limits each keyword x location combination; `maxResults` limits the run as a whole, and the lower of the two wins. Leaving `maxResults` at its default of 100 while running 12 combinations at 50 each stops the run after two combinations, and the other ten never execute. Set `maxResults` to `combinations x maxResultsPerSearch` (12 x 50 = 600). Runs now warn at startup when the configuration would truncate, naming the value to use.

> **Using `splitByCity`? It multiplies the size of your run.** Ten cities at `maxResultsPerSearch: 100` is a 1,000-result run, and results are billed per item. The same rule as any batch applies: set `maxResults` to `cities x maxResultsPerSearch`, or the run stops part way and the remaining cities never execute. The startup warning names the number to use. Splitting only affects locations that name a supported country, a city or region is searched as typed, and if nothing splits the run says so rather than pretending it did.

> **Note on result counts with `titleOnly`:** because LinkedIn has no native title-scope filter, results are filtered on our side, a niche role can return far fewer than `maxResults`. That's expected: the run stops automatically once every matching title is found, rather than padding with description-only matches.

***

### Batch search example

Search for three roles across two cities in one run:

```json
{
  "keywordsList": ["python developer", "data engineer", "ML engineer"],
  "locationsList": ["New York, NY", "San Francisco, CA"],
  "datePosted": "past_week",
  "fetchJobDetails": true,
  "maxResults": 300,
  "maxResultsPerSearch": 50
}
```

This runs 6 searches (3 × 2), returns up to 300 jobs, deduplicates, and tags each result with `searchKeywords` and `searchLocation` so you know which combo found it.

***

### Lead generation example

Find hiring decision-makers at fintech companies:

```json
{
  "keywordsList": ["engineering manager", "vp engineering", "head of engineering"],
  "locationsList": ["United States"],
  "companyFilter": ["stripe", "plaid", "brex", "ramp", "mercury"],
  "fetchJobDetails": true,
  "maxResults": 300,
  "maxResultsPerSearch": 100
}
```

Output includes job descriptions, seniority, employment type, industry, and applicant counts, ready to pipe into your CRM.

***

### MCP Integration

Use this actor as a real-time tool for AI agents, no custom MCP server needed.

- **Endpoint:** `https://mcp.apify.com?tools=labrat011/linkedin-jobs-scraper`
- **Auth:** `Authorization: Bearer <APIFY_TOKEN>`
- **Transport:** Streamable HTTP
- **Compatible with:** Claude Desktop, Cursor, VS Code, Windsurf, Warp, Gemini CLI

**Claude Desktop / Cursor config:**

```json
{
  "mcpServers": {
    "linkedin-jobs-scraper": {
      "url": "https://mcp.apify.com?tools=labrat011/linkedin-jobs-scraper",
      "headers": {
        "Authorization": "Bearer <APIFY_TOKEN>"
      }
    }
  }
}
```

Once connected, your AI agent can search LinkedIn jobs, pull full job details, and track hiring trends, all from a natural language prompt.

***

### Proxy guidance

LinkedIn aggressively blocks datacenter IPs. **Residential proxies are required.** The actor defaults to Apify's RESIDENTIAL proxy group and will fail immediately on Apify if no proxy is configured, this saves you compute on a run that would never succeed anyway.

Without residential proxies, LinkedIn blocks the first request on almost every run. With residential proxies, the actor reliably handles large batch runs.

***

### Timeout & memory guidance

The actor applies a short, jittered politeness delay and fetches up to 5 requests concurrently to stay under LinkedIn's radar without wasting time. Search pages are paginated in concurrent batches, and with `fetchJobDetails: true` each job's detail request fans out concurrently per page, so runtime scales gently with result count.

| Max results | fetchJobDetails | Est. runtime | Recommended timeout |
|---|---|---|---|
| 25 (free tier) | false (enforced) | ~30 sec | 120s |
| 50 | true | ~1 min | 300s |
| 100 | true | ~2-3 min | 600s |
| 200 | true | ~4-5 min | 900s |
| 100 | false (search only) | ~15 sec | 120s |

> **Free tier note:** Free users (25 results max) always run with `fetchJobDetails: false`, listing data only (title, company, location, salary, URL, posted date). Subscribe for full job details: description, seniority, employment type, job function, industry, and applicant count.

> **Proxy data cap:** To protect against runaway proxy cost on blocked or pathological runs, the actor aborts early if a single run downloads far more data than its result count warrants (floor 25 MB + ~0.5 MB per requested result). It keeps and returns everything scraped up to that point. Lower `maxResults` or disable enrichment for very large runs.

**Memory:** 512MB is sufficient for all run sizes. 1-2GB is not needed unless you are running very large batch jobs (500+ results).

To set timeout in Apify: go to your actor run settings → **Timeout** → set in seconds.

***

### Limitations

- LinkedIn caps search pagination at 1,000 results per query. `splitByCity` is the way past it: per-city searches return largely different jobs, so N cities is worth roughly N times the inventory rather than N slices of the same pool
- Salary data is only present when LinkedIn displays it on the listing or job page
- Company employee count and industry appear only when LinkedIn shows them
- `fetchCompanyDetails` opens at most 0.4 x `maxResults` distinct company pages per run (300 at most). Repeat companies are served from cache. Past that limit, `companyEmployeeCount` stays empty and the run log says so
- `fetchJobDetails` adds one request per job, higher cost and runtime; enable only when you need the detail fields
- `jobType` and `experienceLevel` are enforced on our side against each job's published criteria, so they require `fetchJobDetails` and can return fewer rows than `maxResults`
- **No work-arrangement filter.** LinkedIn ignores it for logged-out clients and publishes no remote/hybrid/on-site value per job, see [On remote / hybrid / on-site](#on-remote--hybrid--on-site)
- LinkedIn publishes no seniority for roughly half of postings (`Not Applicable`); with `experienceLevel` set, those rows are dropped and counted in the run summary
- Skills, company logo, and recruiter/hiring-manager data are login-gated by LinkedIn and not available from public pages, so they are not included

# Changelog

This Actor's version history is a separate document: https://apify.com/labrat011/linkedin-jobs-scraper/changelog.md

# Actor input Schema

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

Build the search on linkedin.com, copy the address bar, paste it here. Each URL becomes one search and keeps its own keywords, location, geo ID, radius and date range, so you can mix cities and radii in one run. Overrides every field below when set. Work arrangement, experience level and job type are read out of the URL and reported as ignored: LinkedIn accepts those from a logged-in browser and discards them on the public endpoint this actor uses.

## `keywords` (type: `string`):

Job search keywords (e.g. 'python developer', 'data engineer', 'sales manager'). For a single search. Use Keywords List below for batch mode.

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

Location to search (e.g. 'United States', 'London, UK', 'Remote'). For a single search. Use Locations List below for batch mode.

## `keywordsList` (type: `array`):

Run multiple keyword searches in one go. Each keyword is combined with every location in Locations List: e.g. \['python developer', 'data engineer'] x \['New York', 'Remote'] = 4 searches. Overrides the single Keywords field when provided.

## `locationsList` (type: `array`):

List of locations to search across in batch mode. Combined with every entry in Keywords List. Overrides the single Location field when provided.

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

LinkedIn geographic ID for precise location targeting (e.g. 103644278 for United States). Overrides text location when provided.

## `distance` (type: `integer`):

How far around the location to search, in miles. Verified honoured by LinkedIn: around Boise, 5 returns only Boise postings while 100 also returns Gooding, Hailey, McCall and Meridian. Leave empty for LinkedIn's default.

## `splitByCity` (type: `boolean`):

LinkedIn returns at most 1,000 results for any single search, so a country-wide query cannot go deeper however many jobs exist. Turn this on and a country location becomes one search per major city, which is the only way past that ceiling. City searches return genuinely different jobs, not the same ones relabelled: measured on 'registered nurse', a 500-job United States walk shared just 3% of what New York, Houston and Phoenix returned, and those cities shared nothing with each other. Every city is a separate search and results are billed per item, so raise Max Results to cover cities x Max Results Per Search or the run stops part way. Locations that are not a supported country are left alone.

## `maxCities` (type: `integer`):

Cities per country, largest markets first. Supported: United States (30), United Kingdom (12), Canada (10), Australia (8), Germany (8), India (8). This multiplies the size and cost of the run.

## `companyFilter` (type: `array`):

Only return jobs from specific companies. Enter company names or LinkedIn URL slugs (e.g. 'google', 'stripe', 'openai'). Leave empty to return jobs from all companies.

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

Filter jobs by how recently they were posted.

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

Filter by employment type. Verified against each job's published "Employment type" on its detail page: LinkedIn's search endpoint accepts this filter and then ignores it, so the actor enforces it itself. Turns on Fetch Full Job Details automatically (one extra request per job), and may return fewer rows than Max Results: that number is a ceiling, not a target.

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

Filter by seniority level. Verified against each job's published "Seniority level" on its detail page: LinkedIn's search endpoint accepts this filter and then ignores it, so the actor enforces it itself. LinkedIn omits seniority on roughly half of all postings; those rows are dropped rather than guessed at, and the run summary reports how many. Turns on Fetch Full Job Details automatically (one extra request per job).

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

Loads each job's detail page to extract: full description (text + HTML), seniority level, employment type, job function, industry, applicant count, and salary when LinkedIn shows it. Adds one request per job: higher cost and runtime, so enable only when you need these fields.

## `fetchCompanyDetails` (type: `boolean`):

Also opens each company's LinkedIn page to extract the registered employee count (companyEmployeeCount). Requires Fetch Full Job Details. Results are cached per company within the run to minimize extra requests. At most 0.4 x Max Results distinct company pages (300 at most) are opened per run; past that, companyEmployeeCount stays empty. Note: company website, description, and leadership/founders are not available to guests and are not returned.

## `titleOnly` (type: `boolean`):

When enabled, only returns jobs where the search keyword appears in the job title. By default, LinkedIn searches the full posting (title + description + other fields), which can return jobs where your keyword only appears in the description. Enable this to get results strictly matching your keyword in the title.

## `maxResults` (type: `integer`):

Cap on the TOTAL number of jobs the whole run returns, across every search. In batch mode set this to (number of keyword x location combinations) x Max Results Per Search, or the run stops once the total is reached and the remaining combinations never execute. Example: 30 combinations x 50 per search = 1500. Free users are limited to 25 per run.

## `maxResultsPerSearch` (type: `integer`):

Cap on results from EACH keyword x location combination. This is where LinkedIn's own limit applies: it returns at most 1,000 results per query. Raising this does not raise the run total: Max Results caps that separately, and the lower of the two wins. Example: 3 keywords x 2 locations x 50 per search = up to 300 results, so Max Results must be at least 300.

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

Proxy settings. Residential proxies give the best success rate. Without proxies, LinkedIn may block requests.

## Actor input object example

```json
{
  "searchUrls": [
    "https://www.linkedin.com/jobs/search/?keywords=data%20engineer&location=Boise%2C%20Idaho&distance=25"
  ],
  "keywords": "python developer",
  "location": "United States",
  "keywordsList": [
    "python developer",
    "data engineer",
    "ML engineer"
  ],
  "locationsList": [
    "United States",
    "United Kingdom",
    "Remote"
  ],
  "distance": 25,
  "splitByCity": false,
  "maxCities": 10,
  "companyFilter": [
    "google",
    "meta",
    "stripe"
  ],
  "datePosted": "",
  "jobType": "",
  "experienceLevel": "",
  "fetchJobDetails": false,
  "fetchCompanyDetails": false,
  "titleOnly": false,
  "maxResults": 10,
  "maxResultsPerSearch": 25,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

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

LinkedIn job listings with title, company, location, salary, description, seniority, employment type, job function, industry, and applicant count.

# 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": "python developer",
    "location": "United States",
    "fetchJobDetails": false,
    "fetchCompanyDetails": false,
    "titleOnly": false,
    "maxResults": 10,
    "maxResultsPerSearch": 25,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("labrat011/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": "python developer",
    "location": "United States",
    "fetchJobDetails": False,
    "fetchCompanyDetails": False,
    "titleOnly": False,
    "maxResults": 10,
    "maxResultsPerSearch": 25,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("labrat011/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": "python developer",
  "location": "United States",
  "fetchJobDetails": false,
  "fetchCompanyDetails": false,
  "titleOnly": false,
  "maxResults": 10,
  "maxResultsPerSearch": 25,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call labrat011/linkedin-jobs-scraper --silent --output-dataset

```

## MCP server setup

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