# LinkedIn Jobs Scraper — exact posting dates, no login (`henrycrawl/linkedin-jobs-scraper`) Actor

Scrape public LinkedIn job postings: title, company, location and the exact posting timestamp LinkedIn published, plus the full description on request. Job listings only, no member profiles. Hard cap on results and per-field coverage reported for every run.

- **URL**: https://apify.com/henrycrawl/linkedin-jobs-scraper.md
- **Developed by:** [Henry Crawl](https://apify.com/henrycrawl) (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 $5.00 / 1,000 job postings

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/platform/actors/running/actors-in-store#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

## LinkedIn Jobs Scraper — exact posting dates, no login

Scrape public LinkedIn job postings into typed rows. Every posting carries the exact
timestamp LinkedIn published it with, not a parsed "6 days ago" string, and the run
reports the fill rate of every field so you know the data is complete before you use it.

**Scope:** job listings only. This actor reads company-published postings from
LinkedIn's public job search. It does not touch member profiles, contact details, or
anything behind a login.

### What you get per posting

| Field | Example | Notes |
|---|---|---|
| `jobId` | `4437461678` | LinkedIn's own id, use it to deduplicate across runs |
| `title` | `Software Engineer, Early Career` | |
| `company` | `Notion` | |
| `companyUrl` | | LinkedIn company page |
| `companyLogoUrl` | | |
| `location` | `San Francisco, CA` | |
| `postedDate` | `2026-07-28T00:00:00.000Z` | **Exact timestamp from LinkedIn, not a relative string** |
| `postedDaysAgo` | `6` | Derived from the timestamp |
| `isNewPosting` | `true` | LinkedIn's own "new" marker |
| `benefitBadge` | `Be an early applicant` | LinkedIn's own badge when present |
| `jobUrl` | | Direct link to the posting |
| `searchKeyword`, `searchLocation`, `pageNumber`, `scrapedAt` | | Provenance of every row |

With **Include full job description** enabled, each row also carries:

| Field | Notes |
|---|---|
| `description` | Full posting text |
| `seniorityLevel`, `employmentType`, `jobFunction`, `industries` | LinkedIn's own criteria list |
| `applicantCountText` | Applicant count when LinkedIn shows it |

### Input

```json
{
  "keywords": ["software engineer", "data engineer"],
  "location": "United States",
  "datePosted": "last_week",
  "workplaceType": "remote",
  "experienceLevel": "mid_senior",
  "maxJobs": 1000
}
```

Filters: date posted, job type, experience level, workplace type (on-site / remote /
hybrid), sorting by relevance or date, plus an optional `geoId` when you need a more
precise location than the text field allows.

### Why this one is cheap to run

LinkedIn's public job search returns server-rendered HTML, so this actor needs no
browser at all. That keeps the cost per thousand rows far below a browser-based
scraper — which is why the price per result is what it is.

Enabling descriptions adds one request per posting, so it multiplies run cost. It is
off by default for that reason.

### The result cap is real

`maxJobs` stops the run at exactly that number, and duplicates are dropped on
LinkedIn's job id, so the same posting is never delivered — or billed — twice. The run
also stops on its own if three consecutive pages return nothing usable.

### Runs fail loudly, not silently

Every run writes a `DIAGNOSTICS` record with block rate, bytes per item and the fill
rate of every output field:

```json
{ "itemsExtracted": 500, "blockRate": "0%", "fieldCoverage": { "postedDate": "100%", "company": "100%" } }
```

If nothing was extracted, or more than half the pages were blocked, the run is marked
**failed** with the reason instead of handing you empty columns.

### Typical uses

- Tracking who is hiring for a role, and how fast postings turn over
- Feeding a niche job board or a recruiting pipeline
- Labour market research on posting volume by company, location and seniority
- Competitive hiring intelligence: which teams a company is building out

### Notes and limits

- **Salary is not available in LinkedIn's public job search.** Measured: the search
  fragment contains no compensation markup at all. When an employer publishes pay, it
  appears inside the description body, so enable descriptions if you need it. This actor
  does not invent an estimate.
- The public search returns a limited depth per query. For large harvests, split the
  work by location, seniority or date window rather than paging very deep.
- Keep `maxConcurrency` moderate; the endpoint rate limits per IP.

### Support

If a field stops filling or you need a filter that is not exposed, open an issue on the
actor page with the input you used.

# Actor input Schema

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

Each term runs as its own search using the filters below. Leave empty if you paste URLs instead.

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

Location string as typed on LinkedIn, for example "United States" or "Berlin, Germany".

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

Optional. LinkedIn's internal location id, more precise than the text location when you already know it.

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

Paste LinkedIn guest job search URLs. Scraped in addition to the terms above.

## `maxJobs` (type: `integer`):

The run stops at exactly this many postings. Enforced, so you cannot be billed for more rows than you asked for.

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

Fetches each posting's full description, seniority, employment type, job function and industries. This costs one extra request per posting, so it multiplies the price of a run.

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

Only include postings published within this window.

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

Employment type filter.

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

Seniority filter, same buckets LinkedIn uses.

## `workplaceType` (type: `string`):

On-site, remote or hybrid.

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

Relevance is LinkedIn's default ranking; date returns the newest postings first.

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

Requests in flight. This endpoint tolerates more than a browser-based scraper, but keep it moderate.

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

Defaults to Apify residential proxy. Datacenter groups work for small runs but get rate limited sooner.

## Actor input object example

```json
{
  "keywords": [
    "software engineer"
  ],
  "location": "United States",
  "startUrls": [],
  "maxJobs": 500,
  "includeDescription": false,
  "datePosted": "any",
  "jobType": "any",
  "experienceLevel": "any",
  "workplaceType": "any",
  "sortBy": "relevance",
  "maxConcurrency": 5,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

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

One row per posting: title, company, company URL and logo, location, exact posting timestamp, days since posted, salary when shown, and the posting URL. With descriptions enabled it also carries the full text, seniority, employment type, job function and industries.

## `diagnostics` (type: `string`):

Block rate, pages processed, bytes per item and the fill rate of every output field for this run.

# 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"
    ],
    "location": "United States"
};

// Run the Actor and wait for it to finish
const run = await client.actor("henrycrawl/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"],
    "location": "United States",
}

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

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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"
  ],
  "location": "United States"
}' |
apify call henrycrawl/linkedin-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=henrycrawl/linkedin-jobs-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/frEHzzyWuo1kbSZWi/builds/WMCTHNJijBEb1sfHJ/openapi.json
