# LinkedIn Jobs Scraper with Salary & Company Enrichment (`nimbryxo-studio/linkedin-jobs-scraper-with-salary-and-company`) Actor

Search public LinkedIn job listings by keyword, location, salary band, job type and more, with full descriptions, parsed salary ranges, inferred seniority, visa-sponsorship mentions and enriched hiring-company details. No cookies or login required.

- **URL**: https://apify.com/nimbryxo-studio/linkedin-jobs-scraper-with-salary-and-company.md
- **Developed by:** [Nimbryxo-studio](https://apify.com/nimbryxo-studio) (community)
- **Categories:** Jobs, Automation, Lead generation
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.49 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## LinkedIn Hiring Radar: Jobs, Pay & Employer Profiles

Turn public LinkedIn job pages into a research-ready dataset: one row per posting with the full description, a parsed pay range and where that pay came from, applicant volume, Easy Apply status, hiring badges, recruiter details and the employer's own LinkedIn profile. Search by keywords, places, companies or exact job links. No LinkedIn account, cookies or proxy setup is needed.

### What this actor does

- **Pay that can be compared.** The pay box and pay ranges written in the description are read, normalised to yearly figures and tagged with `salarySource` so you can tell employer-provided pay from description text.
- **Employer intelligence, not just a name.** For every distinct employer, the actor reads the public LinkedIn company page once and attaches industry, headcount band, headquarters, website, founding year, follower count, specialties, offices, tagline, description and numeric company ID.
- **Broad discovery.** Combine several keyword searches with several places, search entire companies, paste LinkedIn search links, or feed a list of job links and IDs. Results are merged and de-duplicated by job ID.
- **Filters that narrow the dataset.** Date posted from the past hour to three months, search radius, Easy Apply, fewer than ten applicants, companies to include or leave out, experience level, job type, industry, staffing agencies, salary and recruiter presence.
- **Past the 1,000-result wall.** When one search reaches LinkedIn's result ceiling, the actor can continue with narrower posting-date windows and merge the unique jobs.
- **Clean rows.** Empty fields are omitted at every depth; a field the source does not show for a posting is never filled with blanks, zeroes or guesses.

### Output per job

| Field | What it holds |
|---|---|
| `id`, `title`, `jobUrl` | LinkedIn job ID, title and canonical job link |
| `companyName`, `companyUrl`, `companyId`, `companyNumericId`, `companyLogo` | Employer identity and logo |
| `location` | Location exactly as LinkedIn displays it |
| `postedDate`, `postedDateText`, `postedAt`, `expiresAt` | Posting date, relative label, exact UTC time when available, and LinkedIn's expiry time when its structured data includes one |
| `salary`, `salaryRange`, `salarySource` | Raw pay text, parsed `{min, max, currency, interval, minYearly, maxYearly}` and `employer` or `description` |
| `applicantCount`, `applicantCountNumeric`, `applicantCountKind`, `applicantInsights` | Applicant text, parsed number, whether it is exact/more/fewer, and early-applicant notes |
| `listingBadge`, `activelyHiring` | Search-card badge such as "Actively Hiring" or "Be an early applicant" |
| `easyApply`, `applyUrl` | Whether the job can be applied to on LinkedIn, plus the LinkedIn application page |
| `descriptionText`, `descriptionHtml` | Full posting description in both formats |
| `responsibilities`, `qualifications`, `preferredQualifications` | Bulleted sections when the description uses a recognisable heading and list |
| `seniorityLevel`, `inferredSeniorityLevel`, `employmentType`, `industries`, `industriesList` | Criteria as LinkedIn publishes them, plus a title-based seniority fallback |
| `experienceYearsMin`, `experienceYearsMax` | Experience range stated in the description |
| `visaSponsorshipMentioned`, `visaSponsorshipPhrase` | Positive or negative sponsorship statement, with the matched phrase |
| `remoteAllowed` | Set only on explicit fully-remote evidence (title, wording or a `TELECOMMUTE` structured-data flag); never inferred from hybrid/on-site text |
| `recruiter` | `{name, title, profileUrl, photoUrl}` when LinkedIn shows the poster card |
| `contactEmails` | Email addresses written in the description |
| `atsPlatform` | Applicant-tracking platform named or linked in the posting, when detectable |
| `companyIndustry`, `companySize`, `companyHeadquarters`, `companyType`, `companyWebsite`, `companyFounded`, `companySpecialties`, `companyLocations`, `companyFollowersCount`, `companySlogan`, `companyDescription` | Employer profile from the company's public page |
| `companyAffiliatedPages`, `companySimilarPages` | Related employer pages as listed on the company profile |
| `companyRecentPosts` | Recent public company posts with reaction and comment counts, when shown |
| `similarJobs`, `peopleAlsoViewed`, `similarSearches`, `industryIds`, `titleId`, `titleCategoryLabel`, `titleCategoryUrl` | Related postings and LinkedIn taxonomy hints (full job-page depth only) |
| `searchQuery`, `searchLocation`, `searchUrl` | The search that surfaced this job |
| `scrapedAt` | Collection timestamp in UTC |

The dataset includes three ready-made views: **Jobs**, **Salaries** and **Employers**.

### Input

At least one of `searchQueries`, `searchUrls`, `jobUrls` or `companies` must be provided. The form is grouped as "What to search", "Where", "Filters", "Data to include" and "Limits".

| Field | Type | Default | Meaning |
|---|---|---|---|
| `searchQueries` | string list | `["Python Developer"]` in the prefill | Job titles, skills or phrases; each is searched in every place |
| `searchUrls` | URL list | - | LinkedIn job-search links; their keywords, place and filters are kept |
| `jobUrls` | string list | - | Job links or numeric job IDs to read directly |
| `locations` | string list | `["United States"]` in the prefill | Places in English; unknown places are reported and skipped, not silently treated as the US |
| `geoIds` | string list | - | Numeric LinkedIn place IDs for an exact market |
| `distance` | select | LinkedIn default | Radius around a city: 0, 5, 10, 25, 50 or 100 miles |
| `timePosted` | select | Any time | Past hour, 24 hours, 3 days, week, 2 weeks, month or 3 months |
| `companies` | string list | - | Employer names, company/university links or numeric company IDs |
| `excludeCompanies` | string list | - | Employers to skip by name or LinkedIn page |
| `easyApply` | boolean | `false` | Keep only jobs applicable on LinkedIn |
| `under10Applicants` | boolean | `false` | Keep only jobs with fewer than ten applicants |
| `experienceLevel` | select | Any | Internship, Entry level, Associate, Mid-Senior level, Director, Executive |
| `jobType` | select | Any | Full-time, Part-time, Contract, Temporary, Internship, Volunteer, Other |
| `industry` | multi-select | `[]` | LinkedIn industry of the employer (all 434 categories) |
| `companySizes` | multi-select | `[]` | Employer headcount bands from 1-10 to 10,001+ |
| `excludeStaffingAgencies` | boolean | `false` | Remove staffing and recruiting firms |
| `requireSalary` | boolean | `false` | Keep only postings that state pay |
| `minYearlySalary` | integer | - | Drop postings whose yearly pay ceiling is below this amount |
| `salaryCurrency` | select | Any | Keep only this pay currency; also the currency used by `minYearlySalary` |
| `requireRecruiter` | boolean | `false` | Keep only postings where LinkedIn names the poster |
| `includeTitleKeywords` | string list | - | Title must contain one of these words |
| `excludeTitleKeywords` | string list | - | Title must not contain any of these words |
| `excludeJobIds` | string list | - | Job IDs seen in an earlier run |
| `scrapeJobDetails` | boolean | `true` | Read each job page for description, pay, criteria, applicants and recruiter |
| `includeCompanyDetails` | boolean | `true` | Read each employer's public profile once |
| `enrichmentDepth` | select | `standard` | `full` adds related jobs, related searches, taxonomy IDs and extra posting-time retries |
| `language` | select | `en` | Language of LinkedIn's labels: English, Spanish, French, German or Portuguese |
| `splitLargeSearches` | boolean | `true` | Continue a capped search with narrower posting-date windows |
| `maxItems` | integer | `100` | Stop after this many saved jobs (1-10,000) |

#### Example: pay research across markets

```json
{
    "searchQueries": ["Python Developer", "Data Engineer"],
    "locations": ["United States", "Germany", "United Kingdom"],
    "timePosted": "r604800",
    "requireSalary": true,
    "minYearlySalary": 80000,
    "salaryCurrency": "USD",
    "maxItems": 300
}
```

#### Example: every current role at two employers

```json
{
    "companies": ["Microsoft", "https://www.linkedin.com/company/affirm"],
    "includeCompanyDetails": true,
    "maxItems": 200
}
```

#### Example: audit a list of postings

```json
{
    "jobUrls": [
        "https://www.linkedin.com/jobs/view/4479196690",
        "4465604021"
    ]
}
```

### Choosing the right depth

`scrapeJobDetails` off returns search-card data only: title, employer, place, posting date, card salary when shown and the job link. It is the cheapest and fastest mode.

`scrapeJobDetails` on reads the public job page for every posting and adds the full description, pay parsing, criteria, applicants, recruiter and apply signals. `includeCompanyDetails` then adds the employer profile once per company.

`enrichmentDepth: full` uses the richer job-page layout and also adds related jobs, related searches and LinkedIn taxonomy IDs. It costs more time per posting and is unnecessary for most salary or hiring research.

### How many jobs can I collect?

LinkedIn returns at most about 1,000 jobs for one keyword/place/filter combination. The actor exposes three ways past that wall:

- `locations` or `geoIds` spread one keyword across several markets.
- `searchQueries` multiply the number of distinct searches.
- `splitLargeSearches` continues a capped search with past-month, past-week, past-3-days and past-24-hours windows, removing overlaps by job ID.

The actor stops on LinkedIn's own end-of-results signals, not on a timer: an empty page, repeated pages with no new job IDs, or the 1,000-result ceiling with no narrower window left.

### Use cases

- **Recruiting and sourcing** - compare public postings, pay ranges and employer profiles for a role
- **Compensation research** - normalise salary data across markets and seniority levels
- **Job-market monitoring** - track hiring volume and employer demand over time
- **Sales and account research** - identify companies actively hiring in a target segment
- **Competitive intelligence** - compare how employers present roles, pay and benefits

### FAQ

**Do I need a LinkedIn account, cookies or a proxy?**
No. Every request reads a public page as a signed-out visitor. The actor chooses its own connection automatically and moves to a fresh session when LinkedIn throttles it; no user proxy configuration is required.

**Why is salary missing on some rows?**
Many employers do not publish pay. The actor reads LinkedIn's pay box and explicit pay ranges in the description and never estimates. `salarySource` tells you which source produced the range. Coverage varies by market and employer.

**Can I filter for remote, hybrid or on-site jobs?**
LinkedIn does not show the workplace label to signed-out visitors, and the public search ignores its workplace parameter. `remoteAllowed` is therefore only set when the posting itself is explicit about being fully remote (title/wording, or a `TELECOMMUTE` structured-data flag). Hybrid and on-site are never guessed. Adding "remote" to your keywords is the practical workaround. Older saved inputs that still contain the old workplace filter keep running; that setting is ignored and the status message says so.

**Can I get the employer's own apply link?**
LinkedIn exposes the external apply destination only to signed-in members. `applyUrl` is the LinkedIn application page and `easyApply` tells you whether the application can be completed on LinkedIn. `atsPlatform` is filled only when the posting text itself names or links an applicant-tracking system.

**Why does `postedAt` sometimes differ from `postedDate`?**
`postedDate` is the date shown on the search card. `postedAt` is the exact UTC timestamp from the job page's structured data or posting-time metadata, and it is omitted when LinkedIn serves a page variant without it. `postedDate` is always present.

**Do all links open?**
Job links and company logos were checked from a clean client: job pages return 200 and logos return 200 with an image content type. LinkedIn company pages and some employer websites block automated `HEAD`/script requests with 403 even though they open normally in a browser; those URLs are the real public pages, not broken builders. An employer's own site can also be down or geo-restricted independently of this actor.

**Why do I see more jobs than `maxItems`?**
You should not: the actor reserves each output slot atomically and never pushes more than `maxItems`. If a search has fewer matching jobs, the run ends with fewer and explains why in the status message.

**How do I get only new postings on a schedule?**
Use "Past 24 hours", or save the `id` values from one run and send them through `excludeJobIds` on the next.

**What is intentionally not available?**
Remote/hybrid/on-site labels for non-remote jobs, the external apply URL, repost history, view counts, job state and the job-function criterion are gated to signed-in LinkedIn members or have been removed from public pages. Skills and benefits are not exposed as structured lists for signed-out visitors; their raw text remains in `descriptionText`. The actor reports these limits rather than inventing values.

**Is this affiliated with LinkedIn?**
No. It is an independent tool that reads LinkedIn's public job pages.

### Data source

Public LinkedIn job-search result cards, public job pages (`linkedin.com/jobs/view/...`), public company profiles (`linkedin.com/company/...`) and LinkedIn's public location/company suggestion endpoints. All requests are cookieless.

# Actor input Schema

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

Job titles, skills or phrases to search for, one per line (e.g. 'data analyst', 'registered nurse'). Each keyword is searched in every location you list, and results are merged without duplicates.

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

Paste job search links copied from LinkedIn (linkedin.com/jobs/search/?...). The keywords, location and filters inside each link are kept.

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

Scrape specific job postings: paste job links (linkedin.com/jobs/view/...) or numeric job IDs. Postings that have been taken down are skipped.

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

Cities, regions or countries in English (e.g. 'Germany', 'Austin, Texas', 'London Area, United Kingdom'). Every keyword is searched in each location. A location LinkedIn doesn't recognise is reported and skipped rather than silently searched as the United States. Leave empty to search worldwide.

## `geoIds` (type: `array`):

Optional numeric LinkedIn location IDs (the geoId value in a LinkedIn search link, e.g. 101282230 for Germany). Use these for exact places in any language.

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

Search radius around a city-level location, in miles.

## `timePosted` (type: `string`):

Only jobs posted (or reposted) within this period.

## `companies` (type: `array`):

Only jobs from these employers. Enter company names (e.g. 'General Motors'), LinkedIn company or university page links, or numeric company IDs. Leave the keywords empty to get all of a company's open jobs.

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

Skip jobs from these employers (exact company name or LinkedIn company page link).

## `easyApply` (type: `boolean`):

Only jobs you can apply to directly on LinkedIn.

## `under10Applicants` (type: `boolean`):

Only jobs that have had fewer than 10 applicants so far.

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

Only jobs listed at this seniority level on LinkedIn. Jobs that LinkedIn lists as 'Not Applicable' are left out when a level is chosen.

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

Only jobs with this employment type.

## `industry` (type: `array`):

Only jobs from employers in these LinkedIn industries.

## `companySizes` (type: `array`):

Only jobs from employers of these sizes, as listed on their LinkedIn page. Each employer's page is looked up once. Jobs whose employer size isn't listed are kept.

## `excludeStaffingAgencies` (type: `boolean`):

Skip jobs posted by employers in LinkedIn's 'Staffing and Recruiting' industry.

## `requireSalary` (type: `boolean`):

Only jobs that state their pay, either in LinkedIn's pay range box or in the job description.

## `minYearlySalary` (type: `integer`):

Skip jobs whose stated pay range tops out below this yearly amount. Hourly, weekly and monthly pay is converted to a yearly figure. The amount is compared in the job's own currency, so set 'Salary currency' to compare like with like. Jobs without a stated salary are kept unless 'Only jobs with a salary' is on.

## `salaryCurrency` (type: `string`):

Only jobs whose stated pay is in this currency. Also sets the currency of 'Minimum yearly salary'. Jobs without a stated salary are kept unless 'Only jobs with a salary' is on.

## `requireRecruiter` (type: `boolean`):

Only jobs where LinkedIn shows the person who posted the job.

## `includeTitleKeywords` (type: `array`):

Keep only jobs whose title contains at least one of these words (not case-sensitive). Useful because LinkedIn's keyword matching is broad.

## `excludeTitleKeywords` (type: `array`):

Skip jobs whose title contains any of these words, e.g. 'Senior' or 'Intern'.

## `excludeJobIds` (type: `array`):

Job IDs (the 'id' field of an earlier run) to leave out, so scheduled runs return only new postings.

## `scrapeJobDetails` (type: `boolean`):

Open each job to add the full description, salary, seniority, job type, industry, applicant count, recruiter and Easy Apply status. Turn off for a fast list of titles, companies, locations and dates only. Filters that need these details switch it on automatically.

## `includeCompanyDetails` (type: `boolean`):

Add each employer's LinkedIn profile: industry, size, headquarters, website, founding year, office locations, followers, tagline and description. Fetched once per company.

## `enrichmentDepth` (type: `string`):

Standard returns each job's core details. Extended also adds similar jobs, 'people also viewed' jobs, related searches and LinkedIn's numeric industry and title IDs, and asks LinkedIn again when the exact posting time is missing, so it takes longer per job.

## `language` (type: `string`):

Language LinkedIn uses for labels such as job type and seniority. Job descriptions stay in the language they were written in. Locations must still be typed in English.

## `splitLargeSearches` (type: `boolean`):

LinkedIn returns at most 1,000 jobs for one search. When a search reaches that limit, continue with the same search limited to the past month, week, 3 days and 24 hours, which surfaces many more jobs (duplicates removed).

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

Stop after saving this many jobs. A single keyword + location search returns at most 1,000 jobs; add keywords or locations to collect more.

## Actor input object example

```json
{
  "searchQueries": [
    "Python Developer"
  ],
  "locations": [
    "United States"
  ],
  "distance": "",
  "timePosted": "",
  "easyApply": false,
  "under10Applicants": false,
  "experienceLevel": "",
  "jobType": "",
  "industry": [],
  "companySizes": [],
  "excludeStaffingAgencies": false,
  "requireSalary": false,
  "salaryCurrency": "",
  "requireRecruiter": false,
  "scrapeJobDetails": false,
  "includeCompanyDetails": false,
  "enrichmentDepth": "standard",
  "language": "en",
  "splitLargeSearches": true,
  "maxItems": 3
}
```

# Actor output Schema

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

Dataset containing all scraped LinkedIn job listings.

# 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": [
        "Python Developer"
    ],
    "locations": [
        "United States"
    ],
    "easyApply": false,
    "under10Applicants": false,
    "excludeStaffingAgencies": false,
    "requireSalary": false,
    "requireRecruiter": false,
    "scrapeJobDetails": false,
    "includeCompanyDetails": false,
    "splitLargeSearches": true,
    "maxItems": 3
};

// Run the Actor and wait for it to finish
const run = await client.actor("nimbryxo-studio/linkedin-jobs-scraper-with-salary-and-company").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": ["Python Developer"],
    "locations": ["United States"],
    "easyApply": False,
    "under10Applicants": False,
    "excludeStaffingAgencies": False,
    "requireSalary": False,
    "requireRecruiter": False,
    "scrapeJobDetails": False,
    "includeCompanyDetails": False,
    "splitLargeSearches": True,
    "maxItems": 3,
}

# Run the Actor and wait for it to finish
run = client.actor("nimbryxo-studio/linkedin-jobs-scraper-with-salary-and-company").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": [
    "Python Developer"
  ],
  "locations": [
    "United States"
  ],
  "easyApply": false,
  "under10Applicants": false,
  "excludeStaffingAgencies": false,
  "requireSalary": false,
  "requireRecruiter": false,
  "scrapeJobDetails": false,
  "includeCompanyDetails": false,
  "splitLargeSearches": true,
  "maxItems": 3
}' |
apify call nimbryxo-studio/linkedin-jobs-scraper-with-salary-and-company --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,nimbryxo-studio/linkedin-jobs-scraper-with-salary-and-company"
        }
    }
}
```

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/tba8gl0gackXF4x6b/builds/IG1scfvEpGbbrWYek/openapi.json
