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

Scrape LinkedIn jobs from keywords and locations or search URLs, no login needed. Get full descriptions, seniority, employment type, industries, declared salary, real posting dates, applicant counts, recruiter cards and company profiles. Schedule it to return only new jobs. $0.60 per 1,000 jobs.

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

## Pricing

from $0.34 / 1,000 job-details

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

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

## What's an Apify Actor?

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

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

## How to integrate an Actor?

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

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

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

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

# README

## LinkedIn Jobs Scraper

### What is LinkedIn Jobs Scraper?

LinkedIn Jobs Scraper gets you LinkedIn job postings with the employer's company profile attached, for one price: $0.60 per 1,000 jobs on the Free plan, company data included. You type keywords and a location, or paste a LinkedIn job search URL. You never sign in to LinkedIn, and there's no API key to arrange.

Four reasons to choose it:

- **The company profile costs nothing extra.** Switch on "Scrape company details" and each job gains industry, size, headquarters, website and follower count. A job with company data is billed the same $0.0006 as a job without.
- **You get the real posting date.** LinkedIn's page says "1 day ago". You get the date, and you can narrow a search to the last N hours, which is what a monitor that runs every six hours needs.
- **Cheap filters run first.** Title and company filters are applied to the search results before any posting is opened. A job they drop costs $0.00005, a twelfth of the full price.
- **It's quick.** 1,100 jobs with details took 2 minutes 14 seconds across six searches on 5 October 2026, about 490 a minute.

**BonCross Verified** means we tested this actor against the live site before release and kept the record:

- 41 of 41 live checks on its inputs and filters passed on 6 October 2026
- 10 searches run on 7 October 2026 across Manchester, Berlin, Tokyo, Austin and São Paulo: 100 jobs, none empty
- 6 of 6 sampled adverts matched LinkedIn's own pages, one read by hand
- Charges checked against the 100 rows

What else it does:

- Extract job title, company, location, the full description (text and HTML) and seniority level
- Read the applicant count as a number, and filter on it
- Pick up the recruiter card where LinkedIn shows one
- Collect declared pay as minimum, maximum, period and currency where the employer states it
- Search by company name. You don't need LinkedIn's numeric company ids
- Search several locations in one run, which is how you get past LinkedIn's ceiling of about 1,000 results per search
- Return only the postings added since your last run
- Drop staffing agencies and see direct employers only

People point it at a job board or aggregator that needs fresh postings, at a watchlist of named companies, or at a market they want to check for new roles every few hours. It also feeds pay benchmarking (where LinkedIn shows pay) and CRM enrichment with company size, industry and headquarters.

### What data can you extract from LinkedIn jobs?

| | |
|---|---|
| 💼 Job title | 🏢 Company name and LinkedIn company URL |
| 📍 Location | 📅 Posting date (real) and the posted-ago text |
| 📝 Full description, text and HTML | Seniority level |
| Employment type | Industries |
| Job poster profile | Benefits tags, where shown |
| 👥 Applicant count | Recruiter name, title and profile, where shown |
| Company logo | 🌐 Company website |
| Company size and employee count | Company type and headquarters, with street, postcode and country code |
| Company specialties | 🔗 Job URL and job id |
| Company follower count | Company tagline and URL slug |
| Company office addresses | Easy Apply flag (`isDirectApply`) |
| 💷 Declared pay, where the employer states it | LinkedIn company id |
| Search keyword and search URL | Scrape timestamp |

### How to scrape LinkedIn jobs

There's no code to write. You fill in a form and download a spreadsheet.

1. [Create a free Apify account](https://console.apify.com/sign-up) and open [LinkedIn Jobs Scraper](https://apify.com/boncross/linkedin-jobs-scraper).
2. Add your keywords and a location. If you've already built the search on LinkedIn, paste its URL into Start URLs.
3. Pick a date window. The default is the past 24 hours.
4. Add title or company filters if you want to keep the bill down, then press Start.
5. Download the jobs as Excel, CSV, JSON, XML or HTML when the run finishes.

### ⬇️ Input

Keywords and a location are enough. So is a LinkedIn search URL on its own.

Each posting is opened for its full advert unless you switch "Fetch job details" off. Company details are off until you ask for them, and add one lookup per company the run hasn't seen before.

You have two limits to work with. "Max results per search" (10 to 1,000, default 100) applies to each keyword, location or URL separately, so ten searches at 50 each can return 500. "Maximum results for the whole run" sits over all of them. Leave it at 0 for no cap. Past it, nothing is returned or charged.

#### Recent jobs for two keywords in London

```json
{
  "keywords": ["paralegal", "compliance analyst"],
  "location": "London, United Kingdom",
  "datePosted": "past24h",
  "maxResultsPerSearch": 200
}
```

#### A six-hourly monitor across two cities, only new postings

```json
{
  "keywords": ["administrator"],
  "locations": ["London, United Kingdom", "Manchester, England, United Kingdom"],
  "postedWithinHours": 6,
  "dedupStoreName": "linkedin-admin-seen",
  "titleExclude": ["recruitment consultant", "sales"],
  "scrapeCompany": true
}
```

#### Entry-level roles with few applicants, agencies excluded

```json
{
  "keywords": ["junior data analyst"],
  "location": "United Kingdom",
  "datePosted": "pastWeek",
  "experienceLevels": ["entry"],
  "seniorityLevelsFilter": ["Entry level", "Associate"],
  "maxApplicants": 25,
  "scrapeCompany": true,
  "excludeStaffingAgencies": true
}
```

#### Jobs at named companies

Give the names and the run finds each company's LinkedIn id for you, then reports what each name matched. Company names aren't unique, so the log lists the runners-up as well.

```json
{
  "keywords": ["engineer"],
  "location": "United Kingdom",
  "companyNames": ["Monzo", "Wise"]
}
```

#### A pasted LinkedIn search URL

```json
{
  "startUrls": [
    { "url": "https://www.linkedin.com/jobs/search/?keywords=product%20manager&location=Berlin&f_TPR=r86400&f_EA=true" }
  ],
  "maxResultsPerSearch": 100
}
```

| Field | Notes |
|---|---|
| `keywords`, `location`, `locations`, `geoId`, `distance` | Search terms and where to search. Several locations run as several searches. |
| `startUrls` | LinkedIn job search URLs, including older ones with legacy filter parameters. |
| `datePosted`, `postedWithinHours` | Date window, down to the hour. |
| `sortBy` | Most recent first. LinkedIn no longer sorts the public job search, so this is applied to the collected results: it orders what the run fetched, it cannot change which jobs LinkedIn selects. |
| `experienceLevels`, `jobTypes`, `workplaceTypes` | Codes such as `entry`, `part-time`, `remote`. Folded into the search terms. This steers the search strongly but is not exact on its own, and it narrows the pool. Pair with the matching post-fetch filter below for exact results. |
| `under10Applicants`, `easyApply`, `companyIds`, `companyNames` | Filters LinkedIn still applies to the search itself. `companyNames` takes plain names ("Monzo") and resolves each to the id the filter needs, so you never have to look one up. |
| `titleInclude`, `titleExclude`, `companyExclude` | Free filters applied before any detail is fetched. |
| `seniorityLevelsFilter`, `employmentTypesFilter`, `maxApplicants`, `minAnnualSalary`, `descriptionInclude`, `descriptionExclude`, `excludeStaffingAgencies` | Filters applied after the detail is fetched, using LinkedIn's own labels. `employmentTypesFilter` and `seniorityLevelsFilter` are the exact versions of job type and experience level: set `jobTypes: ["part-time"]` with `employmentTypesFilter: ["Part-time"]` and every returned row is part-time. |
| `fetchDetails`, `fetchFullJobPage`, `scrapeCompany`, `companyCacheDays` | Whether to open each posting (full advert, criteria and declared pay), whether to read the full job page instead of the short advert (same columns, a real seniority level on far more jobs, about 45% slower), whether to enrich the company, and how long to cache company records. |
| `maxResultsPerSearch` (default 100, 10 to 1,000), `maxItems`, `maxNewJobs`, `dedupStoreName` | The per-target cap, the run-wide cap, a cap on jobs returned, and the only-new store. `maxItems` and `maxNewJobs` default to 0, which means no limit. |
| `maxConcurrency`, `delayMs` | Parallel requests (1 to 10, default 8) and an optional extra pause before each request (default 0; the run already keeps to a rate measured safe for LinkedIn). |

#### For developers

- `maxResultsPerSearch` is the per-search limit and `maxItems` the run-wide one. `maxItems` and `maxNewJobs` default to 0, meaning no limit.
- `titleInclude`, `titleExclude` and `companyExclude` run on the search results, before any posting is opened. A job they drop is billed `job-scanned` only.
- The seniority, employment-type, applicant, salary, description and staffing-agency filters need the posting first. A posting that was opened is billed as a detail even when one of them then drops the job.
- `dedupStoreName` names the store that remembers which jobs you've had. Later runs return only unseen postings, and the ones already seen cost the scan rate. `maxNewJobs` caps how many a run returns.
- `companyCacheDays` (default 30) is how long a company record is reused before it's fetched again.
- `fetchFullJobPage` reads the full job page in place of the short advert. The columns are the same, the seniority level is filled on far more jobs, and the run is about 45% slower.
- `maxConcurrency` (default 8, from 1 to 10) is the number of requests in flight. `delayMs` (default 0) adds a pause before each one. The run already keeps to a rate measured safe for LinkedIn, and we'd leave both alone.
- Jobs are written as they're read. Stop a run early and you keep what it collected.
- A posting that can't be read after retries is returned with its listing fields and a `detailError`, billed as `job-listed`.

### ⬆️ Output

One record per job. The Output tab shows them as a labelled table, or as JSON if you prefer.

```json
{
  "id": "4462959761",
  "url": "https://uk.linkedin.com/jobs/view/data-analyst-music-at-handle-recruitment-4462959761",
  "title": "Data Analyst - music",
  "company": "Handle Recruitment",
  "companyUrl": "https://uk.linkedin.com/company/handle-recruitment",
  "companySlug": "handle-recruitment",
  "location": "London, England, United Kingdom",
  "postedAt": "2026-09-07",
  "closingAt": null,
  "postedText": "6 days ago",
  "benefits": [
    "Actively Hiring"
  ],
  "companyLogo": "https://media.licdn.com/dms/image/v2/D4E0BAQElJwj0RgST6A/company-logo_100_100/B4EZ1Df3q3HkAQ-/0/1774953921569/handle_recruitment_logo?e=2147483647&v=beta&t=ICdY7XWN2mVtJ-vArq8TMcqIC7bzPuO8NvuKIu78Mag",
  "searchKeywords": "data analyst",
  "searchUrl": "https://www.linkedin.com/jobs/search/?keywords=data+analyst&location=London&f_TPR=r604800&sortBy=DD",
  "descriptionHtml": "<p>…</p>",
  "description": "An exciting record label who are looking for a commercially minded Data Analyst to join their London team.\n\n This is a great opportunity for someone with 3-4 ye…",
  "seniorityLevel": "Mid-Senior level",
  "employmentType": "Full-time",
  "industries": "Accounting",
  "salaryText": null,
  "salaryMin": null,
  "salaryMax": null,
  "salaryPeriod": null,
  "salaryCurrency": null,
  "applicantsText": "45 applicants",
  "jobPosterProfileUrl": "https://uk.linkedin.com/in/example-recruiter",
  "jobPosterName": "Alex Example",
  "jobPosterTitle": "Recruitment Director",
  "jobPosterPhoto": "https://media.licdn.com/dms/image/…/profile-displayphoto-scale_400_400/…",
  "applicantsCount": 45,
  "isDirectApply": true,
  "companyDescription": "Handle Recruitment has been supporting the creative industries since 1978, building our reputation in music before expanding across film, TV, entertainment, fas…",
  "companyWebsite": "http://www.handle.co.uk",
  "companyIndustry": "Staffing and Recruiting",
  "companySize": "51-200 employees",
  "companyHeadquarters": "London",
  "companyType": "Privately Held",
  "companyFounded": "1978",
  "companySpecialties": ["Digital Recruitment", "Sales and Marketing Recruitment", "Office Support Recruitment", "Legal Recruitment", "…"],
  "companyId": "1234567",
  "companyEmployeesCount": 56,
  "companySlogan": "We've been supporting the UK's Creative Industries since 1978.",
  "companyFollowers": 295258,
  "companyLocations": [
    "Ink Rooms, 25-37 Easton Street London, WC1X 0DS, GB"
  ],
  "scrapedAt": "2026-09-13T21:45:29.322Z"
}
```

`postedAt` is a date, and `postedText` keeps LinkedIn's "6 days ago" wording beside it. The salary columns are filled on jobs with details where LinkedIn shows pay, and are null elsewhere. `closingAt` is always null: LinkedIn doesn't publish an expiry date on its public postings.

### How many jobs can you get, and how fast?

| Run | Result |
|---|---|
| 100 jobs with details | 100 jobs in 18-26 seconds |
| 6 searches, 1,100 jobs with details | 1,100 jobs in 2 minutes 14 seconds (about 490 a minute) |
| 100 jobs with **Read the full job page** on | 100 jobs in 28-32 seconds |
| 6 searches, 1,142 jobs with **Read the full job page** on | 1,142 jobs in 3 minutes 26 seconds (about 330 a minute) |

These are whole runs on Apify, measured on 5 October 2026. At that pace an hour covers roughly 25,000 jobs with details, or about 19,000 with the full job page.

LinkedIn serves at most about 1,000 results for any one search. A large run is therefore several keywords or several locations. Splitting a country into its cities is the usual way to go deeper.

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

| Event | Free plan | Starter | Scale | Business |
|---|---|---|---|---|
| `actor-start`, per run | $0.002 | $0.002 | $0.002 | $0.002 |
| `job-scanned`, per listing card fetched, before dedup or filters | $0.00005 | $0.00005 | $0.00005 | $0.00005 |
| `job-detail`, per job returned with details | $0.0006 | $0.00054 | $0.00048 | $0.00034 |
| `job-detail-enriched`, per job returned with details and company data, replacing `job-detail` for that job | $0.0006 | $0.00054 | $0.00048 | $0.00034 |
| `job-listed`, per job returned without details | $0.0002 | $0.00018 | $0.00016 | $0.00014 |

Enterprise plans pay the Business price.

**$0.60 per 1,000 jobs** on the Free plan and $0.34 on Business, with company details included. Nothing is added on top: proxy usage and platform usage are in that price.

Your bill follows the number of postings opened. A job removed by deduplication, by the only-new store or by a title or company filter costs the scan rate and no more. A posting that was opened is charged as a detail even if a later filter drops it. A posting that couldn't be read is returned listing-only and charged as `job-listed`.

- Two keywords at 200 results each, 60 dropped by title filters, 340 jobs returned with details and company data: $0.002 + 400 × $0.00005 + 340 × $0.0006 = **$0.226** ($0.138 on Business).
- A six-hourly monitor that scans 150 cards and finds 12 new jobs: $0.002 + 150 × $0.00005 + 12 × $0.0006 = **$0.017** ($0.014 on Business).

A run of 100 jobs with details costs about $0.067. On Apify's Free plan, which comes with $5 of credits a month, that's about 74 runs, or roughly 7,400 jobs, before you pay anything.

### Limits: what LinkedIn doesn't show

- **About 1,000 results per search.** Use "Locations (multiple)" to split a country into cities.
- **No apply URL and no expiry date.** Neither is published on LinkedIn's public postings.
- **No job function.** LinkedIn stopped showing it publicly (0 of 1,136 jobs in a check on 2 October 2026). Industries, seniority and employment type are returned.
- **No workplace-type column.** LinkedIn doesn't expose on-site, remote or hybrid as a field.
- **Experience level, job type and workplace type no longer filter the search on LinkedIn.** Here they're folded into the keywords. They steer the results strongly without being exact. Pair them with "Keep only these seniority levels" or "Keep only these employment types", which use LinkedIn's own labels on the posting.
- **Pay only where the employer states it.** On 5 October 2026, 193 of 1,142 London jobs across six searches had a range (12 of 100 for "software engineer"). The rest have empty pay columns.
- **Seniority is often "Not Applicable" on the short advert**: 580 of 1,100 jobs in the same check. With "Read the full job page" on, 125 of 1,142 read "Not Applicable". All other columns are identical either way.
- **A filter never guesses.** "Minimum annual salary" keeps a job whose pay can't be compared with a yearly figure (an hourly rate, or no pay shown). The seniority, employment-type and description filters keep a job whose page doesn't show that field.
- **Sorting orders what the run collected.** LinkedIn no longer sorts its public job search, so "Sort" can't change which jobs LinkedIn selects.

### FAQ

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

Neither. The actor reads LinkedIn's public job search only, and you never sign in. Proxy usage is included in the price and there's nothing for you to configure.

#### How do I get only the new jobs on a schedule?

Give "Only new jobs" a store name, then schedule the run in Apify. From the second run on you get the postings that weren't there before, and the ones already seen cost $0.00005 each to skip. Set "Posted within the last N hours" a little wider than your schedule interval.

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

Split it. Each keyword and each location is its own search with its own ceiling, so "nurse" across twelve cities reaches far further than "nurse" in "United Kingdom".

#### How accurate is the posting date?

It's the date LinkedIn holds for the posting, not a guess worked back from "1 day ago". The label is kept in `postedText` if you want it.

#### Why is there no apply URL or closing date?

LinkedIn stopped publishing both on its public job pages. `closingAt` stays in the row as null so the columns line up with our other job scrapers, and `isDirectApply` tells you whether the job uses Easy Apply.

#### How do I make sure every row really is part-time (or entry level)?

Use the pair. Set "Job type" to `part-time` to steer the search, and "Keep only these employment types" to `Part-time` to check each posting. With both set, every returned row is part-time. The same pairing works for experience level and seniority.

#### What does the company data include?

Description, website, industry, size, employee count, headquarters (with street, postcode and country code in `companyAddressParts`), type, specialties, follower count and office addresses. Each company is fetched once and cached for 30 days by default.

#### Is scraping LinkedIn jobs legal?

The actor reads only the job postings LinkedIn serves to logged-out visitors. Those are business content, not personal profiles. Web scraping of publicly available data is generally legal, but you're responsible for complying with LinkedIn's terms and applicable law in how you use the data.

### Run it on a schedule or from code

The setup we'd recommend is a monitor: an Apify Schedule every few hours with an only-new store named. A webhook can fire at the end of each run, and the new postings can go to Slack, Google Sheets, Zapier, Make or Airbyte through Apify's integrations.

From code, `POST https://api.apify.com/v2/acts/boncross~linkedin-jobs-scraper/run-sync-get-dataset-items` with the input JSON as the body returns the jobs in one call for short runs. Use the standard run endpoints for longer ones. Clients exist for Node.js (`apify-client` on NPM) and Python (`apify-client` on PyPI).

For an AI agent, add the Apify MCP server (`mcp.apify.com`) to your MCP client and the actor appears as a tool. You could ask it for today's new postings at three named companies and get a summary back.

### More from BonCross

- [LinkedIn Company Scraper](https://apify.com/boncross/linkedin-company-details-scraper): LinkedIn company pages by URL, slug or name, with no login.
- [LinkedIn Company Posts Scraper](https://apify.com/boncross/linkedin-company-posts-scraper): The latest posts from any LinkedIn company page, with no login.
- [LinkedIn Post Scraper](https://apify.com/boncross/linkedin-post-details-scraper): Any public LinkedIn post by URL, with no login.
- [NHS Jobs Scraper](https://apify.com/boncross/nhs-jobs): NHS Jobs vacancies by keyword and location.
- [Reed.co.uk Jobs Scraper](https://apify.com/boncross/reed-jobs-scraper): Reed.co.uk jobs by keyword and location with Reed's own filters: salary, contract,…
- [UK Government & NHS Jobs Scraper](https://apify.com/boncross/uk-public-sector-jobs): NHS Jobs, Civil Service Jobs (every department) and Ministry of Justice vacancies in…

### Your feedback

Spotted a LinkedIn job that came back wrong, or a field you expected and didn't get? Tell us on the Issues tab in Apify Console. We read each report and reply there, and fixes go out as new builds.

# Changelog

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

# Actor input Schema

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

One search per entry, for example "paralegal" or "compliance analyst". Results are deduplicated across searches. Leave this empty if you give start URLs instead.

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

The default location, written as you would type it into LinkedIn, such as "London, United Kingdom". Ignored when the locations list is set.

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

Optional list. Every keyword runs against each of these and the results are deduplicated. Splitting a country into its cities is how you get past LinkedIn's ceiling of about 1,000 results per search.

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

Radius around the location in miles, as on LinkedIn. 0 uses LinkedIn's default.

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

Optional. Paste full LinkedIn job-search URLs. Date-posted, company, Easy Apply and under-10-applicants filters are kept. The experience, job-type and workplace filters LinkedIn dropped are turned into keyword terms.

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

Ceiling per keyword or URL. The search stops earlier when LinkedIn runs out of matches for your window. LinkedIn serves at most about 1,000 per search.

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

A run-wide ceiling across every target. The per-target limit above applies to each one separately, so ten targets at 50 each returns 500; set this to cap the total. Zero means no run-wide limit. Rows that report an input the actor could not resolve are not counted here and are never charged for.

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

Only return jobs posted within this window. It is sent to LinkedIn as a real filter, so you do not pay for stale results.

## `postedWithinHours` (type: `integer`):

A finer window than the presets: 6 for a run every six hours, 1 for hourly. Overrides date posted when set. LinkedIn honours any number of hours.

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

Most recent first, or the order LinkedIn returned. LinkedIn no longer sorts the public job search, so "Most recent" is applied to the results after they are collected: it orders what the run fetched, it cannot change which jobs LinkedIn selects.

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

Experience level. Since August 2026 LinkedIn no longer filters on this, so it is added to your search terms instead. That steers results strongly but is not exact, and it narrows the pool. For exact results pair it with "Seniority levels" below.

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

Job type. Since August 2026 LinkedIn no longer filters on this, so it is added to your search terms instead. That steers results strongly but is not exact, and it narrows the pool. For exact results pair it with "Employment types" below.

## `workplaceTypes` (type: `array`):

Workplace type. Since August 2026 LinkedIn no longer filters on this, so it is added to your search terms instead. In testing this raised remote roles from about 1 in 9 to about 11 in 15, but it is not exact and it narrows the pool.

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

LinkedIn's own "under 10 applicants" filter, applied as a real URL filter.

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

LinkedIn's own Easy Apply filter, applied as a real URL filter.

## `companyIds` (type: `array`):

Optional numeric LinkedIn company ids to restrict the search to. Applied as a real URL filter. If you do not know the ids, use Company names above instead.

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

Company names to restrict the search to, e.g. "Monzo". Each is resolved to the numeric LinkedIn id that the company filter needs, so you do not have to find ids yourself. The run log shows what every name resolved to, and lists the other candidates it matched, because names are not unique. Resolution costs one lookup per name and is paced, so keep the list short.

## `seniorityLevelsFilter` (type: `array`):

A real filter on the seniority label printed on the job page (Internship, Entry level, Associate, Mid-Senior level, Director, Executive, Not Applicable). It runs after the detail fetch, so those jobs are still charged as details.

## `employmentTypesFilter` (type: `array`):

Filter on the employment-type label from the job page (Full-time, Part-time, Contract, Temporary, Internship, Volunteer, Other). Runs after the detail fetch.

## `maxApplicants` (type: `integer`):

Drop jobs with more applicants than this, for example 25. Finer than LinkedIn's under-10 toggle. Runs after the detail fetch.

## `minAnnualSalary` (type: `integer`):

Keep only jobs whose declared yearly pay reaches this. Needs "Fetch job details" on. A job that states no pay, or only an hourly or daily rate, is kept rather than dropped.

## `titleInclude` (type: `array`):

Optional, case-insensitive. Jobs whose title contains none of these are dropped before the detail page is fetched, so you are not charged a detail for them.

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

Optional, case-insensitive. Handy for cutting agency noise, for example "recruitment consultant" or "sales".

## `companyExclude` (type: `array`):

Case-insensitive substrings matched against the company name before the detail fetch, so these cost only the scan rate.

## `descriptionInclude` (type: `array`):

Case-insensitive substrings the full description must contain. Runs after the detail fetch.

## `descriptionExclude` (type: `array`):

Case-insensitive substrings that drop a job, for example "security clearance" or "driving licence". Runs after the detail fetch.

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

Needs company details switched on. Drops jobs whose company industry is Staffing and Recruiting.

## `fetchDetails` (type: `boolean`):

Fetch each job's page for the full description, seniority, employment type, industries and pay when the employer states it. Turn it off for a cheap listing-only run.

## `fetchFullJobPage` (type: `boolean`):

Reads each job's full LinkedIn page instead of the short advert. The columns are the same; the difference is the seniority level, which names a real level on far more jobs (in a London check of 1,100 jobs, "Not Applicable" fell from 580 to 125). Runs are about 45% slower. Declared pay is included either way. Needs "Fetch job details" on.

## `scrapeCompany` (type: `boolean`):

Also fetch each unique company's public LinkedIn page for description, website, industry, size, headquarters, type, founding year and specialities. One extra request per company rather than per job, cached within the run.

## `maxNewJobs` (type: `integer`):

Stop after this many jobs have been returned. 0 means no cap.

## `dedupStoreName` (type: `string`):

Name a key-value store, for example "linkedin-seen", and the actor remembers every job id it has returned. On later runs those jobs are skipped before the detail fetch, so a scheduled run gives you only what is new.

## `companyCacheDays` (type: `integer`):

With a dedup store set, company details fetched within this many days are reused rather than fetched again.

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

LinkedIn's numeric location id, such as 102257491 for London. More precise than the location text. Ignored when the locations list has more than one entry.

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

How many requests run at once across the whole run. The default is the fastest level measured clean; lower it only if a run reports refusals or errors.

## `delayMs` (type: `integer`):

Extra pause before each request, jittered by about 30 percent either way. The run already keeps to a request rate measured safe for LinkedIn, so the default is 0; raise it only if a run reports refusals.

## `restartAttempt` (type: `integer`):

Set by the actor when it restarts itself in a fresh container after being refused. Leave it alone.

## Actor input object example

```json
{
  "keywords": [
    "paralegal",
    "compliance analyst"
  ],
  "location": "London, United Kingdom",
  "locations": [],
  "distance": 0,
  "maxResultsPerSearch": 100,
  "maxItems": 0,
  "datePosted": "past24h",
  "postedWithinHours": 0,
  "sortBy": "date",
  "experienceLevels": [],
  "jobTypes": [],
  "workplaceTypes": [],
  "under10Applicants": false,
  "easyApply": false,
  "companyIds": [],
  "companyNames": [],
  "seniorityLevelsFilter": [],
  "employmentTypesFilter": [],
  "maxApplicants": 0,
  "minAnnualSalary": 0,
  "titleInclude": [],
  "titleExclude": [],
  "companyExclude": [],
  "descriptionInclude": [],
  "descriptionExclude": [],
  "excludeStaffingAgencies": false,
  "fetchDetails": true,
  "fetchFullJobPage": false,
  "scrapeCompany": false,
  "maxNewJobs": 0,
  "dedupStoreName": "",
  "companyCacheDays": 30,
  "geoId": "",
  "maxConcurrency": 8,
  "delayMs": 0,
  "restartAttempt": 0
}
```

# Actor output Schema

## `dataset` (type: `string`):

LinkedIn jobs: every row the run returned.

## `stats` (type: `string`):

Counts for the run: what was resolved, fetched, filtered and returned.

# 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": [
        "paralegal",
        "compliance analyst"
    ],
    "location": "London, United Kingdom",
    "companyNames": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("boncross/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": [
        "paralegal",
        "compliance analyst",
    ],
    "location": "London, United Kingdom",
    "companyNames": [],
}

# Run the Actor and wait for it to finish
run = client.actor("boncross/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": [
    "paralegal",
    "compliance analyst"
  ],
  "location": "London, United Kingdom",
  "companyNames": []
}' |
apify call boncross/linkedin-jobs-scraper --silent --output-dataset

```

## MCP server setup

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