# LinkedIn Easy Apply Jobs Scraper, Verified (`automation_craft/linkedin-easy-apply-jobs-scraper`) Actor

Find LinkedIn Easy Apply jobs with no login and no cookies. Every job is checked on its own public LinkedIn page and ships the evidence: the apply control seen and its raw marker. Get title, company, location, salary, seniority, job type and full description. Pay only for confirmed Easy Apply jobs.

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

## Pricing

from $0.16 / 1,000 easy apply 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 Easy Apply Jobs Scraper, Verified

**Find LinkedIn Easy Apply jobs with no login and no cookies, each one confirmed on its own public LinkedIn job page and delivered with the evidence.** Search by keyword, location, date posted, company, distance and applicant count, filter by experience level and job type, go past LinkedIn's 1,000 result cap, or paste a list of job links and learn which ones are Easy Apply. You pay only for jobs that passed the Easy Apply check.

Other LinkedIn scrapers give you an `easyApply: true` column and ask you to trust it. This Actor opens every job's public page, reads the apply button LinkedIn renders there and ships what it saw: the signal that fired, the raw page marker, whether LinkedIn's own Easy Apply search filter agreed, and when the check was made. External apply, closed and unclear jobs are never charged.

#### What makes it different

- **Verified Easy Apply, with evidence on every row.** The check is plain deterministic code on LinkedIn's own markup, never a guess and never an AI model. In our launch measurements the marker agreed with LinkedIn's Easy Apply filter on 299 of 299 labelled jobs and with a person's view of the page in Chrome on 13 of 13.
- **Honest about LinkedIn's filters.** LinkedIn ignores the workplace type, job type and experience filters for logged-out visitors. Experience level and job type are therefore checked on each job page, and jobs that do not match are free. Workplace type is not offered, because the public page does not carry it.
- **Past the 1,000 result cap.** Every public LinkedIn search stops at 1,000 results. When a search reaches the cap, the Actor repeats it with narrower posting windows until one fits, and the run summary tells you which window was covered completely.
- **Check your own job links.** Paste LinkedIn job URLs and get a paid row for each Easy Apply job and a free check row for every other link, with the reason: external apply, closed, or not found.
- **Only new jobs on a schedule.** Give a memory name and later runs skip every job already delivered, free.
- **Relevance you control.** LinkedIn fills thin searches with unrelated jobs. A title match option drops them before their page is even opened.

### Quick start

1. Type one or more **Keywords** (for example `data analyst`) and **Locations** (for example `United States`).
2. Pick **Date posted** and, if you like, experience level, job type, company ids or under 10 applicants.
3. Set **Max Easy Apply jobs** (20 is a good first run) and click **Start**.
4. Open the **Easy Apply jobs** view, or export JSON, CSV or Excel. Every row links to the LinkedIn page with the Easy Apply button.

To check a list of links instead, leave the search empty and paste them into **Job URLs to check**.

### What you get

One `job` row per confirmed Easy Apply job. Real values from a launch test run:

| Field | Example | Filled on |
|---|---|---|
| `jobId`, `jobUrl` | `4462847517`, `https://www.linkedin.com/jobs/view/4462847517` | every row |
| `title` | `C# Developer .Net - FinTech` | every row |
| `companyName`, `companyId`, `companyUrl`, `companyLogo` | `Client Server`, `99339` | 111 of 113 measured |
| `location` | `Columbus, OH` | every row |
| `postedDate`, `postedTimeAgo` | `2026-09-24`, `4 days ago` (LinkedIn's own date, never estimated) | every row |
| `applicantsText`, `applicantsCount` | `57 applicants`, `57` (count only when LinkedIn states a number) | text on every row, count on 29 of 113 |
| `seniorityLevel`, `employmentType` | `Associate`, `Full-time` | 113 of 113 |
| `jobFunction`, `industries` | `Engineering, Information Technology, and Consulting` | 101 and 111 of 113 |
| `salary` | `{ "text": "$140,000.00/yr - $160,000.00/yr", "min": 140000, "max": 160000, "currency": "$", "period": "yr" }` | when the employer shows pay (9 of 113) |
| `descriptionText` (and `descriptionHtml` on request) | the full description | every row |
| `jobPoster` (on request) | name, headline and profile link of the poster LinkedIn shows | 22 of 113 |
| `easyApply`, `applyType` | `true`, `easy_apply` | every row |
| `easyApplyEvidence` | see below | every row |
| `search` | keywords, location, posting window, slice and a link to the same search on LinkedIn | search rows |

The evidence object on every row:

```json
{
  "verdict": "easy_apply",
  "signal": "onsite_apply_control",
  "rawMarker": "data-tracking-control-name=\"public_jobs_apply-link-onsite\"",
  "markers": { "onsiteApplyControl": true, "offsiteApplyControl": false, "offsiteApplyIcon": false, "closedNotice": false },
  "searchFilter": "f_AL=true",
  "filterAgrees": true,
  "pageJobId": "4462847517",
  "sourceUrl": "https://www.linkedin.com/jobs-guest/jobs/api/jobPosting/4462847517",
  "checkedAt": "2026-09-29T02:38:26.867Z",
  "method": "deterministic check of the apply control on LinkedIn's public job page"
}
```

A free `check` row (only for job links you pasted, or for searches when **Include free check rows** is on) carries the job id, title, company, the verdict (`external`, `closed`, `unknown`, `not_found`, a filter) and the same evidence. The last row of every run is a free `summary` with counters, the coverage of each search and slice, and what was billed.

#### How the Easy Apply check works

LinkedIn renders one apply control on each public job page. An Easy Apply job has LinkedIn's on-site apply button (tracking name ending in `onsite`). An external job has a sign-in button with LinkedIn's off-site icon (tracking names containing `offsite`). A closed job says "No longer accepting applications" and has no button. The Actor delivers a job only when the on-site control is present and no off-site control is. Anything else is reported as external, closed or unknown and is free. The searches also use LinkedIn's own Easy Apply filter, so almost every job checked passes; `filterDisagreements` in the summary counts the ones that did not.

### How much does it cost to scrape LinkedIn Easy Apply jobs?

Pay per event, no subscription. You pay for confirmed Easy Apply jobs and a tiny start fee, nothing else:

| Event | Free plan | Bronze | Silver | Gold |
|---|---|---|---|---|
| Easy Apply job | $0.25 / 1,000 | $0.20 / 1,000 | $0.18 / 1,000 | $0.16 / 1,000 |
| Actor start (per run, 256 MB) | $0.0005 | $0.0005 | $0.0005 | $0.0005 |

Platinum and Diamond plans pay the Gold price.

So 1,000 verified Easy Apply jobs with full descriptions cost $0.20 on the Bronze plan, plus $0.0005 for the run. External apply jobs, closed jobs, jobs your filters removed, duplicates, jobs already delivered under your memory name, check rows, status rows and the summary are all free. Set a maximum cost per run in the run options and the Actor stops cleanly when it is reached.

### Input

| Field | What it does |
|---|---|
| `keywords`, `locations` | Every keyword is searched in every location, with LinkedIn's Easy Apply filter on. |
| `geoIds` | Numeric LinkedIn geo ids when a place name is ambiguous (`103644278` is the United States). |
| `searchUrls` | Paste LinkedIn jobs search URLs; their keywords, location, date, company, distance and under 10 applicants filters are used. |
| `jobUrls` | LinkedIn job links or ids to check one by one. |
| `datePosted`, `postedWithinHours` | Any time, past month, past week, past 24 hours, or a custom window in hours (for example `2`). |
| `companyIds`, `under10Applicants`, `distanceMiles` | Filters LinkedIn applies to the search itself. |
| `experienceLevels`, `jobTypes` | Checked on each job page (LinkedIn ignores these search filters for logged-out visitors). If 100 job pages in a row fail them, that search stops and the summary says so. |
| `titleMatch`, `excludeTitleWords`, `excludeCompanies` | Relevance filters applied before a job page is opened. |
| `maxJobs`, `maxJobsPerSearch` | Hard caps on delivered and charged jobs, honoured exactly. |
| `splitWhenCapped` | On by default: go past the 1,000 result cap with narrower posting windows. |
| `memoryName`, `resetMemory` | Deliver only jobs you have not received before under that name. |
| `includeDescriptionHtml`, `includeJobPoster`, `includeRejected` | Optional extra output. |

Defaults for API callers: fields you leave out take the values below, not the Console form's prefills. `maxJobs` is 100, `titleMatch` is `linkedin` (no extra check), `datePosted` is any time, `splitWhenCapped` is on. An input with no search field at all (`{}`) runs a small example search (software engineer, United States, 10 jobs); an input that names a search field but leaves it empty or null is treated as empty and costs nothing.

API example:

```bash
curl -X POST "https://api.apify.com/v2/acts/automation_craft~linkedin-easy-apply-jobs-scraper/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"keywords":["data analyst"],"locations":["United States"],"datePosted":"pastWeek","titleMatch":"allWords","maxJobs":20}'
```

### What this Actor does NOT do

- It does not log in, apply to jobs or use your LinkedIn account.
- It does not return the employer's external apply URL: LinkedIn hides it behind a sign-in wall for logged-out visitors, so an external job is reported as external without a fake link.
- It does not filter by workplace type (remote, hybrid, on-site): LinkedIn ignores that filter for logged-out visitors and the public job page has no workplace field.
- It does not promise every job past the 1,000 cap: narrower windows cover recent jobs completely, and the summary says exactly which window was complete.

### FAQ

#### How do I find only Easy Apply jobs on LinkedIn?

Search with LinkedIn's Easy Apply filter, which this Actor always does, and then confirm each job on its page. Every delivered row is a job whose public page shows LinkedIn's on-site Easy Apply button, with the evidence attached.

#### Is the Easy Apply filter still there after LinkedIn's new AI job search?

Yes. Easy Apply, date posted, company and under 10 applicants still work for logged-out visitors. Workplace type, job type and experience level do not, which is why this Actor checks job type and experience on each job page instead.

#### What is the difference between Easy Apply and Apply on LinkedIn?

Easy Apply means you apply inside LinkedIn with your profile. Apply sends you to the employer's own careers site. The `easyApplyEvidence.signal` field says which button the job page showed.

#### Can I check a list of LinkedIn job links for Easy Apply?

Yes. Paste them into Job URLs to check. Easy Apply jobs come back as full paid rows, and every other link gets a free check row saying external, closed or not found.

#### Can I get more than 1,000 results for one search?

LinkedIn stops every public search at 1,000 results. With Go past the 1,000 result cap on, the Actor repeats a capped search with narrower posting windows (30 days, 21 days, 14 days and so on) until a window fits under the cap, and reports it as covered completely. Splitting a search by location or keyword also helps.

#### Why does this Actor run with limited permissions?

Least privilege: it can only read and write its own run storage and the memory store it creates for your memory name. It cannot see anything else in your account.

### More data tools by Automation Craft

- [LinkedIn Jobs Scraper - No Login, Real Dates](https://apify.com/automation_craft/linkedin-jobs-scraper)
- [LinkedIn Ad Library Scraper: Ads by Company](https://apify.com/automation_craft/linkedin-ad-library-scraper)
- [Greenhouse Jobs Scraper & API - Pay Once Per Job](https://apify.com/automation_craft/greenhouse-jobs-scraper)
- [Lever Jobs Scraper & API - Pay Once Per Job](https://apify.com/automation_craft/lever-jobs-scraper)
- [Workday Jobs Scraper & API - Whole Career Site](https://apify.com/automation_craft/workday-jobs-scraper)
- [SEEK Job Scraper: Australia & New Zealand Jobs](https://apify.com/automation_craft/seek-job-scraper)

### Changelog

- 0.1 (2026-09-29): first release. Easy Apply filtered search with a per job page check and evidence, experience and job type checked on the job page, 1,000 cap splitting, job link checks, cross-run memory.

# Changelog

This Actor's version history is a separate document: https://apify.com/automation_craft/linkedin-easy-apply-jobs-scraper/changelog.md

# Actor input Schema

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

Job titles or skills to search for, one per line, for example `data analyst`. Every keyword is searched in every location. LinkedIn's own Easy Apply filter is always on.

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

Places as you would type them on LinkedIn, one per line, for example `United States`, `London, England, United Kingdom` or `Bengaluru`.

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

Numeric LinkedIn geo ids, taken from the geoId= part of a LinkedIn jobs URL (for example `103644278` is the United States). Use them when a place name is ambiguous.

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

Paste LinkedIn jobs search URLs. Their keywords, location, geoId, date posted, company and under 10 applicants filters are used, and the Easy Apply filter is added. Filters LinkedIn ignores for logged-out visitors (workplace type, job type, experience) are dropped and reported.

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

LinkedIn job URLs or job ids to check one by one. Easy Apply jobs are delivered as full job rows. Every other URL gets a free check row that says why (external apply, closed, not found).

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

Posting window, passed to LinkedIn. Every job also carries LinkedIn's own posted date, so you can verify it.

## `postedWithinHours` (type: `integer`):

A custom window in hours, for example `2` for jobs posted in the last two hours. Overrides Date posted. Minimum 1, maximum 720.

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

Only jobs at these companies. Numeric LinkedIn company ids, found in the f_C= part of a LinkedIn jobs URL, or paste that URL. Delivered jobs also carry companyId.

## `distanceMiles` (type: `string`):

Search radius around each location, in miles, as LinkedIn's own distance filter. Leave empty for LinkedIn's default.

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

Only jobs LinkedIn marks as having fewer than 10 applicants.

## `experienceLevels` (type: `array`):

Keep only jobs whose Seniority level on the job page is one of these. Checked on each job page; jobs that do not match are free.

## `jobTypes` (type: `array`):

Keep only jobs whose Employment type on the job page is one of these. Checked on each job page; jobs that do not match are free.

## `titleMatch` (type: `string`):

LinkedIn matches keywords loosely and fills thin searches with unrelated jobs. Choose how strictly the job title must match your keywords. Jobs that do not match are dropped before their page is opened, free.

## `excludeTitleWords` (type: `array`):

Drop jobs whose title contains any of these words, for example `senior` or `intern`. Free.

## `excludeCompanies` (type: `array`):

Drop jobs from companies whose name contains any of these, for example staffing agencies. Free.

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

The most confirmed Easy Apply jobs to deliver and charge in this run, across all searches. Honoured exactly.

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

The most jobs from any one keyword and place pair, so one broad search cannot use up the whole run. 0 or empty means no per search limit.

## `splitWhenCapped` (type: `boolean`):

LinkedIn stops every public search at 1,000 results. When a search reaches that cap, repeat it with narrower posting windows (30 days, 21 days, 14 days and so on) until a window fits under the cap. Duplicates are free and the run summary says which window was covered completely.

## `includeDescriptionHtml` (type: `boolean`):

Add the job description as HTML next to the plain text version. Makes rows larger.

## `includeJobPoster` (type: `boolean`):

Off by default: the job poster card stays out of the dataset. Turn on to add the name, headline and public profile link of the person LinkedIn shows as the job poster, when LinkedIn shows one.

## `includeRejected` (type: `boolean`):

Also output a small free row for every job that was checked and rejected (external apply, closed, filtered), with the reason and the evidence. Useful for auditing the classification.

## `memoryName` (type: `string`):

Give a name, for example `weekly-data-analyst`, and every job delivered under it is remembered. Later runs with the same name skip those jobs free, so a scheduled run pays only for new Easy Apply jobs. The memory belongs to this Actor and your account.

## `resetMemory` (type: `boolean`):

Forget every job remembered under the memory name before this run.

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

The Actor uses the Apify datacenter proxy, which LinkedIn's public job pages accept, and retries a refused request on a fresh session. Other Apify proxy groups are not needed and are replaced by the datacenter proxy. You can pass your own proxy URLs instead.

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

How many job pages are checked at the same time. The default of 4 is gentle on LinkedIn and fast enough for most runs.

## Actor input object example

```json
{
  "keywords": [
    "data analyst"
  ],
  "locations": [
    "United States"
  ],
  "datePosted": "pastWeek",
  "titleMatch": "allWords",
  "maxJobs": 20,
  "splitWhenCapped": true,
  "includeJobPoster": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "BUYPROXIES94952"
    ]
  }
}
```

# Actor output Schema

## `items` (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",
    "titleMatch": "allWords",
    "maxJobs": 20,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "BUYPROXIES94952"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation_craft/linkedin-easy-apply-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",
    "titleMatch": "allWords",
    "maxJobs": 20,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["BUYPROXIES94952"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("automation_craft/linkedin-easy-apply-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",
  "titleMatch": "allWords",
  "maxJobs": 20,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "BUYPROXIES94952"
    ]
  }
}' |
apify call automation_craft/linkedin-easy-apply-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation_craft/linkedin-easy-apply-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/QROpvaVGnllyHLJrG/builds/HN6Slh0OS6vYhvaAO/openapi.json
