# LinkedIn Jobs Scraper - Keyword & Location Search (No Login) (`kamerozkan/linkedin-jobs-scraper`) Actor

Scrape LinkedIn jobs by keyword, location, date, company or search URL without a login. Full job details: description, seniority, employment type, function, industries, applicants. $1 per 1,000 jobs, new-jobs-only mode for daily runs, optional official apply-link and ghost-job checks.

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

## Pricing

from $0.70 / 1,000 job results

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

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

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

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

Scrape LinkedIn job postings by keyword and location, with full public job details, without a LinkedIn login.

This Actor reads LinkedIn's public job search, the same pages anyone can open in a private browser window, and returns one clean row per job: title, company, location, posting date, seniority level, employment type, job function, industries, applicant count, salary when LinkedIn shows it, and the complete description as text and HTML. It never asks for your LinkedIn account, session cookies, or password.

Built for job boards and aggregators, recruiters and staffing agencies, lead generation from hiring signals, market and salary research, and daily job alert automations in Make, Zapier, n8n, or your own code.

### What you get for every job

| Field | Description |
| --- | --- |
| `id`, `url` | LinkedIn job ID and the canonical job URL |
| `title`, `companyName`, `companyUrl`, `companyLogo` | Job title and the hiring company's public LinkedIn page and logo |
| `location`, `postedAt`, `postedText` | Location as posted, posting date (YYYY-MM-DD), and LinkedIn's relative text such as "3 weeks ago" |
| `seniorityLevel`, `employmentType`, `jobFunction`, `industries` | The four structured criteria LinkedIn publishes on the job page |
| `applicantsCount`, `applicantsText` | Applicant count when LinkedIn shows a number, plus the original text |
| `salaryText`, `benefits` | Salary range and highlights such as "Actively Hiring" whenever LinkedIn shows them publicly |
| `descriptionText`, `descriptionHtml` | The full job description as readable text and as HTML |
| `detailStatus` | `complete`, `not_requested`, or `unavailable` (the detail page could not be read after retries; such rows are not charged) |
| `searchKeywords`, `searchLocation`, `searchUrl` | Which search produced the row |
| `isNew`, `firstSeenAt` | New-jobs-only bookkeeping for scheduled runs |
| `officialApplyUrl`, `jobStatus`, `ghostJobRisk`, `verificationStatus` and related fields | Filled only when optional apply-link verification is enabled |

The output contains no job poster names, profile photos, recruiter profile links, applicant identities, emails, or phone numbers. It is a job-postings dataset, not a people dataset.

### Quick start

```json
{
  "keywords": ["software engineer"],
  "locations": ["Berlin, Germany"],
  "datePosted": "pastWeek",
  "maxJobsPerSearch": 100
}
```

Every keyword is searched in every location. Add more keywords and locations to cover a market, or paste LinkedIn search URLs when you prefer to build the filters on linkedin.com first.

### Input fields

| Field | Description | Default |
| --- | --- | --- |
| `keywords` | Up to 20 job titles or keywords, one search each; LinkedIn boolean syntax such as `data (analyst OR scientist)` works | Empty |
| `locations` | Up to 20 cities, regions, or countries as you would type them on LinkedIn; empty means worldwide | Empty |
| `searchUrls` | Up to 20 LinkedIn jobs search URLs copied from a private browser window | Empty |
| `datePosted` | `anyTime`, `past24Hours`, `pastWeek`, `pastMonth` | `anyTime` |
| `sortBy` | `relevance` or `date` | `relevance` |
| `companyIds` | Numeric LinkedIn company IDs (the `f_C` value in a search URL) that restrict every search | Empty |
| `geoId` | Numeric LinkedIn geoId for exact location targeting; overrides `locations` | Empty |
| `easyApplyOnly` | Only jobs with LinkedIn Easy Apply | `false` |
| `under10Applicants` | Only jobs with fewer than 10 applicants | `false` |
| `workplaceType`, `jobType`, `experienceLevel` | Remote/hybrid/on-site, job type, and seniority selections, added to the search text (see below) | Empty |
| `maxJobsPerSearch` | Cost and time cap per keyword and location combination, 10 to 1,000 | `100` |
| `includeJobDetails` | Fetch every job's public detail page | `true` |
| `newJobsOnly` | Return only jobs not seen in earlier runs of the same watchlist | `false` |
| `stateNamespace` | Watchlist history scope for new-jobs-only mode; `auto` isolates each saved Task | `auto` |
| `verifyApplyLinks` | Optional official apply-link and ghost-job verification through a separate Actor | `false` |
| `maxConcurrency` | Parallel detail requests, 1 to 10 | `5` |
| `proxyConfiguration` | Apify Proxy, included in the price | Apify Proxy |

At least one keyword, one search URL, or one company ID is required. The input expands to at most 50 searches per run.

### How the filters really work

LinkedIn changed its public job search in August 2026. The date posted, company, Easy Apply, under-10-applicants, location, geoId, and sort filters still work as real server-side filters, and this Actor applies them exactly. The workplace type, job type, and experience level filters are no longer applied by LinkedIn on public search pages. Instead of silently ignoring your selection, the Actor adds it to the search text in a fixed order, for example `Remote (Full-time OR Contract) Entry level software engineer`, which is what LinkedIn itself recommends. Treat those three selections as strong hints, not guarantees, and check `seniorityLevel` and `employmentType` on the returned rows when you need exact values.

Search URLs are parsed the same way: `keywords`, `location`, `geoId`, `distance`, `f_TPR`, `f_C`, `f_AL`, `f_EA`, and `sortBy` are applied as filters; `f_WT`, `f_JT`, and `f_E` are converted into search text. The run summary lists every converted filter under `convertedFilters` so nothing happens silently.

### New jobs only: pay for new postings, not for re-downloads

Most job pipelines run the same search every day and then have to deduplicate on their side. Turn on `newJobsOnly` and the Actor remembers every job ID it has returned for your watchlist and emits only jobs it has not seen before. Combined with `datePosted: "past24Hours"`, a daily schedule delivers exactly the new postings, and you pay only for those rows.

```json
{
  "keywords": ["account executive", "sales development representative"],
  "locations": ["United States"],
  "datePosted": "past24Hours",
  "sortBy": "date",
  "newJobsOnly": true,
  "stateNamespace": "auto"
}
```

Save the input as an Apify Task, add a Schedule, and keep `stateNamespace` on `auto` so each Task keeps its own history. API callers should set one stable namespace per watchlist. The first run returns everything it finds and creates the baseline. Job IDs are remembered for 120 days.

### Search URL mode

Open https://www.linkedin.com/jobs/search/ in a private browser window, enter your search, apply filters, and copy the address bar into `searchUrls`:

```json
{
  "searchUrls": [
    "https://www.linkedin.com/jobs/search/?keywords=data%20analyst&location=London%2C%20United%20Kingdom&f_TPR=r86400&sortBy=DD"
  ],
  "maxJobsPerSearch": 300
}
```

Keyword searches and search URLs can be combined in one run. Identical searches are merged, and a job found by several searches is returned once.

### Official apply links and ghost-job risk (optional)

LinkedIn's public job page does not expose the employer's application URL. When you enable `verifyApplyLinks`, the scraped jobs are sent to the [LinkedIn Job Apply Link Verifier](https://apify.com/kamerozkan/linkedin-job-apply-link-verifier), which returns the official application URL, the applicant tracking system behind it, the live job status, a confidence score, evidence, and a ghost-job risk label into the same rows. Use `maxJobsToVerify` and `verificationMaxTotalChargeUsd` as hard cost guards; the verifier is billed separately at the price shown on its Store page. A failed verification never removes scraped rows; the rows simply carry `verificationStatus: "failed"`.

### Example output

The row below is unedited output from a local production run on 2026-09-02 for the search `software engineer` in `Berlin, Germany` (past week, remote hint, job details on, verification off). The description, HTML, and logo URL are shortened here for readability only.

```json
{
  "id": "4413440825",
  "url": "https://www.linkedin.com/jobs/view/4413440825",
  "title": "Product Engineer",
  "companyName": "Attio",
  "companyUrl": "https://uk.linkedin.com/company/attio",
  "companyLogo": "https://media.licdn.com/dms/image/v2/C4D0BAQF16UXOMcHaCA/company-logo_100_100/company-logo...",
  "location": "Germany",
  "postedAt": "2026-08-27",
  "postedText": "6 days ago",
  "applicantsCount": 200,
  "applicantsText": "Over 200 applicants",
  "salaryText": "€80,000.00/yr - €100,000.00/yr",
  "benefits": null,
  "seniorityLevel": "Not Applicable",
  "employmentType": "Full-time",
  "jobFunction": "Engineering and Information Technology",
  "industries": "Software Development",
  "descriptionText": "Attio is the CRM for agentic revenue. Designed for the most ambitious go-to-market teams, it gives companies the power to understand every customer, automate at ...",
  "descriptionHtml": "<strong>Attio is the CRM for agentic revenue.</strong> Designed for the most ambitious go-to-market teams, it gives comp ...",
  "detailStatus": "complete",
  "searchKeywords": "software engineer",
  "searchLocation": "Berlin, Germany",
  "searchUrl": "https://www.linkedin.com/jobs/search?keywords=Remote+software+engineer&location=Berlin%2C+Germany&f_TPR=r604800&sortBy=R",
  "isNew": null,
  "firstSeenAt": "2026-09-02T16:57:29.774Z",
  "verificationStatus": "not_requested",
  "officialApplyUrl": null,
  "applyMethod": null,
  "jobStatus": null,
  "ghostJobRisk": "unknown",
  "verificationConfidence": null,
  "verifiedAt": null,
  "verificationEvidence": [],
  "safeToPublish": null,
  "reviewRequired": null,
  "sourceProvider": null,
  "companyWebsite": null,
  "scrapedAt": "2026-09-02T16:57:29.774Z"
}
```

The `OUTPUT` record in the key-value store reports, for every search, how many jobs were found, the total LinkedIn reported, whether the search finished or stopped at a cap, how many pages and retries were needed, plus billing counters, new-jobs-only state sizes, and warnings.

### Pricing

- Job results: **$1.00 per 1,000** jobs returned with details included
- Actor start: **$0.005 per GB of run memory** (one event per run at the default 512 MB)
- Platform usage and Apify Proxy: included

Rows whose public detail page could not be read after retries are returned with `detailStatus: "unavailable"` and are not charged. Turning `includeJobDetails` off does not change the price; it only makes runs faster.

| Jobs returned | Job result cost |
| ---: | ---: |
| 100 | $0.10 |
| 1,000 | $1.00 |
| 10,000 | $10.00 |

A daily new-jobs-only schedule over five keyword searches that finds 40 new postings per day costs about $1.20 per month in job results plus the Actor start for each run. Bronze, Silver, Gold, Platinum, and Diamond plans receive the reduced tiered price shown on the Store page. Optional apply-link verification is billed by the verifier Actor.

### Scheduling and integrations

1. Save your input as an Apify Task.
2. Add a Schedule, for example daily at 07:00.
3. Turn on `newJobsOnly` and set `datePosted` to `past24Hours`.
4. Add a webhook for `ACTOR.RUN.SUCCEEDED`, or read the dataset from Make, Zapier, n8n, the Apify API, the JavaScript or Python client, or Apify's MCP server for AI agents.

Export the dataset as JSON, CSV, Excel, or XML from the run page or through the API.

### Limits and honesty notes

- LinkedIn's public search returns at most 1,000 jobs per search. Split a large market by city, keyword, or date window to go beyond it; the run summary shows when a search hit the cap.
- Public search results are ranked and geo-resolved by LinkedIn per session, so the same broad search can return a noticeably different slice between runs, and LinkedIn's reported total can change too. For reproducible schedules use `sortBy: "date"`, a short `datePosted` window, and ideally a `geoId`; new-jobs-only mode then turns the remaining variation into a clean stream of new postings.
- Salary appears only when LinkedIn publishes it on the job. Employer application URLs require the optional verification step.
- LinkedIn can throttle public traffic. Every search and every job detail request uses its own proxy session and is retried with a fresh session; searches that still fail are listed in the summary, and no job is charged twice.
- The Actor reads public job pages only. It does not log in, does not collect applicant or recruiter profiles, and does not bypass LinkedIn's authentication.
- Users remain responsible for using the output lawfully and in line with applicable terms.

### FAQ

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

No. The Actor reads only the public job search and job pages that are available without login.

#### How many jobs can I scrape per run?

Up to 1,000 jobs per search and up to 50 searches per run, so up to 50,000 jobs in one run when the searches are broad enough. Use `maxJobsPerSearch` to control cost.

#### Can I search by company instead of keyword?

Yes. Put numeric LinkedIn company IDs in `companyIds` with or without keywords. For a change feed of a company's whole job inventory with new, changed, and closed roles, use the companion [LinkedIn Company Jobs Monitor](https://apify.com/kamerozkan/linkedin-hiring-signals).

#### Why does the remote or experience filter return other jobs too?

LinkedIn no longer applies those filters on public search; the Actor adds them to the search text, which improves ranking but is not an exact filter. Check `employmentType` and `seniorityLevel` on the rows or filter in your own workflow.

#### Is this affiliated with LinkedIn?

No. This Actor is not affiliated with or endorsed by LinkedIn or Microsoft.

### Related Actors

- [LinkedIn Company Jobs Monitor & Change Feed](https://apify.com/kamerozkan/linkedin-hiring-signals): stateful new, changed, reopened, and confirmed closed roles per company.
- [LinkedIn Job Apply Link Verifier](https://apify.com/kamerozkan/linkedin-job-apply-link-verifier): official apply URLs, live status, and ghost-job risk for any LinkedIn job URL.
- [ATS Job Change Feed API](https://apify.com/kamerozkan/ats-job-change-feed): the same change-feed contract read directly from Greenhouse, Lever, Workday, and other public job boards.

# Actor input Schema

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

One search per keyword, for example "software engineer" or "registered nurse". LinkedIn boolean syntax such as "data (analyst OR scientist)" works. Leave empty when you only use search URLs or company IDs.

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

City, region, or country as you would type it on LinkedIn, for example "Berlin, Germany", "United States", or "London Area, United Kingdom". Every keyword is searched in every location. Leave empty for a worldwide search.

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

Open https://www.linkedin.com/jobs/search/ in a private browser window, apply filters, and paste the address bar URL. Keywords, location, geoId, date posted, company, Easy Apply, under-10-applicants, and sort order are read from the URL.

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

Only return jobs posted within this window. Use "Past 24 hours" for daily schedules.

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

Most relevant follows LinkedIn ranking; most recent orders by posting date and is the better choice for schedules and new-jobs-only mode.

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

Numeric LinkedIn company IDs, for example 1441 for Google. Found as f\_C in a LinkedIn jobs search URL. Restricts every search to these companies.

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

LinkedIn numeric geoId for exact location targeting, found as geoId in a LinkedIn jobs search URL. Overrides the free-text locations.

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

Only return jobs that can be applied to with LinkedIn Easy Apply.

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

Only return jobs that LinkedIn marks as having fewer than 10 applicants.

## `workplaceType` (type: `array`):

Remote, hybrid, or on-site. LinkedIn stopped applying this as a public search filter in August 2026, so the selection is added to the search text instead; treat it as a strong hint, not a guarantee.

## `jobType` (type: `array`):

Added to the search text for the same reason as workplace type.

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

Added to the search text for the same reason as workplace type.

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

Cost and time cap for each keyword and location combination. LinkedIn's public search never returns more than 1,000 jobs per search; split by city or narrow the date window for more.

## `includeJobDetails` (type: `boolean`):

Fetch every job's public detail page: description, seniority level, employment type, job function, industries, applicants, and company logo. Turn off for a fast list of titles, companies, locations, and dates only.

## `newJobsOnly` (type: `boolean`):

Remembers every job ID per watchlist namespace and returns only jobs that were not seen before. Ideal for daily schedules: you pay only for new postings. The first run returns everything.

## `stateNamespace` (type: `string`):

Separates independent new-jobs-only histories. "auto" isolates each saved Task automatically; API callers should set a stable unique value per watchlist.

## `verifyApplyLinks` (type: `boolean`):

Optional premium step. Sends the scraped jobs to the LinkedIn Job Apply Link Verifier Actor, which returns the official employer application URL, live status, confidence, and ghost-job risk into the same rows. Billed separately by that Actor.

## `maxJobsToVerify` (type: `integer`):

Cost guardrail for the optional apply-link verification in one run.

## `verificationMaxTotalChargeUsd` (type: `number`):

Maximum pay-per-event charge allowed for the verifier run.

## `verifierActorId` (type: `string`):

Actor used for optional apply-link verification.

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

Parallel job detail requests. Higher values are faster but raise the chance of LinkedIn rate limits; the Actor retries with fresh proxy sessions.

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

Apify Proxy is used by default and included in the price. Each search and each job detail request uses its own proxy session.

## Actor input object example

```json
{
  "keywords": [
    "software engineer"
  ],
  "locations": [
    "Berlin, Germany"
  ],
  "datePosted": "anyTime",
  "sortBy": "relevance",
  "easyApplyOnly": false,
  "under10Applicants": false,
  "maxJobsPerSearch": 100,
  "includeJobDetails": true,
  "newJobsOnly": false,
  "stateNamespace": "auto",
  "verifyApplyLinks": false,
  "maxJobsToVerify": 100,
  "verificationMaxTotalChargeUsd": 1,
  "verifierActorId": "kamerozkan/linkedin-job-apply-link-verifier",
  "maxConcurrency": 5,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

Scraped LinkedIn job postings with public details and optional apply-link verification fields.

## `summary` (type: `string`):

Per-search job counts, completeness, LinkedIn's reported totals, new-jobs-only state, billing, and warnings.

# 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": [
        "software engineer"
    ],
    "locations": [
        "Berlin, Germany"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("kamerozkan/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": ["software engineer"],
    "locations": ["Berlin, Germany"],
}

# Run the Actor and wait for it to finish
run = client.actor("kamerozkan/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": [
    "software engineer"
  ],
  "locations": [
    "Berlin, Germany"
  ]
}' |
apify call kamerozkan/linkedin-jobs-scraper --silent --output-dataset

```

## MCP server setup

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