# LinkedIn Jobs Scraper (no login, no cookies) (`kingly_contest_7cb/linkedin-jobs-scraper`) Actor

Scrape LinkedIn job postings without an account or cookies. Search keywords × locations with LinkedIn filters; get title, company, location, country, posted date, salary, seniority, applicants, Easy Apply and full description. Deduped, resumable.

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

## Pricing

from $0.60 / 1,000 jobs

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 (no login, no cookies)

Get job postings from LinkedIn's **public job search**: the pages anyone can see without signing in. You type keywords and locations like you would on LinkedIn. You get a clean table of jobs with company, location, posted date, applicant count, seniority, employment type, salary (when LinkedIn shows it) and the full description.

No LinkedIn account, no cookies and no browser are needed. Your own LinkedIn profile is never involved.

### What it does

- **Searches like LinkedIn does.** Keywords × locations, plus LinkedIn's own filters: date posted, job type, experience level, on-site/remote/hybrid, sort order and distance.
- **Runs many searches at once.** Every keyword is searched in every location (2 keywords × 3 locations = 6 searches). The same job is saved only once, even if several searches find it.
- **Opens each job's detail page** (optional) to add seniority, employment type, industries, applicant count, Easy Apply flag and the description.
- **Resumes after interruptions.** Progress is saved in the run's key-value store, so a migrated or restarted run picks up where it stopped instead of starting over or saving duplicates.
- **Writes one row per job** in a format that matches other job-board and ATS scrapers. It's easy to merge, dedupe on `jobId` and load into a spreadsheet, CRM or database.

### Use cases

- **Recruiters and sourcers.** Track who is hiring for a role in a region, how fresh each posting is and how many people have applied.
- **Job boards and aggregators.** Fill a niche board (e.g. "remote data jobs") with fresh postings, then link out to the original posting.
- **Sales and market-signal tools.** Use hiring activity as a buying signal: companies hiring "Salesforce admin" or "data engineer" this week, by country and industry.
- **Labour-market research.** Compare demand, seniority mix and posted salary ranges across cities or titles over time.

### Quick start

Open the Actor, keep the pre-filled input (data analyst · United States · past week · 25 jobs) and click **Start**. Then:

1. Change **Keywords** and **Locations**. Use one entry per line, e.g. `product manager`, `Berlin, Germany`.
2. Optionally narrow the search with the **Filters**.
3. Set **Max jobs**. Leave **Open each job's detail page** on for full records, or turn it off for a fast list from the search cards only.

#### Input examples

One role, nationwide, last 7 days:

```json
{
  "keywords": ["data analyst"],
  "locations": ["United States"],
  "datePosted": "pastWeek",
  "maxItems": 25
}
```

Several roles × several cities, remote only, newest first, card fields only:

```json
{
  "keywords": ["data engineer", "analytics engineer"],
  "locations": ["Austin, TX", "Denver, CO", "Toronto, Ontario, Canada"],
  "datePosted": "past24h",
  "workplaceType": ["remote"],
  "sortBy": "recent",
  "includeDetails": false,
  "maxItems": 300,
  "maxItemsPerSearch": 50
}
```

Exact location using LinkedIn's `geoId` (103644278 = United States):

```json
{ "keywords": ["python developer"], "geoId": "103644278", "maxItems": 100 }
```

#### Output example

This is one real item from a test run on 30 Oct 2026. Descriptions are shortened here.

```json
{
  "platform": "linkedin",
  "jobId": "4485262266",
  "title": "Senior Data Analyst",
  "company": "Marathon Petroleum Corporation",
  "companySlug": "marathon-petroleum-company",
  "companyUrl": "https://www.linkedin.com/company/marathon-petroleum-company",
  "location": "Findlay, OH",
  "locations": ["Findlay, OH"],
  "country": "US",
  "workplaceType": null,
  "remote": null,
  "employmentType": "FULL_TIME",
  "employmentTypeRaw": "Full-time",
  "seniority": "Not Applicable",
  "industries": "Oil and Gas",
  "salary": null,
  "postedAt": "2026-10-29T00:00:00+00:00",
  "postedAtText": "1 day ago",
  "applicantCount": 87,
  "applicantCountText": "87 applicants",
  "applicantCountIsLowerBound": false,
  "applicantCountIsUpperBound": false,
  "easyApply": false,
  "jobUrl": "https://www.linkedin.com/jobs/view/4485262266/",
  "applyUrl": "https://www.linkedin.com/jobs/view/4485262266/",
  "applyUrlIsExternal": false,
  "descriptionText": "An exciting career awaits you\n\nAt MPC, we’re committed to being a great place to work – one that welcomes new ideas, enc…",
  "descriptionSnippet": "An exciting career awaits you …",
  "searchQuery": { "keywords": "data analyst", "location": "United States" },
  "sourceUrl": "https://www.linkedin.com/jobs-guest/jobs/api/seeMoreJobPostings/search?keywords=data%20analyst&location=United%20States&f_TPR=r86400&sortBy=R&start=0",
  "scrapedAt": "2026-10-31T00:20:16+00:00"
}
```

### Output fields

| Field | What it is | Source |
|---|---|---|
| `jobId` | LinkedIn job ID (unique key; duplicates across searches are skipped) | card |
| `title`, `company`, `companyUrl`, `companySlug` | Job title and the company's LinkedIn page | card |
| `location`, `locations` | Location exactly as LinkedIn shows it | card |
| `country` | ISO country code (e.g. `US`, `CA`, `GB`), worked out from the location text. Falls back to the search location for metro areas like "New York City Metropolitan Area". `null` if it can't be determined | derived |
| `workplaceType`, `remote` | `remote` / `hybrid` / `onsite` when LinkedIn shows it on the card (e.g. "(Remote)") or in the job title, or when you filter by exactly one workplace type. Otherwise `null` | card / filter |
| `postedAt`, `postedAtText` | Posted date (date only; LinkedIn gives no time of day) and LinkedIn's relative text, e.g. "2 hours ago" | card |
| `salary` | `{min, max, currency, interval, text}`. Only present when LinkedIn shows a salary | card / detail |
| `seniority`, `employmentType`, `employmentTypeRaw`, `industries` | From the job's criteria box | detail |
| `applicantCount`, `applicantCountText` | Number of applicants and LinkedIn's wording | detail |
| `applicantCountIsLowerBound` | `true` for "Over 200 applicants" (real number is higher) | detail |
| `applicantCountIsUpperBound` | `true` for "Be among the first 25 applicants" (real number is lower) | detail |
| `easyApply` | `true` = apply on LinkedIn, `false` = apply on the employer's site | detail |
| `applyUrl`, `applyUrlIsExternal` | Employer's apply URL when LinkedIn exposes it to signed-out visitors (`applyUrlIsExternal: true`); otherwise the LinkedIn job URL | detail |
| `descriptionHtml`, `descriptionText`, `descriptionSnippet` | Full description (HTML and plain text) and the first 300 characters | detail |
| `jobUrl`, `sourceUrl`, `searchQuery`, `scrapedAt` | Job page, the search page it came from, which keyword × location found it, and when | run |
| `department`, `team`, `updatedAt`, `jobFunction` | Kept for compatibility with other job datasets. LinkedIn's public pages currently don't show these, so expect `null` | – |

The run also saves a **RUN_SUMMARY** record with pages, cards, saved jobs and duplicates for each search, plus request and block counts.

### How many jobs per search?

LinkedIn's public search returns at most about **1,000 jobs per keyword × location**. For more, split the search: use more specific locations, narrower date ranges or extra keywords. Results from all searches are deduplicated.

### FAQ

**Do I need a LinkedIn account or cookies?** No. The Actor only reads pages that are public to signed-out visitors.

**Why use a residential proxy?** LinkedIn rate-limits data-centre IPs quickly. The default is Apify's residential proxy. Requests are sent one at a time with a pause in between (3 s by default, plus a random 0–2 s). A proxy IP is swapped when LinkedIn answers with a rate-limit or block page.

**Why is `applyUrl` usually the LinkedIn URL?** For jobs that apply on the employer's site, LinkedIn currently shows the employer's link only to signed-in members. Whenever the public page does include it, the Actor saves it and sets `applyUrlIsExternal: true`. `easyApply` still tells you whether the application happens on LinkedIn or off-site.

**Why are some fields empty?** Salary appears only when the employer published one. In a 75-job US test run, 6 jobs had a salary. `workplaceType` is filled only when LinkedIn labels it on the card, in the title, or through your filter. Turning off detail pages leaves the detail-only fields empty.

**Can I run it on a schedule?** Yes. Use Apify Schedules, e.g. daily with `datePosted: past24h`, and dedupe on `jobId` downstream.

**Can I get results through the API or in Google Sheets, Zapier or Make?** Yes, like any Apify Actor: through the API, webhooks or the standard integrations.

### Limits (honest version)

- Only data that LinkedIn shows to signed-out visitors is collected. There are no recruiter names, no contact details and no member profiles.
- There's a cap of about 1,000 results per search (LinkedIn's own limit).
- LinkedIn changes its pages from time to time, which can break parsing until the Actor is updated. The **RUN_SUMMARY** shows pages, cards and saved jobs per search, so a search that suddenly returns 0 cards is easy to spot.
- Without a proxy (or on a busy IP) LinkedIn answers with HTTP 429 quickly. The Actor backs off and retries, but the default residential proxy is strongly recommended.
- Large runs can still be rate-limited. Very aggressive settings (a short interval, many searches) increase blocks.

### Responsible use

This Actor reads publicly available pages. You are responsible for how you use it and the data it collects. That includes complying with LinkedIn's User Agreement and with privacy and data-protection laws that apply to you (e.g. GDPR or CCPA). Use the data for legitimate purposes and don't use it to spam people.

### Support

Found a bug or need a field? Open an issue on the Actor's **Issues** tab with the run ID, and it will be looked at.

# Actor input Schema

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

What to search for, e.g. 'data engineer' or 'product manager'. Each keyword is searched separately, once per location, so 2 keywords x 3 locations = 6 searches.

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

Where to search, as you would type it on LinkedIn: a country, region or city, e.g. 'United States', 'Berlin, Germany', 'Greater London'. Leave empty to search worldwide.

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

LinkedIn's numeric location ID (the geoId=... value in a LinkedIn jobs search URL), e.g. 103644278 for United States. Makes the location exact. Applied to every search.

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

LinkedIn's 'Date posted' filter (f_TPR).

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

LinkedIn's 'Job type' filter (f_JT). Pick one or more; empty = all.

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

LinkedIn's 'Experience level' filter (f_E). Empty = all.

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

LinkedIn's 'Remote' filter (f_WT). Empty = all. When exactly one is selected, every saved job gets that workplaceType.

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

Most relevant (LinkedIn's default) or most recent first.

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

Search radius around a city location. Leave empty for LinkedIn's default.

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

Stop after saving this many jobs in total across all searches. 0 = no limit (each search still stops at about 1,000 results, LinkedIn's own cap).

## `maxItemsPerSearch` (type: `integer`):

Cap for each keyword x location search, so one broad search doesn't use up the whole run. 0 = no cap.

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

Adds seniority, employment type, job function, industries, applicant count, Easy Apply flag and the description. Costs one extra request per job; turn off for a fast, card-only list (title, company, location, date, salary when shown).

## `includeDescription` (type: `boolean`):

Save the full description as HTML and plain text (needs 'Open each job's detail page'). A 300-character descriptionSnippet is saved either way when details are on.

## `skipClosed` (type: `boolean`):

Drop postings whose detail page says they are closed.

## `requestIntervalSecs` (type: `number`):

Minimum pause between two requests to LinkedIn (plus 0-2 s random jitter). Requests are never made in parallel. Lower values get rate-limited sooner.

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

LinkedIn rate-limits datacenter IPs quickly, so residential proxies are the default. Each proxy session keeps one IP for up to 30 requests and is replaced when LinkedIn answers with 429/999.

## Actor input object example

```json
{
  "keywords": [
    "data analyst"
  ],
  "locations": [
    "United States"
  ],
  "datePosted": "pastWeek",
  "sortBy": "relevance",
  "maxItems": 25,
  "maxItemsPerSearch": 0,
  "includeDetails": true,
  "includeDescription": true,
  "skipClosed": true,
  "requestIntervalSecs": 3,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

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

No description

## `jobsDetails` (type: `string`):

No description

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

No description

# 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": [
        "data analyst"
    ],
    "locations": [
        "United States"
    ],
    "datePosted": "pastWeek",
    "maxItems": 25,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("kingly_contest_7cb/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": ["data analyst"],
    "locations": ["United States"],
    "datePosted": "pastWeek",
    "maxItems": 25,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("kingly_contest_7cb/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": [
    "data analyst"
  ],
  "locations": [
    "United States"
  ],
  "datePosted": "pastWeek",
  "maxItems": 25,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call kingly_contest_7cb/linkedin-jobs-scraper --silent --output-dataset

```

## MCP server setup

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