# LinkedIn Jobs Scraper & Monitor ($0.30/1K) (`hydedata/linkedin-jobs-scraper`) Actor

$0.30 per 1,000 jobs, no login or cookies. Search LinkedIn job postings by keyword and location. Filter by date posted (even past hour), Easy Apply, company, experience level and job type. Optional full descriptions and pay ranges. Monitor mode returns only new jobs, with Discord or Slack alerts.

- **URL**: https://apify.com/hydedata/linkedin-jobs-scraper.md
- **Developed by:** [Jacob Hyde](https://apify.com/hydedata) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.30 / 1,000 job delivereds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

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

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

## How to integrate an Actor?

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

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

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

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

# README

## LinkedIn Jobs Scraper & Monitor

Scrape LinkedIn job postings by keyword and location, or run any LinkedIn job search URL. No LinkedIn
account, login or cookies needed. $0.30 per 1,000 jobs.

Turn on **monitor mode** and schedule it: each run returns only jobs posted since the last run, and can
post them straight to Discord or Slack. You're never billed for a job you've already received, or for
jobs your filters drop.

### What you get

For every job:

| Field | Example |
| --- | --- |
| Title | Senior Software Engineer, Backend |
| Company, with its LinkedIn page and logo | General Motors |
| Location | Warren, MI |
| Posted date, and how long ago | 2026-10-04, 1 week ago |
| LinkedIn's note on the card | Actively Hiring, Be an early applicant |
| Link to the job | linkedin.com/jobs/view/4419969671 |

Each row also names the keyword and location it was found with.

Turn on **Add job details** to also get:

| Field | Example |
| --- | --- |
| Full description, as text and HTML | "As a Senior Software Engineer you will..." |
| Pay range, when the posting states one | 160000 to 200000, USD, per year |
| Seniority level | Mid-Senior level |
| Employment type | Full-time |
| Industries | Motor Vehicle Manufacturing |
| Applicants | 195 |
| Easy Apply or apply on the company site | easy-apply / external |

Pay is read from the description, where most US employers now state a range. In a sample of San
Francisco software jobs, 11 of 12 had one.

Recruiter names, profiles and contact details aren't collected.

### How to use it

#### Search by keyword and location

Add one or more job titles or keywords and one or more locations. Each keyword runs in every location.
Locations are typed the way you would on LinkedIn: a city ("Austin, TX"), a state, a country or a metro
area ("San Francisco Bay Area"). Leave them empty to search worldwide.

```json
{
  "searchQueries": ["data analyst", "business analyst"],
  "locations": ["New York, NY", "Chicago, IL"],
  "postedWithin": "week",
  "maxJobsPerSearch": 200
}
```

LinkedIn's search syntax works in keywords: quotes for exact phrases, `OR`, `NOT`
(`"data engineer" OR "analytics engineer"`). Jobs that show up in more than one search are delivered once.

#### Filters

LinkedIn's own filters:

- **Posted within**: past hour, 24 hours, week or month. "Past hour" isn't in LinkedIn's menu but works.
- **Easy Apply only**
- **Under 10 applicants**
- **Distance**: radius in miles around a city.
- **Company IDs**: only jobs at these companies (e.g. 1441 for Google).

Filters the Actor checks for you, before you're charged:

- **Title must contain the keywords**: LinkedIn fills thin searches with unrelated jobs (a rare keyword
  in a small city returns dental assistants and arborists). This keeps only titles with every keyword word.
- **Exclude titles with these words**: e.g. senior, intern, "sales associate".
- **Exclude companies**: e.g. staffing agencies.
- **Experience level** and **Job type**: LinkedIn's public search ignores these, so the Actor reads each
  job's page and keeps only the ones you picked. This turns on job details.

```json
{
  "searchQueries": ["product manager"],
  "locations": ["United States"],
  "postedWithin": "day",
  "excludeKeywords": ["senior", "director", "principal"],
  "excludeCompanies": ["Jobot", "Robert Half"],
  "strictKeywordMatch": true
}
```

#### Paste search URLs

Run a search on linkedin.com/jobs/search in a private browser window and paste the address into
**LinkedIn job search URLs**. Its keywords, location and filters are kept. You can mix URLs and keywords
in one run.

#### Switching from another LinkedIn jobs scraper

Input written for other popular LinkedIn job scrapers works here as is: `urls`, `keywords`, `location`,
`datePosted`, `easyApply`, `companyId`, `titleExclude`, and limits like `limit` or `limitPerSource` are read
the same way. Change the Actor name in your API call or integration and keep your input.

#### Monitor new jobs

1. Set up your searches and turn on **Monitor mode**.
2. Run it once. The first run returns up to your limit and remembers everything else LinkedIn shows for
   the search, so it isn't sent to you later.
3. Add a schedule (every hour, every morning, whatever fits). Each later run returns only jobs posted
   since then.

Pair it with **Posted within: Past 24 hours** so each run checks fewer pages. Use a different **Monitor
state store name** for each independent monitor.

#### Get alerts in Discord or Slack

Paste a Discord or Slack incoming webhook URL into **Send new jobs to a webhook**. Each run posts what it
found, with title, company, location, pay and link. Nothing is sent when a run finds nothing new. Any
other URL receives the jobs as JSON.

For email, Google Sheets, Zapier or Make, use Apify's integrations on the Actor or your task.

### Pricing

You pay per event. Platform usage is included.

| Event | Price |
| --- | --- |
| Job delivered | $0.0003 ($0.30 per 1,000) |
| Search checked (once per keyword and location, or URL, on each run) | $0.003 |
| Job details (optional) | $0.001 per job with details |
| Run start | $0.00005 |

Examples:

- **Pull 1,000 jobs once:** $0.30 for the jobs plus $0.003 for the search. Extra result pages aren't charged.
- **1,000 jobs with full descriptions and pay:** $0.30 for the jobs, $1 for details, $0.003 for the search.
- **Watch one search every hour for a month:** 720 checks x $0.003 = $2.16, plus $0.0003 per new job.
  A search with 50 new jobs a day adds about $0.45.
- **Check 10 searches every morning:** 300 checks = $0.90 a month, plus new jobs.

Jobs you've already received are never billed in monitor mode, jobs your filters drop are never billed,
and a search that fails, gets blocked or comes back empty is never billed. Details are only billed when
they were actually fetched. Set a maximum charge per run in the run options and the Actor stops cleanly
when it's reached.

### Input options

| Option | What it does |
| --- | --- |
| Job titles or keywords | What to search for. Each keyword runs in every location. LinkedIn's syntax (quotes, OR, NOT) works. |
| Locations | Cities, states, countries or metro areas. Empty means worldwide. |
| LinkedIn job search URLs | Searches to run as they are, with their filters. |
| Max jobs per search | Upper limit per keyword and location, or per URL (default 100, at most 1,000). |
| Posted within | Any time, past hour, 24 hours, week or month. |
| Add job details | Description, pay range, seniority, employment type, industries, applicants, apply type. Charged per job. |
| Easy Apply only | Only jobs you apply to on LinkedIn. |
| Under 10 applicants | Only jobs with fewer than 10 applicants. |
| Distance (miles) | Radius around a city location. |
| Company IDs | Only jobs at these companies. |
| Title must contain the keywords | Drops unrelated jobs LinkedIn mixes into thin searches. Off by default. |
| Exclude titles with these words | Drops jobs whose title contains any of these. |
| Exclude companies | Drops jobs from these companies. |
| Experience level | Only these seniority levels. Turns on details. |
| Job type | Only these employment types. Turns on details. |
| Monitor mode | Return only jobs posted since the last run. |
| Send new jobs to a webhook | Discord or Slack webhook URL for alerts, or any URL for JSON. Stored encrypted. |
| Monitor state store name | Keeps each monitor's memory separate. |
| Forget jobs after (days) | How long a job is remembered (default 30). |
| Proxy configuration | Apify datacenter proxies by default. |

### Output example

```json
{
  "id": "4419969671",
  "url": "https://www.linkedin.com/jobs/view/4419969671/",
  "title": "Senior Software Engineer – Go (Golang)",
  "companyName": "General Motors",
  "companyUrl": "https://www.linkedin.com/company/general-motors",
  "companyLogo": "https://media.licdn.com/dms/image/v2/D4E0BAQEfAZWzerj03w/company-logo_100_100/...",
  "location": "Warren, MI",
  "postedAt": "2026-10-04",
  "postedTimeAgo": "1 week ago",
  "insight": "Actively Hiring",
  "salary": "$160,000 and $200,000",
  "salaryMin": 160000,
  "salaryMax": 200000,
  "salaryCurrency": "USD",
  "salaryPeriod": "year",
  "searchKeyword": "software engineer",
  "searchLocation": "United States",
  "searchUrl": "https://www.linkedin.com/jobs-guest/jobs/api/seeMoreJobPostings/search?keywords=software+engineer&location=United+States",
  "scrapedAt": "2026-10-17T20:58:48.775Z",
  "description": "Job Description\n\nAs a Senior Software Engineer – Go (Golang), you will design, develop, and deliver...",
  "descriptionHtml": "<strong>Job Description<br><br></strong>As a Senior Software Engineer...",
  "seniorityLevel": "Not Applicable",
  "employmentType": "Full-time",
  "industries": "Motor Vehicle Manufacturing, Appliances, Electrical, and Electronics Manufacturing",
  "applicantsCount": 195,
  "applicantsText": "195 applicants",
  "applyType": "external"
}
```

Without details, the description, job facts and pay range fields are left out or null. Download results
as JSON, CSV, Excel or HTML, or read them through the Apify API.

### Tips

- **More than 1,000 jobs?** LinkedIn shows at most 1,000 per search. Split it into several locations, or
  use **Posted within** and run it more often.
- **Results look unrelated?** LinkedIn pads searches that run out of real matches. Turn on **Title must
  contain the keywords**, or add exclude words.
- **Most jobs say "Not Applicable" for seniority.** That's what the employer set: about 3 in 4 jobs in
  our samples, with only 3 to 4 in 100 marked Entry level. So an experience filter returns few jobs from a
  general search. Pair it with matching keywords ("junior", "graduate"), or pick "Not set by employer" in
  **Experience level** to keep those.
- **Remote jobs:** add "remote" to the keywords ("product manager remote"). LinkedIn's public search has no
  remote filter, and "Remote" as a location searches worldwide.

### FAQ

**Do I need a LinkedIn account?**
No. The Actor only reads job postings anyone can see on LinkedIn without logging in.

**Why don't I get the apply link for external jobs?**
LinkedIn only shows it to signed-in members. Each row has the job's LinkedIn link, and `applyType` says
whether it's Easy Apply or on the company's site.

**Is it legal to scrape LinkedIn jobs?**
The Actor collects only publicly visible job postings and no personal profiles. How you use the data is
up to you; check the rules that apply where you are.

**Something broke?**
Open an issue on the Actor's Issues tab. The Actor is tested against live LinkedIn every day.

**Like it?**
A rating on the Actor's page helps other people find it, and tells us what to build next.

# Changelog

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

# Actor input Schema

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

What to search for, as you'd type it on LinkedIn. Each keyword is a separate search in every location below. LinkedIn's search syntax works: quotes, OR, NOT (e.g. "data engineer" OR "analytics engineer"). Leave empty to get every job in the locations, e.g. with a company filter.

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

Cities, states, countries or metro areas, as you'd type them on LinkedIn (e.g. "Austin, TX", "London, United Kingdom", "Germany"). A LinkedIn geoId works too. Leave empty to search worldwide. For remote jobs, add "remote" to the keywords instead; "Remote" as a location searches worldwide.

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

Optional. Run a search on linkedin.com/jobs/search in a private browser window and paste the address. Its keywords, location and filters are kept. Can be used with or instead of keywords.

## `maxJobsPerSearch` (type: `integer`):

Upper limit of jobs delivered per keyword and location (or per search URL). LinkedIn shows at most 1,000 jobs per search; for more, split it by location or use "Posted within".

## `postedWithin` (type: `string`):

Only jobs posted in this window. "Past hour" isn't in LinkedIn's menu but works, which makes it the fastest way to catch new postings with a schedule.

## `includeDetails` (type: `boolean`):

For each delivered job, also get the full description (text and HTML), seniority level, employment type, industries, applicant count, Easy Apply or external apply, and the pay range when the posting states one (min, max, currency, per hour or year). Charged per job that gets details. In monitor mode only new jobs are fetched. Recruiter names and profiles are never collected.

## `easyApplyOnly` (type: `boolean`):

Only jobs you can apply to on LinkedIn.

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

Only jobs with fewer than 10 applicants so far.

## `distanceMiles` (type: `integer`):

Search radius around a city location, e.g. 10, 25, 50 or 100. Leave empty for LinkedIn's default.

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

Only jobs at these companies. Use LinkedIn's numeric company ID, found as f_C= in a search URL after picking the company in LinkedIn's Company filter (e.g. 1441 for Google).

## `strictKeywordMatch` (type: `boolean`):

Drop jobs whose title doesn't contain every keyword word. LinkedIn fills thin searches with unrelated jobs (a rare keyword in a small city returns dental assistants and arborists), and this keeps only real matches. Leave it off for skill keywords like "python" that rarely appear in titles. Dropped jobs aren't charged.

## `excludeKeywords` (type: `array`):

Drop jobs whose title contains any of these words or phrases (e.g. senior, intern, "sales associate"). Dropped jobs aren't charged.

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

Drop jobs from companies whose name contains any of these (e.g. staffing agencies you don't want). Dropped jobs aren't charged.

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

Only jobs at these seniority levels. LinkedIn's public search ignores this filter, so the Actor checks each job's page; it turns on job details (charged). About 3 in 4 jobs have no level set by the employer, so expect few matches from a general search, or pick "Not set by employer" to keep those.

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

Only jobs of these employment types. LinkedIn's public search ignores this filter, so the Actor checks each job's page; it turns on job details (charged).

## `monitorMode` (type: `boolean`):

Remembers what previous runs returned and only outputs jobs you haven't seen. Use with a Schedule. The first run returns everything as the baseline. Jobs seen before are never charged again.

## `notifyWebhookUrl` (type: `string`):

Paste a Discord or Slack incoming webhook URL to get each run's jobs as a message with title, company, location, pay and link. Any other URL gets the jobs as JSON. Only sent when the run delivers something, so quiet monitor runs stay silent.

## `stateStoreName` (type: `string`):

Named key-value store that keeps monitor memory between runs. Use different names to run independent monitors.

## `stateRetentionDays` (type: `integer`):

Jobs not seen for this many days are dropped from monitor memory.

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

Apify datacenter proxies work for LinkedIn's public job pages. Switch to RESIDENTIAL only if runs keep getting rate-limited.

## Actor input object example

```json
{
  "searchQueries": [
    "software engineer"
  ],
  "locations": [
    "United States"
  ],
  "maxJobsPerSearch": 100,
  "postedWithin": "any",
  "includeDetails": false,
  "easyApplyOnly": false,
  "under10Applicants": false,
  "strictKeywordMatch": false,
  "monitorMode": false,
  "stateStoreName": "linkedin-jobs-monitor-state",
  "stateRetentionDays": 30,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

Dataset items, one per delivered job. Field meanings are in the dataset schema.

# 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": [
        "software engineer"
    ],
    "locations": [
        "United States"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("hydedata/linkedin-jobs-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "searchQueries": ["software engineer"],
    "locations": ["United States"],
    "proxyConfiguration": { "useApifyProxy": True },
}

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

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

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

```

## CLI example

```bash
echo '{
  "searchQueries": [
    "software engineer"
  ],
  "locations": [
    "United States"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call hydedata/linkedin-jobs-scraper --silent --output-dataset

```

## MCP server setup

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