# LinkedIn Job Description Scraper ⚡ Advanced Data, Best Value (`claygenius/linkedin-job-description-scraper`) Actor

Paste LinkedIn job URLs, get the full job description, criteria, applicant count and company info for each one. No login, no browser, one request per job.

- **URL**: https://apify.com/claygenius/linkedin-job-description-scraper.md
- **Developed by:** [Muhammad Shamshad Aslam](https://apify.com/claygenius) (community)
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

## 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 Job Description Scraper by URL

Paste LinkedIn job links, get the **full job description** and every detail LinkedIn shows on the public job page — title, company, location, seniority, employment type, function, industries, applicant count, salary when published, recruiter contact when shown, and the employer's company profile. No login, no browser, one request per job, so it is fast and very low cost per result.

Built as the companion to the LinkedIn Jobs Scraper: that one finds jobs from a search, this one takes job URLs you already have — from a spreadsheet, a CRM, an alert email, a Clay table, or another scraper — and fills in everything behind the link.

### Features

- 🔗 **Any LinkedIn job link format** — `linkedin.com/jobs/view/4419969671`, the long slugged version, regional hosts (`uk.linkedin.com`, `de.linkedin.com`), search and collection links with `currentJobId=…`, or just the numeric ID
- 📄 **Full description** — plain text (`descriptionText`) and the original HTML (`descriptionHtml`) with headings, lists and bold intact
- ✅ **Job criteria** — seniority level, employment type, job function, industries
- 💰 **Salary** — read from the job page, where LinkedIn actually publishes it
- 👤 **Job poster** — recruiter name, title and profile URL when the listing shows one
- 🏢 **Company profile** — website, **domain**, industry, employee count, headquarters, type, founding year, specialties, description. One cached request per company, so ten jobs at one employer cost one fetch. Switch off for the cheapest run
- ✂️ **Long-description splitting** — optional `descriptionChunks` array of up to 3 labelled parts ("Job description part 1 of 2: …") plus flat `descriptionPart1`–`descriptionPart3` fields, so descriptions over 8,000 characters fit Clay, Google Sheets and other per-cell limits
- 🚦 **Closed-job flag** — `isClosed` is `true` when LinkedIn shows "No longer accepting applications"
- ✅ **Standard field names** — `id`, `link`, `companyName`, `salaryInfo`, `postedAt`, `descriptionText`, `industries`, `applicantsCount`, `companyEmployeesCount`, `applyMethod`, identical to the LinkedIn Jobs Scraper so rows from both merge in one sheet
- ✅ **Duplicate-free** — the same job pasted twice is fetched once
- 🧾 **Failure report** — URLs that were removed, blocked or malformed are listed in the key-value store record `FAILED_URLS`, never silently dropped
- ✅ **No login required** — reads the public job page, so no account can get banned

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `jobUrls` | array | 3 example jobs | LinkedIn job URLs or IDs, one per line. Duplicates are fetched once |
| `fetchCompanyDetails` | boolean | `true` | Also fetch the employer's public LinkedIn page. Off = one request per job, fastest and cheapest |
| `chunkDescription` | boolean | `false` | Split the description into up to 3 labelled parts. Adds `descriptionChunks`, `descriptionPartsCount` and `descriptionPart1`–`3`. `descriptionText` stays in full |
| `chunkSize` | integer | `7000` | Max characters per part, label included. Only used when splitting is on |
| `proxyConfig` | proxy | Residential | Residential is the default; datacenter IPs get blocked quickly |

#### Example input

```json
{
  "jobUrls": [
    "https://www.linkedin.com/jobs/view/svp-chief-medical-officer-at-main-line-health-4447207474",
    "https://www.linkedin.com/jobs/view/4419969671",
    "https://www.linkedin.com/jobs/search/?currentJobId=4455931376&keywords=engineer"
  ],
  "fetchCompanyDetails": true,
  "chunkDescription": true
}
```

### Output

One record per unique job. Fields that LinkedIn does not show for a given job are `null`.

```json
{
  "id": "4419969671",
  "title": "Senior Software Engineer – Go (Golang)",
  "companyName": "General Motors",
  "companyLinkedinUrl": "https://www.linkedin.com/company/general-motors",
  "companyLogo": "https://media.licdn.com/dms/image/v2/.../company-logo_100_100",
  "location": "Warren, MI",
  "postedRelative": "1 week ago",
  "postedAt": "2026-09-17",
  "salaryInfo": "$120,000.00/yr - $180,000.00/yr",
  "link": "https://www.linkedin.com/jobs/view/4419969671",
  "applyUrl": "https://www.linkedin.com/jobs/view/4419969671/",
  "applyMethod": "external",
  "isClosed": false,
  "jobPosterName": "Jane Doe",
  "jobPosterTitle": "Technical Recruiter at General Motors",
  "jobPosterProfileUrl": "https://www.linkedin.com/in/jane-doe",
  "descriptionText": "Job Description\n\nWe are looking for a Senior Software Engineer …",
  "descriptionHtml": "<p><strong>Job Description</strong></p><p>We are looking for …</p>",
  "descriptionChunks": [
    "Job description part 1 of 2:\nJob Description\n\nWe are looking for a Senior Software Engineer …",
    "Job description part 2 of 2:\nBenefits\n\nGM offers a competitive …"
  ],
  "descriptionPartsCount": 2,
  "descriptionPart1": "Job description part 1 of 2:\nJob Description\n\nWe are looking for a Senior Software Engineer …",
  "descriptionPart2": "Job description part 2 of 2:\nBenefits\n\nGM offers a competitive …",
  "descriptionPart3": null,
  "seniorityLevel": "Mid-Senior level",
  "employmentType": "Full-time",
  "jobFunction": "Engineering and Information Technology",
  "industries": "Motor Vehicle Manufacturing",
  "applicantsCount": "153 applicants",
  "companyWebsite": "https://www.gm.com/",
  "companyDomain": "gm.com",
  "companyIndustry": "Motor Vehicle Manufacturing",
  "companyEmployeesCount": "10,001+ employees",
  "companyHeadquarters": "Detroit, Michigan",
  "companyType": "Public Company",
  "companyFounded": "1908",
  "companySpecialties": "Automotive, Electric Vehicles, and Autonomous Driving",
  "companyDescription": "General Motors is a global company …",
  "inputUrl": "https://www.linkedin.com/jobs/view/4419969671"
}
```

**Field notes**

- `postedAt` is derived from LinkedIn's relative wording (`1 week ago`), so it is approximate. `postedRelative` is exactly what LinkedIn shows.
- `descriptionChunks`, `descriptionPartsCount` and `descriptionPart1`–`descriptionPart3` only appear when `chunkDescription` is on. Parts are cut at paragraph or sentence boundaries, never mid-word. A description short enough for one part gives a single "part 1 of 1". Unused parts are `null`. In Clay, map `descriptionPart1`, `descriptionPart2`, `descriptionPart3` to three columns.
- `applyMethod` is `easy_apply` or `external`. LinkedIn hides the external ATS link from logged-out visitors, so `applyUrl` opens the job's apply flow on LinkedIn.
- Company fields are `null` when `fetchCompanyDetails` is off, when the employer has no LinkedIn page, or when the page is a university (`/school/`), which LinkedIn does not serve publicly.

### What counts as a result

Only jobs that were actually fetched are saved to the dataset and charged. Removed jobs (LinkedIn answers 404), blocked requests and inputs with no job ID cost nothing; they are listed in the key-value store record `FAILED_URLS` under `notFound`, `blocked`, `failed` and `invalid`, and summarised at the end of the log.

### Use cases

- **Enrich a job list** — you have links from alerts, a CRM or a search scraper; add the description, seniority and company data behind each one
- **Lead generation** — read the description to qualify the hiring need, then pair with a contact finder for the job poster or company
- **Applicant tooling** — pull the full text of postings for résumé matching, keyword analysis or cover-letter generation
- **Monitoring** — re-run on saved links to see which roles have closed (`isClosed`) and how applicant counts move
- **Clay, n8n, Make, Zapier** — pass a column of job URLs in, get structured fields back

### Tips

- Copy links from a **logged-out** LinkedIn tab or from any LinkedIn job scraper output; both work. Logged-in links also work, since only the numeric ID is used.
- Turn `fetchCompanyDetails` off when you only need the description; that halves the requests.
- Seeing "Cell data size exceeds limit" in Clay? Turn `chunkDescription` on and use the `descriptionPart` fields instead of `descriptionText`.
- Export as CSV, Excel or JSON from the dataset tab, or pull via the Apify API.

# Actor input Schema

## `jobUrls` (type: `array`):

One or more LinkedIn job URLs. Any format LinkedIn uses works: linkedin.com/jobs/view/4419969671, the long slugged version, a regional host (uk.linkedin.com), a search URL with currentJobId=..., or just the numeric job ID. Duplicates are fetched once.

## `fetchCompanyDetails` (type: `boolean`):

Also fetch each employer's public LinkedIn page for website, domain, industry, size, headquarters, founding year, specialties and description. One extra request per company (cached, so ten jobs at one employer cost one fetch). Turn off for the fastest, cheapest run.

## `chunkDescription` (type: `boolean`):

Adds descriptionChunks (an array of up to 3 labelled parts, e.g. "Job description part 1 of 2: …") and flat descriptionPart1, descriptionPart2, descriptionPart3 fields. Turn on for Clay, Google Sheets or any tool that rejects cells over ~8,000 characters. descriptionText is still included in full.

## `chunkSize` (type: `integer`):

Only used when splitting is on. Each part, including its label, stays under this size. 7000 leaves headroom under Clay's 8,000-character cell limit. Minimum 500.

## `proxyConfig` (type: `object`):

Residential proxies recommended. LinkedIn blocks datacenter IPs quickly on these endpoints.

## Actor input object example

```json
{
  "jobUrls": [
    "https://www.linkedin.com/jobs/view/4419969671",
    "https://www.linkedin.com/jobs/view/4455931376",
    "https://www.linkedin.com/jobs/view/4465023265"
  ],
  "fetchCompanyDetails": true,
  "chunkDescription": true,
  "chunkSize": 7000,
  "proxyConfig": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `jobDetails` (type: `string`):

Scraped job postings with title, company, location, full description, criteria and apply link

# 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 = {
    "jobUrls": [
        "https://www.linkedin.com/jobs/view/4419969671",
        "https://www.linkedin.com/jobs/view/4455931376",
        "https://www.linkedin.com/jobs/view/4465023265"
    ],
    "fetchCompanyDetails": true,
    "chunkDescription": true,
    "chunkSize": 7000,
    "proxyConfig": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("claygenius/linkedin-job-description-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 = {
    "jobUrls": [
        "https://www.linkedin.com/jobs/view/4419969671",
        "https://www.linkedin.com/jobs/view/4455931376",
        "https://www.linkedin.com/jobs/view/4465023265",
    ],
    "fetchCompanyDetails": True,
    "chunkDescription": True,
    "chunkSize": 7000,
    "proxyConfig": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("claygenius/linkedin-job-description-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 '{
  "jobUrls": [
    "https://www.linkedin.com/jobs/view/4419969671",
    "https://www.linkedin.com/jobs/view/4455931376",
    "https://www.linkedin.com/jobs/view/4465023265"
  ],
  "fetchCompanyDetails": true,
  "chunkDescription": true,
  "chunkSize": 7000,
  "proxyConfig": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call claygenius/linkedin-job-description-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,claygenius/linkedin-job-description-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/w0hRiMW5eb2fwfMFX/builds/tB8fQiHxP9u4IgWz4/openapi.json
