# Advanced LinkedIn Job Search API (`insights_data/advanced-linkedin-job-search-api`) Actor

Scrape public LinkedIn jobs by search URL, job URL or title + location. Get title, company, salary, seniority, applicants, full description and AI insights (skills, experience, benefits) as clean JSON. 60 input options, live output, no login or cookies needed.

- **URL**: https://apify.com/insights_data/advanced-linkedin-job-search-api.md
- **Developed by:** [Insights Data](https://apify.com/insights_data) (community)
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.45 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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

### What is Advanced LinkedIn Job Search API?

**Advanced LinkedIn Job Search API** is a LinkedIn job scraper that collects public LinkedIn job postings from search links, single job links, or job titles and locations, and returns one structured record per job. On the free Apify plan, each run returns up to 10 jobs; on a paid plan, you can set up to 5,000 jobs per run. It's built for recruiters, job-market researchers, job-board builders and developers, and it doesn't need a LinkedIn account or login.

Despite the "API" in its name, this LinkedIn job scraper tool isn't an official LinkedIn API: it's an Apify Actor that you run in Apify Console or call through the [Apify API](https://docs.apify.com/api/v2).

To try it, type your job titles and locations, or paste a LinkedIn job search link, and click **Start**. The steps are in "How to scrape LinkedIn jobs" below.

- **Job market research:** count openings and compare skills, seniority and salary ranges by job title and country.
- **Hiring trends:** run the same search every week and see which roles and locations are growing.
- **Talent sourcing:** collect recruitment data on which companies are hiring for the roles you recruit for, and where.
- **LinkedIn job alerts:** schedule runs with a matching time range and get new postings as a dataset.
- **Job boards:** keep your own job board or job aggregator filled with fresh postings.
- **Salary benchmarking:** compare the salary ranges that employers publish in their postings.

#### What can Advanced LinkedIn Job Search API do?

- 🔗 **Search your way:** paste LinkedIn job search links in bulk, single job links or bare job IDs, or type job titles and locations. Links and job-title searches can run together in one run.
- ✅ **Filters:** LinkedIn's own search filters (time range, workplace type, employment type, experience level, Easy Apply, under 10 applicants, companies, industries, sort order), plus filters on title, location, description, company name, seniority, salary and posting date that run before a job is saved. A few company and category filters can't work on public data; see [Known issues](#known-issues).
- 📝 **Full job details:** the Actor opens each job for the full description, seniority, employment type and applicant count (when LinkedIn shows them), and adds the company's public LinkedIn description when it's available.
- 💲 **Description insights:** fixed text rules pull the salary range, years of experience, work arrangement, key skills, benefits and visa-sponsorship mentions out of each description.
- ⭐ **No duplicates in a run:** the same job found by several links or searches is saved once.
- ⬆️ **Live output:** each job is saved as soon as it's ready, so the dataset fills while the run is going.
- 🌐 **Apify platform:** export to JSON, CSV, Excel, XML or HTML, run it via API, schedule and monitor runs, and connect it to other apps.

***

### What data can Advanced LinkedIn Job Search API extract?

Each saved job is one record of LinkedIn job postings data: summary fields at the top, then nested blocks with the details. The top-level field names are the same for every job.

| | Field | What you get |
|---|---|---|
| 🔗 | `linkedin_job_id`, `url` | LinkedIn job ID and job link |
| 📝 | `title` | Job title as shown on LinkedIn |
| 👤 | `company_name`, `company_url` | Hiring company and its LinkedIn page |
| 📍 | `location_text` | Job location in plain words |
| 📅 | `posted_at` | Date the job was posted |
| 📝 | `employment_type`, `seniority` | For example `FULL_TIME`, `Mid-Senior level`. The top-level `employment_type` is the first value; the job record keeps the full list. |
| 🌐 | `workplace_type` | `On-site`, `Hybrid`, `Remote OK` (the description mentions remote work) or `Remote Solely` (remote only), read from the job description. Empty when the description doesn't say. |
| 📝 | `aggregator` | **Job record**, the main detail block: posting details, locations with derived city, region, country and time zone, salary, company fields and the plain-text description (`description_text`) |
| 👤 | LinkedIn job details (nested block) | Header caption, applicant count, employment type, experience level, job functions, industries, Easy Apply link, and the description as text or HTML |
| 💲 | `ai_insights` | Description insights: salary currency, minimum, maximum and period, years of experience, work arrangement, key skills, benefits, keywords, visa sponsorship and language |
| ⭐ | `scrape_meta` | Run details: collection time, run ID, matched search query and location, access level, time spent and a completeness score |

The "ai" in names like `ai_insights` and `aiWorkArrangementFilter` is historical. No AI model is used: the insights come from fixed text rules, so treat them as hints.

> **Note:** Fields stay empty (`null`) when LinkedIn doesn't show the value publicly; nothing is invented.
>
> - **LinkedIn salary data** comes from the salary in the posting's structured data or from amounts written in the description. A salary shown only on the search card isn't copied. A `$` amount in a description is read as USD, even for Canadian or Australian dollars.
> - **LinkedIn applicant count** appears only when LinkedIn shows it on the public job page.
> - `posted_at` is often empty for single job links.
> - The LinkedIn job details block is empty when the job page shows none of these extras.
> - The company description is often missing because LinkedIn doesn't always show company pages to visitors who aren't logged in.

#### What data can't Advanced LinkedIn Job Search API extract?

- Recruiter or hiring manager names, titles and profile links (always empty)
- Company headcount, size, industry, follower count and website (not public on LinkedIn job pages)
- Job categories, core responsibilities and requirements summaries (always empty)
- Job views and expiry dates (they stay empty without sign-in)
- Candidate or applicant profiles (never collected)

For company profiles with founders and open roles from the Y Combinator directory, try [Y Combinator Scraper](https://apify.com/insights_data/y-combinator-scraper).

***

### How to scrape LinkedIn jobs

You can run this LinkedIn jobs scraper from the input form without writing code.

1. [Create a free Apify account](https://console.apify.com/sign-up) using your email.
2. Open [Advanced LinkedIn Job Search API](https://apify.com/insights_data/advanced-linkedin-job-search-api).
3. Choose **one** way to search:
   - **By job titles and locations:** first delete the sample link in **LinkedIn job or job-search URLs**. Then type your titles in **Job titles / search queries** (for example `Data Analyst`) and your places in **Locations** (for example `United Kingdom`). If you keep the sample link, its search (Data Engineer jobs in the United States) runs first, next to your own searches, and can use up your jobs.
   - **By search link:** search on LinkedIn Jobs, set the filters you want, copy the address from your browser, and paste it in place of the sample link.
4. Leave **Maximum jobs per run** at 10 for a first test. If you search several titles or locations, also set **Maximum jobs per search** (see "How the job limit is shared between searches" below).
5. Click **Start**. Jobs appear in the **Output** tab while the run is going.
6. Download your data in JSON, CSV, Excel, XML or HTML.

**Recommended first run:** 10 jobs. It costs at most $0.035 in result charges, plus the $0.00005 start fee and platform usage. The run page then shows the full cost, which helps you plan bigger runs.

***

### LinkedIn job scraper pricing: how much does it cost to scrape LinkedIn jobs?

Advanced LinkedIn Job Search API uses **pay-per-event** pricing. The bill for one run has three parts:

| Part | Price | When it's charged |
|---|---|---|
| 💲 Job saved (result) | **$0.0035 per job** ($3.50 per 1,000 jobs) on the Free plan (lower on paid plans, see below) | For each job saved to the dataset |
| Actor start | $0.00005 per GB of run memory | Once per run: **$0.00005** at the default 1 GB |
| Apify platform usage | Based on the resources the run uses | Paid by you, on top of the event prices |

**What you know before a run:** the result charges (at most **Maximum jobs per run** × your plan's price per job, $0.0035 on the Free plan) and the start fee. Platform usage depends on the resources the run uses, so you see it on the run page in Apify Console only after the run. Do a small test run first, check its total on the run page, and use it as a guide for bigger runs.

**What counts as a result:**

- Only jobs saved to the dataset are charged.
- Jobs your filters remove are neither saved nor charged.
- If a job's detail page can't be read, the job is still saved with its search-card data and is charged.
- A job that fails completely is not saved and not charged.

**Example result charges** at the Free plan price (add the start fee and platform usage):

| Jobs saved | Result charges |
|---|---|
| 10 | $0.035 |
| 1,000 | $3.50 |
| 5,000 (maximum per run) | $17.50 |

The price per job depends on your Apify plan:

| Apify plan | Price per job | Price per 1,000 jobs |
|---|---|---|
| Free | $0.0035 | $3.50 |
| Starter | $0.00315 | $3.15 |
| Scale | $0.0028 | $2.80 |
| Business and higher | $0.00245 | $2.45 |

Paid Apify plans get a lower price per job. The plan discount applies to that price only. The start fee is the same on every plan, and platform usage is billed at your Apify plan's own rates.

If you set a maximum charge for a run, the run stops cleanly when it's reached and keeps the jobs already saved. See the [pricing tab](https://apify.com/insights_data/advanced-linkedin-job-search-api/pricing) for the current events.

#### Is scraping LinkedIn jobs free?

You can try it on the [Apify Free plan](https://apify.com/pricing), which includes $5 in monthly platform credit. That credit pays for both kinds of cost: the event charges (saved jobs and the start fee) and platform usage. Free-plan runs return up to 10 jobs each. There is no monthly cap: every run gets its own 10. If you set a higher limit, the Actor lowers it to 10 and says so in the log and in the run's status message. A free run of 10 jobs costs $0.035 in result charges plus the start fee and platform usage.

***

### LinkedIn job scraper input

No field is strictly required, and the form works without code. Give at least one link or job ID in `urls`, or job titles and locations in `jobTitles` and `locations`. If you leave every search field empty, the Actor runs one search for any job in any location, still limited by `limit` and `timeRange`.

The prefilled example searches Data Engineer jobs in the United States posted in the past week (the default `timeRange`) and takes up to 10 jobs:

```json
{
  "urls": [
    "https://www.linkedin.com/jobs/search/?keywords=Data%20Engineer&location=United%20States"
  ],
  "limit": 10
}
```

To search by job titles and locations instead, clear `urls`. This example runs two searches (Data Analyst in the United Kingdom, then Data Analyst in Germany) and takes up to 5 jobs from each:

```json
{
  "urls": [],
  "jobTitles": ["Data Analyst"],
  "locations": ["United Kingdom", "Germany"],
  "limit": 10,
  "maxItems": 5
}
```

See the [input tab](https://apify.com/insights_data/advanced-linkedin-job-search-api/input-schema) for all 60 fields.

#### Main fields

| Field (form label) | What it does |
|---|---|
| 🔗 `urls` (**LinkedIn job or job-search URLs**) | LinkedIn job search links (bulk, one per line), single job links, or a bare job ID with 8–12 digits. Non-LinkedIn links and LinkedIn links outside the jobs section are skipped and counted as "Skipped links" in the run summary. If none of the links can be used, the Actor runs a keyword search from your other fields instead (any job in any location if they're empty). |
| 📝 `jobTitles` (**Job titles / search queries**) | Search queries, sent to LinkedIn exactly as you type them. Each one runs once per location. |
| 📍 `locations` (**Locations**) | Locations for keyword searches (see below). |
| ⬇️ `limit` (**Maximum jobs per run**) | 10 to 5,000, default 10. Shared by all searches in the run. |
| ⬇️ `maxItems` (**Maximum jobs per search**) | Optional cap for each search link or each job title and location pair. Empty = one search can take the whole `limit`. |
| ⬇️ `page` (**Start page number**) | Result page to start from. Page 3 starts at job 51. |
| ⬇️ `sortBy` (**Sort by**) | `relevance` (default) or `date`. |
| 📅 `timeRange` (**Time range**) | How recent the jobs are: `1h`, `24h`, `7d` (default), `month`, `6m` or `any`. Use this field to set the period. |
| 📅 `postedLimit` (**Posted limit**) | An older way to set the same period. Leave it empty; if you set it, it overrides `timeRange`. |
| 📝 `fetchJobDetails` (**Collect full job details**) | On by default. Off gives faster runs with search-card data only: no description, seniority or applicant count, and only the ID and link for single job links. |
| 👤 `fetchCompanyEnrichment` (**Collect company details**) | On by default. Adds the company's public LinkedIn description when available. |
| 📝 `descriptionType` (**Description format**) | `text` (default) or `html`. See below. |
| 🌐 `proxyConfiguration` (**Proxy configuration**) | No proxy by default. See below. |

> ⚠️ Filling `jobTitles`, `titleSearch`, `locations`, `locationSearch`, `geoIds` or `company` starts keyword searches in addition to your links. Whatever you leave empty means "any job" or "any location" for those searches.

#### Maximum jobs per run (`limit`)

`limit` counts jobs **checked**, not jobs saved. Jobs that your filters remove, or that fail, still use up the limit, so a run can save fewer jobs than `limit` even when LinkedIn has more.

- ✅ Good: `50`
- ❌ Bad: `20000`

> ⚠️ The form accepts higher numbers, but any run that isn't capped by the free plan (for example, on a paid plan) fails with an "Input problem" message if `limit` is above 5,000. Free-plan runs are lowered to 10.

#### How the job limit is shared between searches

Searches run one after another, in this order:

1. Each link, in the order you pasted them. A single job link adds one job.
2. Each job title with every location: the first title in the first location, the first title in the second location, and so on, then the second title.

Each search takes jobs until it reaches `maxItems`, runs out of new results, or the run reaches `limit`. Without `maxItems`, the first search can use up the whole `limit`, and the later searches don't run.

To spread jobs evenly, set `maxItems` to `limit` divided by the number of searches. Example on the free plan: 2 job titles × 5 locations = 10 searches, so set `maxItems` to 1 and each search adds 1 job. A job that an earlier search already found isn't saved again, but it still counts toward the current search's `maxItems`. On the free plan, a `maxItems` above 10 is lowered to 10.

#### Search links

A pasted search link is the most precise input for a LinkedIn job search scraper: the Actor reuses the link's keyword, location and these settings: time range, experience level, employment type, workplace type, Easy Apply, under 10 applicants, industries, companies, sort order and start position. Settings the link doesn't have come from the input fields. A time filter in your link (one of LinkedIn's standard periods: past hour, 24 hours, week, month or 6 months) wins over `timeRange` for that link.

For the location of a link, the Actor uses the link's own location ID first, then the link's location text, then the first entry of `geoIds`. The `locations` field doesn't apply to search links.

#### Locations and geo IDs

For keyword searches, each location text is handled in this order:

1. If LinkedIn recognizes the text (its first location suggestion), that LinkedIn location is used.
2. If LinkedIn doesn't recognize it and you filled `geoIds`, the **first** geo ID is used instead and the text isn't sent.
3. Otherwise, the text is searched as plain text.

A location written as digits is used directly as a LinkedIn location ID. With no location text, the first geo ID is used. Only the first geo ID is ever used; the others are ignored.

- ✅ Good: `United Kingdom`
- ❌ Bad: `UK`

> ⚠️ Abbreviations can match the wrong place. Use clear location names or a single geo ID, not both.

#### Companies (`company`)

Enter up to 10 company **names**. Each name is matched to LinkedIn's first company suggestion and used as a search filter. Names that LinkedIn doesn't suggest are not used. If no name matches, the searches run with no company restriction.

- ✅ Good: `Deloitte`
- ❌ Bad: a LinkedIn company link

> ⚠️ Enter company names only. Company links are not used as a filter. Filling `company` also starts a keyword search, even when you paste links.

#### Salary filter (`salary`)

Use ranges like `100k+` or `80k-120k`; a job passes if it matches any of them. Numbers are always read as thousands. A job is removed when its highest known salary value is outside every range. There's no currency or pay-period conversion, so hourly salaries are compared as raw numbers.

Jobs with no known salary are kept, so this filter alone doesn't guarantee a salary in every row. To keep only jobs with a salary, also turn on **Has salary** (`hasSalary`).

- ✅ Good: `80k-120k`
- ❌ Bad: `80000-120000`

#### LinkedIn job search filters

These are sent to LinkedIn with each search:

- `timeRange`, `sortBy`, `page`
- `workplaceType` (LinkedIn's own values `on_site`, `remote`, `hybrid`): pick `remote` to find remote jobs. The output field `workplace_type` uses different values because it's read from the description.
- `employmentType`, `experienceLevel`
- `easyApply`: LinkedIn Easy Apply jobs only
- `under10Applicants`: jobs with fewer than 10 applicants
- `company`, `industryIds` (LinkedIn industry codes), `geoIds`

#### Filters applied after the search

These run on each job before it's saved. Removed jobs aren't saved or charged, but they count toward `limit`. The search and exclusion lists for title, location, description and organization are case-insensitive and match anywhere in the text; add `:*` to a term for prefix matching (`Soft:*` matches "Software"). The slug filters need an exact slug.

- **Title:** `titleSearch`, `titleExactMatch`, `titleExclusionSearch`
- **Location:** `locationSearch`, `locationExclusionSearch`, `hasNoLocation`
- **Description:** `descriptionSearch`, `descriptionExclusionSearch` (title + description; not applied when `timeRange` is `6m`)
- **Company:** `organizationSearch`, `organizationExclusionSearch`, `organizationSlugFilter` (keeps only jobs whose company slug is known), `organizationSlugExclusionFilter`
- **Job details:** `seniorityFilter`, `functionFilter`, `aiEmploymentTypeFilter` (normalized employment type such as `FULL_TIME`), `salary`, `hasSalary`, `directApply`, `noDirectApply`
- **Dates:** `datePostedAfter`, `postedBeforeDate` (exact date or relative period such as `7 days`; jobs without a known posting date are kept)
- **Description insights:** `aiWorkArrangementFilter` (`On-site`, `Hybrid`, `Remote OK`, `Remote Solely`), `aiExperienceLevelFilter`, and `aiVisaSponsorshipFilter` for visa sponsorship jobs (jobs whose description mentions visa sponsorship)

> ⚠️ `titleSearch` (**Title search**) and `locationSearch` (**Location search**) do two things: they keep only jobs whose title or location matches, and they also add extra searches (title search entries run like job titles, location search entries like locations). Those extra searches use up `limit`.

> ⚠️ `hasNoLocation` can't be combined with `locations`, `locationSearch` or `geoIds`; the run fails with an "Input problem" message. `functionFilter` and `directApply` depend on details LinkedIn often hides, so they may remove all jobs. Some company and category filters don't work on public data at all; see [Known issues](#known-issues).

#### Description format (`descriptionType`)

`html` changes only the description inside the nested LinkedIn job details block (plain text or HTML there). The main description in `aggregator.description_text` is always plain text. When the LinkedIn job details block is empty, this setting has no visible effect.

#### Proxy (`proxyConfiguration`)

By default, no proxy is used. If LinkedIn blocks the run, the Actor switches to Apify Proxy automatically when your Apify account can use it. If you pick a proxy here, it's used first. For what happens when Apify Proxy isn't available, see "What happens if LinkedIn blocks the run?" in the FAQ.

***

### LinkedIn job scraper output

Results are stored in a dataset in the **Output** or **Storage** tab. You can view them as a table or JSON, pick only the fields you need, and download them in JSON, CSV, Excel, XML or HTML. To export LinkedIn jobs to Excel, open the dataset, click **Export** and choose Excel. All saved jobs are also stored as one JSON file in the run's key-value store under the `OUTPUT` key.

The Output tab has seven views: **Overview**, **Salary & AI insights**, **Company**, **Location**, **Applicants & apply**, **Description** and **Run details**. The full records are available too. The insights in the salary view come from the same text rules as `ai_insights`.

#### Export to a spreadsheet

- To work with LinkedIn job market data in a spreadsheet, start with the top-level fields (the **Overview** columns): one column per field, ready to sort and filter.
- In a CSV or Excel export of the full records, every nested field gets its own column named with its path, for example `aggregator/description_text` or `ai_insights/salary_min`. Lists get numbered columns, such as `ai_insights/key_skills/0`. Full records therefore have many columns.
- To keep the file small, pick only the fields you need in the export options, or download a single view through the API (for example `format=csv` with `view=overview`).

#### JSON example

A shortened record for the first input example above. The values are illustrative, and the LinkedIn job details block is left out:

```json
{
  "linkedin_job_id": "4475178472",
  "title": "Data Engineer III",
  "company_name": "Deloitte",
  "company_url": "https://www.linkedin.com/company/deloitte",
  "url": "https://www.linkedin.com/jobs/view/4475178472",
  "location_text": "Greater Indianapolis",
  "posted_at": "2026-10-07",
  "employment_type": "FULL_TIME",
  "workplace_type": null,
  "seniority": "Not Applicable",
  "aggregator": {
    "title": "Data Engineer III",
    "organization": "Deloitte",
    "salary": {
      "currency": "USD",
      "value": { "minValue": 105400.0, "maxValue": 207800.0, "unitText": "YEAR" }
    },
    "employment_type": ["FULL_TIME"],
    "locations_derived": ["Greater Indianapolis"],
    "seniority": "Not Applicable",
    "ai_experience_level": "5-10",
    "ai_key_skills": ["Data Engineering", "ETL"],
    "ai_job_language": "English",
    "description_text": "Our Deloitte AI & Engineering team works to transform technology platforms..."
  },
  "ai_insights": {
    "salary_currency": "USD",
    "salary_min": 105400.0,
    "salary_max": 207800.0,
    "salary_period": "YEAR",
    "experience_level": "5-10",
    "work_arrangement": null,
    "key_skills": ["Data Engineering", "ETL"],
    "visa_sponsorship": null,
    "language": "English"
  },
  "scrape_meta": {
    "scraped_at": "2026-10-09T10:00:00+00:00",
    "matched_query": "Data Engineer",
    "matched_location": "United States",
    "auth_level": "anonymous",
    "completeness_score": 0.356
  }
}
```

- `scraped_at` is the run's start time, so it's the same for every job in a run.
- `completeness_score` is the share of fields in the job record and the LinkedIn job details block that have a value, from 0 to 1. Higher means more fields are filled. Many company and recruiter fields can't be filled from public pages, so scores well below 1 are normal. Use it to compare jobs, not as a quality grade.

***

### Tips and advanced usage

#### How many results can you scrape with Advanced LinkedIn Job Search API?

You can set up to 5,000 jobs per run (10 on the free plan). A run stops taking new jobs when any of these happens:

- `limit` is reached (jobs checked across the whole run).
- `maxItems` is reached for one search; that search stops and the next one starts.
- LinkedIn stops returning new jobs for a search.
- A search keeps failing; it stops with a warning in the log and the other searches continue.
- An unexpected problem stops the searching; jobs found before it are saved and the run still finishes.
- Your maximum charge for the run is reached; the jobs already saved are kept.
- On the free plan, 10 jobs are reached.
- The input has a problem (`limit` above 5,000 on a run that isn't free-capped, or `hasNoLocation` combined with `locations`, `locationSearch` or `geoIds`); the run fails before searching.

The exact number also depends on how many jobs LinkedIn has for your search and time range, and on how many your filters remove. The best way to know for your use case is a quick test run.

#### How to get faster or cheaper results

- Put your conditions into LinkedIn-side filters (in your link or in `workplaceType`, `experienceLevel` and similar fields) instead of only post-search filters, so `limit` isn't spent on jobs that get filtered out.
- Use `maxItems` to share the run between several searches.
- Turn off `fetchJobDetails` when search-card data is enough; runs are faster, but descriptions and insights stay mostly empty.
- Turn off `fetchCompanyEnrichment` if you don't need company descriptions.

#### Set up LinkedIn job alerts with schedules

Create an [Apify schedule](https://docs.apify.com/platform/schedules) and match `timeRange` to it, for example every day with `24h`. Duplicates are removed only within a run, so overlapping time ranges across runs can return the same job again; use `linkedin_job_id` to remove them on your side. The Actor doesn't send emails itself; connect an integration for notifications.

#### Known issues

Some filters rely on data LinkedIn doesn't show publicly. Don't use them:

- **Remove every job:** `organizationSizeFilter`, `recruiterOnly`, `industryFilter`, `aiTaxonomiesFilter`, `aiTaxonomiesPrimaryFilter`, and `organizationEmployeesGte` or `minCompanyFollowers` above 0.
- **Remove nothing:** `removeAgency`, `aiTaxonomiesExclusionFilter`, `excludeATSDuplicate`, `industryExclusionFilter`, and `organizationEmployeesLte` (unknown headcount counts as 0, so every job passes).
- **English only:** the language check only detects English. With `aiLanguageFilter`, only jobs detected as English can pass; any other language value matches no job.

Other known issues:

- A `$` salary in a description is read as USD, even for Canadian or Australian dollars.
- `posted_at` is often empty for single job links, and date filters keep jobs without a date.
- `descriptionSearch` and `descriptionExclusionSearch` are ignored when `timeRange` is `6m`.
- The derived time zone is one representative time zone per country, and only for countries the Actor recognizes.
- Company descriptions are often unavailable because LinkedIn doesn't always show company pages to visitors who aren't logged in.
- `jobTitles` text is sent to LinkedIn unchanged, so how special characters or operators behave is up to LinkedIn's search.

***

### Integrations and API

#### Can I use it as a LinkedIn job listings API?

Yes, through the Apify API. It isn't an official LinkedIn jobs API, but you can start runs, schedule them and fetch the results as JSON from your own code, with the same JSON input you'd use in Console. Use the `apify-client` [NPM package](https://apify.com/insights_data/advanced-linkedin-job-search-api/api/javascript) for Node.js or the `apify-client` [PyPI package](https://apify.com/insights_data/advanced-linkedin-job-search-api/api/python) for Python. The [API tab](https://apify.com/insights_data/advanced-linkedin-job-search-api/api) has ready-to-use code.

#### LinkedIn job scraper Python example

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")
run = client.actor("insights_data/advanced-linkedin-job-search-api").call(run_input={
    "urls": [],
    "jobTitles": ["Data Analyst"],
    "locations": ["United Kingdom", "Germany"],
    "limit": 10,
    "maxItems": 5,
})

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["title"], item["company_name"], item["location_text"])
```

A LinkedIn job scraper API call like this one waits for the run to finish and returns the run details; `defaultDatasetId` points to the saved jobs.

#### Can I integrate Advanced LinkedIn Job Search API with other apps?

Yes. Connect this LinkedIn job data scraper with almost any cloud service through [Apify integrations](https://apify.com/integrations): **Make, Zapier, Slack, Google Sheets, Google Drive, Airbyte, GitHub, LangChain** and more. Use [webhooks](https://docs.apify.com/integrations/webhooks) to trigger actions when a run finishes.

Example workflow: schedule a daily run with `timeRange` set to `24h`, then send the new jobs to a Google Sheet so your recruiting team has up-to-date LinkedIn hiring data.

#### LinkedIn job scraper MCP: can AI agents use it?

Yes. Connect Advanced LinkedIn Job Search API to Claude and other AI assistants through the Apify MCP server, so they can start runs and read LinkedIn job data from the results. Open the [MCP tab](https://apify.com/insights_data/advanced-linkedin-job-search-api/api/mcp) for setup steps.

***

### Other job scrapers

Need more sources than a LinkedIn job board scraper? These Actors from the same author cover other job and company sources:

| Actor | What it does |
|---|---|
| 🔗 [Welcome to the Jungle Jobs Scraper](https://apify.com/insights_data/welcome-to-the-jungle-jobs-scraper) | Scrapes Welcome to the Jungle (WTTJ) jobs by keyword, location, salary, remote or direct URLs: titles, companies, salaries, skills and apply links, with no login. |
| 🔗 [Y Combinator Scraper](https://apify.com/insights_data/y-combinator-scraper) | Extracts company profiles, founders, open roles, press and Launch YC posts from Y Combinator's public directory. |

***

### ❓ FAQ

#### Is it legal to scrape LinkedIn jobs?

It depends on where you are, what you collect and how you use it; this isn't legal advice. The Actor reads only public job postings and doesn't log in. Job postings can still contain personal data, such as a recruiter's name in the description, and laws like the GDPR protect that data. LinkedIn's [User Agreement](https://www.linkedin.com/legal/user-agreement) also restricts using software, scripts or bots to scrape or copy its services, so review it and ask a lawyer if you're unsure.

#### How does Advanced LinkedIn Job Search API work?

It's a LinkedIn job crawler that works like a visitor browsing public LinkedIn job pages, only faster:

1. It reads your input and applies the free-plan cap if you're on the free plan.
2. It sorts your links into search links and job links and skips the ones it can't use.
3. It matches location texts and company names to LinkedIn's suggestions.
4. It pages through each search link, then every job title and location pair, and drops jobs it has already seen in the run.
5. It opens each job's public page for the details (with **Collect full job details** on) and the company's public page (with **Collect company details** on). Both are on by default.
6. It reads the description with built-in rules to fill the insights.
7. It applies your filters, saves each job that passes and charges one result for it.
8. It prints a run summary in the log: jobs saved, charged, checked, filtered out and failed, skipped links and searches run.

#### Is this a LinkedIn job web scraper or an official API?

It's a scraper, not an official API. It runs as an Apify Actor, and LinkedIn doesn't make or endorse it.

#### Does LinkedIn have a job search API?

The jobs API that LinkedIn documents is for posting jobs, not for searching them. LinkedIn's [Job Posting API](https://learn.microsoft.com/en-us/linkedin/talent/job-postings/api/overview) lets authorized third parties, such as applicant tracking systems and job distributors, post jobs to LinkedIn. Its use is restricted to developers approved by LinkedIn, and LinkedIn says it is currently not accepting new partnerships for it. If you need a LinkedIn job search API for reading public listings, call this Actor through the Apify API instead.

#### How is Advanced LinkedIn Job Search API different from LinkedIn's Job Posting API?

They do opposite jobs: LinkedIn's Job Posting API posts jobs to LinkedIn, while this LinkedIn job posting scraper reads public job listings from it.

| | LinkedIn Job Posting API | Advanced LinkedIn Job Search API |
|---|---|---|
| What it's for | Posting jobs to LinkedIn on behalf of customers | Collecting public LinkedIn job listings as data |
| Who can use it | Developers approved by LinkedIn; not accepting new partnerships | Anyone with an Apify account |
| Setup | API agreement with LinkedIn and OAuth | No LinkedIn account; run it in Console or via the Apify API |
| Price | Not listed on LinkedIn's overview page | $3.50 per 1,000 saved jobs on the Free plan, down to $2.45 on Business, plus the start fee and platform usage |

#### Can I get LinkedIn jobs without login?

Yes. The Actor needs no LinkedIn account and reads only what any visitor can see. Data shown only to signed-in users, such as job views, stays empty.

#### Can I use it as a LinkedIn job extractor for single job links?

Yes. Paste job links (`https://www.linkedin.com/jobs/view/…`) or bare job IDs in **LinkedIn job or job-search URLs**. Each one adds that job directly, with no search. Keep **Collect full job details** on: with it off, only the job ID and link are known for single job links.

#### Can it work as a LinkedIn job description scraper?

Yes. With **Collect full job details** on (the default), the full description is in `aggregator.description_text` as plain text. Set **Description format** to `html` to get HTML in the LinkedIn job details block when that block is filled.

#### What happens if LinkedIn blocks the run?

The Actor first tries Apify Proxy automatically. If your Apify account can't use it, the run continues on its current connection:

- A search that LinkedIn keeps refusing stops with a warning in the log, and the Actor moves on to the next search.
- If LinkedIn keeps refusing job pages, the Actor stops opening them for the rest of the run. Those jobs are still saved with their search-card data (and charged), so descriptions and details stay empty.
- The run still finishes and keeps every job saved so far.

If your account has access to Apify Proxy, you can also pick it in **Proxy configuration** so the run starts with it.

#### Why am I getting fewer results than expected?

Check the run summary in the log first. Common causes:

- `limit` counts jobs checked, so filtered-out and failed jobs use it up.
- The searches that ran first used up `limit`; set `maxItems` to share it (see "How the job limit is shared between searches").
- Your filters removed jobs ("Filtered out by your filters" in the summary). Some filters remove every job; see [Known issues](#known-issues).
- LinkedIn has no more new jobs for your search and time range. The default `timeRange` is the past week.
- `maxItems` capped one or more searches.
- A search kept failing and stopped; look for a warning in the log.
- An unexpected problem stopped the searching; the log says so, and the jobs found before it are saved.
- The same job was found by several searches and saved once.
- You're on the free plan (10 jobs per run), or the run reached your maximum charge.

#### What do the run status messages mean?

- **"Done – N job(s) saved in Xs."** The run finished normally.
- **"Done – N job(s) saved." followed by a free-plan note:** the run was on the free plan, which allows up to 10 jobs per run, and either you asked for more than 10 or the run reached 10.
- **"Finished – no job matched your search and filters."** The run succeeded but saved no jobs: LinkedIn returned none, your filters removed all of them, or none could be processed.
- **"Spend limit reached – N job(s) saved."** Your maximum charge stopped the run; the saved jobs are kept.
- **"Input problem: …"** The run failed before searching because `limit` is outside 10–5,000 on a run that isn't free-capped, or `hasNoLocation` is combined with `locations`, `locationSearch` or `geoIds`.

***

### Support and feedback

Found a bug or want a new field or filter? Please create an issue on the [Issues tab](https://apify.com/insights_data/advanced-linkedin-job-search-api/issues/open).

Need a **custom solution** or a different data source? Contact us at hello.insights.data@gmail.com.

Built and maintained by [insights_data](https://apify.com/insights_data).

# Actor input Schema

## `timeRange` (type: `string`):

Only jobs posted within this period. Run the Actor on a schedule that matches the range (e.g. every day with 24 hours) to catch every new job without duplicates.

## `limit` (type: `integer`):

Maximum number of jobs collected across the whole run (all links, titles and locations together). Minimum 10, maximum 5,000. Free plan users get up to 10 jobs per run; paid Apify plans have no limit.

## `urls` (type: `array`):

One or more LinkedIn links (bulk input supported, one per line). Accepts job search links (e.g. https://www.linkedin.com/jobs/search/?keywords=Data%20Engineer\&location=United%20States) with their keyword, location and filter settings (settings missing from a link, such as the time range, come from the fields below), and single job links (e.g. https://www.linkedin.com/jobs/view/4475178472). A bare job ID also works.

## `jobTitles` (type: `array`):

Search queries sent to LinkedIn job search, e.g. 'Data Engineer'. Boolean operators are supported. Each query runs once per location.

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

Locations such as 'San Francisco' or 'United Kingdom'. Each one is matched to LinkedIn's first location suggestion, so write 'United Kingdom' rather than 'UK' (which LinkedIn reads as Ukraine).

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

Optional cap per search (each search link, or each job title and location pair). Leave empty to use the run maximum above.

## `page` (type: `integer`):

Result page to start from. Each page counts as 25 jobs, so page 3 starts at job 51.

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

Order of the search results.

## `postedLimit` (type: `string`):

Alternative way to set the time range. When set, it replaces the time range above.

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

Select one or more workplace types.

## `employmentType` (type: `array`):

Select one or more employment types.

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

Select one or more experience levels.

## `salary` (type: `array`):

Salary ranges such as '100k+' or '80k-120k'. Jobs with a known salary outside every range are removed; jobs without salary information are kept.

## `company` (type: `array`):

Company names or LinkedIn company URLs (up to 10). Names are matched to LinkedIn's first company suggestion.

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

Only jobs with fewer than 10 applicants.

## `easyApply` (type: `boolean`):

Only jobs that offer LinkedIn Easy Apply.

## `industryIds` (type: `array`):

LinkedIn industry codes (up to 20), e.g. 4 for Software Development.

## `geoIds` (type: `array`):

LinkedIn location IDs (up to 10), e.g. 103644278 for the United States. Used when no location text is given.

## `titleSearch` (type: `array`):

Keep only jobs whose title contains one of these terms. They are also searched as extra queries. Use :\* for prefix matching ('Soft:\*' matches 'Software', 'Softball').

## `titleExactMatch` (type: `boolean`):

Title search must match the whole job title exactly instead of a part of it.

## `titleExclusionSearch` (type: `array`):

Remove jobs whose title contains one of these terms. Use :\* for prefix matching.

## `locationSearch` (type: `array`):

Keep only jobs whose location contains one of these phrases, e.g. 'London, England, United Kingdom' or 'New York, New York, United States'. Use full English names, no abbreviations. Use :\* for prefix matching ('New:\*'). They are also searched as extra locations.

## `locationExclusionSearch` (type: `array`):

Remove jobs whose location contains one of these phrases. Use :\* for prefix matching.

## `descriptionSearch` (type: `array`):

Keep only jobs whose title or description contains one of these terms. Use :\* for prefix matching. Not applied with the 6-month time range.

## `descriptionExclusionSearch` (type: `array`):

Remove jobs whose title or description contains one of these terms. Use :\* for prefix matching. Not applied with the 6-month time range.

## `organizationSearch` (type: `array`):

Keep only jobs from organizations whose name contains one of these terms. Use :\* for prefix matching ('Google:\*').

## `organizationExclusionSearch` (type: `array`):

Remove jobs from organizations whose name contains one of these terms. Use :\* for prefix matching.

## `organizationSlugFilter` (type: `array`):

Keep only jobs from these LinkedIn company slugs (exact match). The slug is the part after /company/ in the URL, e.g. 'tesla-motors'. Only jobs whose company slug is known are kept.

## `organizationSlugExclusionFilter` (type: `array`):

Remove jobs from these LinkedIn company slugs (exact match).

## `industryFilter` (type: `array`):

Keep only jobs from companies in these LinkedIn industries (exact names). LinkedIn rarely shows the company industry publicly, so this filter may remove all jobs.

## `industryExclusionFilter` (type: `array`):

Remove jobs from companies in these LinkedIn industries (exact names).

## `organizationSizeFilter` (type: `array`):

Keep only jobs from companies in these size buckets. LinkedIn rarely shows the company size publicly, so this filter may remove all jobs.

## `organizationEmployeesGte` (type: `integer`):

Keep only jobs from companies with at least this many employees. LinkedIn rarely shows the headcount publicly and unknown counts as 0, so a value above 0 may remove all jobs.

## `organizationEmployeesLte` (type: `integer`):

Keep only jobs from companies with at most this many employees.

## `minCompanyFollowers` (type: `integer`):

Keep only jobs from companies with at least this many LinkedIn followers. LinkedIn rarely shows the follower count publicly and unknown counts as 0, so a value above 0 may remove all jobs.

## `removeAgency` (type: `boolean`):

Remove jobs from companies recognised as staffing or recruiting agencies.

## `recruiterOnly` (type: `boolean`):

Keep only jobs that include a link to the recruiter's LinkedIn profile. Recruiter profiles are not shown publicly, so this filter may remove all jobs.

## `seniorityFilter` (type: `array`):

Keep only jobs with these seniority levels (as shown on LinkedIn in English).

## `functionFilter` (type: `array`):

Keep only jobs with one of these LinkedIn job functions, e.g. 'Engineering' or 'Information Technology'. The job function is often not shown publicly, so this filter may remove all jobs.

## `datePostedAfter` (type: `string`):

Keep only jobs posted on or after this point. Pick an exact date (e.g. 2026-01-01, UTC) or a relative period such as '7 days' (= posted in the last 7 days), '2 weeks', '1 month' or '12 hours'.

## `postedBeforeDate` (type: `string`):

Keep only jobs posted on or before this point. Pick an exact date (e.g. 2026-01-31, UTC) or a relative period such as '3 days' (= posted more than 3 days ago), '1 week' or '1 month'.

## `directApply` (type: `boolean`):

Keep only jobs marked as LinkedIn Easy Apply. The Easy Apply mark is often not shown publicly, so this filter may remove all jobs; the Easy Apply search filter above is more reliable.

## `noDirectApply` (type: `boolean`):

Remove jobs marked as LinkedIn Easy Apply.

## `hasSalary` (type: `boolean`):

Keep only jobs with salary information (from the posting or found in the description).

## `hasNoLocation` (type: `boolean`):

Keep only jobs without a known location. Do not combine with locations, location search or geo IDs.

## `aiWorkArrangementFilter` (type: `array`):

Keep only jobs whose description points to this work arrangement. Remote OK = remote with an office available, Remote Solely = remote only.

## `aiExperienceLevelFilter` (type: `array`):

Keep only jobs whose description asks for this many years of experience.

## `aiEmploymentTypeFilter` (type: `array`):

Keep only jobs with one of these normalized employment types.

## `aiVisaSponsorshipFilter` (type: `boolean`):

Keep only jobs whose description mentions visa sponsorship.

## `aiLanguageFilter` (type: `array`):

Keep only jobs whose description is in one of these languages, e.g. English.

## `aiTaxonomiesFilter` (type: `array`):

Keep only jobs in these categories, e.g. Technology. Job categories are not filled by the built-in extraction, so this filter removes all jobs when used.

## `aiTaxonomiesPrimaryFilter` (type: `array`):

Keep only jobs whose main category is one of these. Job categories are not filled by the built-in extraction, so this filter removes all jobs when used.

## `aiTaxonomiesExclusionFilter` (type: `array`):

Remove jobs in any of these categories. Job categories are not filled by the built-in extraction, so this filter currently removes nothing.

## `excludeATSDuplicate` (type: `boolean`):

Kept for compatibility with other job APIs. This Actor does not compare jobs with career-site datasets, so the duplicate flag is always empty and this option removes nothing.

## `descriptionType` (type: `string`):

Format of the job description in the output: plain text or HTML.

## `populateAiRemoteLocation` (type: `boolean`):

Copy the job's location into ai_remote_location when that field is empty.

## `populateAiRemoteLocationDerived` (type: `boolean`):

Copy the job's location into ai_remote_location_derived when that field is empty.

## `fetchJobDetails` (type: `boolean`):

Open every job for its full description, seniority, employment type and applicant count. Turn off for faster runs with search-card data only.

## `fetchCompanyEnrichment` (type: `boolean`):

Add the company's LinkedIn description when it is publicly available.

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

By default no proxy is used. If LinkedIn blocks the run, it switches to a datacenter proxy and then to a residential proxy automatically. Pick a proxy here to start with it instead.

## Actor input object example

```json
{
  "timeRange": "7d",
  "limit": 10,
  "urls": [
    "https://www.linkedin.com/jobs/search/?keywords=Data%20Engineer&location=United%20States"
  ],
  "page": 1,
  "sortBy": "relevance",
  "under10Applicants": false,
  "easyApply": false,
  "titleExactMatch": false,
  "removeAgency": false,
  "recruiterOnly": false,
  "directApply": false,
  "noDirectApply": false,
  "hasNoLocation": false,
  "excludeATSDuplicate": false,
  "descriptionType": "text",
  "populateAiRemoteLocation": false,
  "populateAiRemoteLocationDerived": false,
  "fetchJobDetails": true,
  "fetchCompanyEnrichment": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `overview` (type: `string`):

Job ID, title, company, location, posting date, employment type, workplace, seniority and links.

## `salaryInsights` (type: `string`):

Salary range, years of experience, work arrangement, skills, benefits, keywords, visa sponsorship and language.

## `company` (type: `string`):

Company logo, name, link, slug, industry, size, followers, website and description.

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

Cities, regions, countries, time zones and applicant location requirements.

## `applicants` (type: `string`):

Applicant counts, header caption, job functions, industries and Easy Apply information.

## `description` (type: `string`):

Full job description in text or HTML.

## `runDetails` (type: `string`):

Collection time, matched search, completeness score and warnings per job.

## `allData` (type: `string`):

Every field of every job, including all nested sections.

## `outputJson` (type: `string`):

Every job saved in this run as one JSON array, stored in the run's key-value store under the OUTPUT key. Available when at least one job was saved.

# 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 = {
    "limit": 10,
    "urls": [
        "https://www.linkedin.com/jobs/search/?keywords=Data%20Engineer&location=United%20States"
    ],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("insights_data/advanced-linkedin-job-search-api").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 = {
    "limit": 10,
    "urls": ["https://www.linkedin.com/jobs/search/?keywords=Data%20Engineer&location=United%20States"],
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("insights_data/advanced-linkedin-job-search-api").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 '{
  "limit": 10,
  "urls": [
    "https://www.linkedin.com/jobs/search/?keywords=Data%20Engineer&location=United%20States"
  ],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call insights_data/advanced-linkedin-job-search-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,insights_data/advanced-linkedin-job-search-api"
        }
    }
}
```

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/3GVvOeIagBWpkzSaQ/builds/GGOjatgbtqvXLqPs2/openapi.json
