LinkedIn Jobs Scraper — No Login, Delta Mode | Pay Per Result
Pricing
$0.90 / 1,000 job results
LinkedIn Jobs Scraper — No Login, Delta Mode | Pay Per Result
LinkedIn jobs scraper — no login, no cookies, no proxy, no start fee. Search by keyword, location, remote & posting age; clean JSON: title, company, apply URL, description, seniority & industry. Delta mode bills only unseen postings — ideal for job alerts. Honest ~200 jobs/run.
Extract live job postings from LinkedIn's public job search — no account, no cookies, no browser, no start fee. Filter by keyword, location, remote-only and posting age, and receive one clean JSON record per job.
Built for scheduled monitoring and job-alert bots: turn on delta mode, run it on a cron, and pay only for postings you haven't seen before — a run that finds nothing new costs $0.
Why this scraper
| This Actor | Typical alternatives | |
|---|---|---|
| Price per 1,000 jobs | $0.90 | $0.40–$10.00 |
| Start fee per run | $0 | up to $0.005–$0.05 — adds up fast on scheduled runs |
| Cross-run dedup (delta mode) | Built in, unseen-only billing | mostly absent |
| Login / cookies / proxy | None needed | some require cookies or proxies |
| Zero-result runs | Free + diagnostic row | often billed or fail silently |
| Max jobs per run | ~200 (honest guest-API limit) | up to 1,000 for bulk scrapers |
If you need 1,000+ postings in one bulk pull, a large-scale scraper serves you better. If you poll LinkedIn every hour and want only what's new — this is the cheapest way to do it: no start fee × delta mode means a quiet hour costs literally nothing.
What LinkedIn jobs data does this scraper extract?
Each result is one flat JSON record per job posting:
| Field | Meaning |
|---|---|
id | Numeric LinkedIn job posting id, extracted from the posting URL (null if it couldn't be parsed) |
title | Job title as posted |
company | Hiring company / organisation |
location | Location / duty station (may include remote hints) |
postedAt | Posting date (YYYY-MM-DD) read from the search card, when LinkedIn includes it (null otherwise) |
url | Direct link to the posting |
source | Always "linkedin" |
description | Full plain-text job description (empty string if includeDescription is off or the fetch failed) |
snippet | Alias of description, same value — kept for compatibility with other scrapers in this collection |
employmentType | Employment type, e.g. Full-time (only when includeCompanyInfo is on; null otherwise) |
seniorityLevel | Seniority / experience level, e.g. Mid-Senior level (only when includeCompanyInfo is on) |
jobFunction | Job function, e.g. Engineering and Information Technology (only when includeCompanyInfo is on) |
industries | Company industry, e.g. Software Development (only when includeCompanyInfo is on) |
applicantsCount | Applicant-count caption, e.g. Over 200 applicants — best-effort, often hidden by LinkedIn (only when includeCompanyInfo is on) |
isNew | true when delta mode (onlyNewSinceLastRun) emitted this posting as unseen on a previous flagged run (absent on normal runs) |
The employmentType / seniorityLevel / jobFunction / industries / applicantsCount fields are read straight from each posting's job-criteria list — any value LinkedIn doesn't expose stays null; nothing is guessed.
LinkedIn's guest cards never carry salary data, so no salary field is emitted — we don't fabricate one.
A run that returns zero postings (or hits an unexpected error) still finishes successfully and pushes a single non-billed diagnostic row with a warnings field explaining why — so an empty result is never a silent failure.
How to scrape LinkedIn jobs with this Actor
- Click Try for free / Run — no login to the target site, no cookies, no proxies to configure.
- Adjust the input (keyword, filters,
maxItems) or keep the defaults. - Run it and export the dataset as JSON, CSV or Excel, or read it over the API.
Run it from your own code:
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("nomad-agent/linkedin-scraper").call(run_input={"maxItems": 50})for item in client.dataset(run["defaultDatasetId"]).iterate_items():print(item["title"], "—", item["company"], item["url"])
Or a single HTTP call that runs the Actor and returns items in one response:
curl -X POST \"https://api.apify.com/v2/acts/nomad-agent~linkedin-scraper/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \-H "Content-Type: application/json" \-d '{"maxItems": 50}'
Input
| Field | Type | Default | Notes |
|---|---|---|---|
keyword | string | "" | Job title, skill or role to search for (e.g. "software engineer", "product manager react"). Leave empty to… |
location | string | "" | City, region or country to filter by (e.g. "Spain", "London", "European Union"). Leave empty for worldwide… |
remote | boolean | false | When enabled, restricts results to remote-eligible postings. |
timeFilter | string | "r86400" | Restrict to postings published within the chosen window. |
maxItems | integer | 100 | Maximum number of job postings to return. Hard ceiling: ~200 per run — LinkedIn's guest search endpoint stops paginating past that offset no matter what you set here (0 = "no limit" still caps at ~200). |
includeDescription | boolean | true | Fetch and include the full plain-text job description for each posting. Disabling this makes runs faster… |
includeCompanyInfo | boolean | false | Also read structured job details (employmentType, seniorityLevel, jobFunction, industries, applicantsCount) from each posting. Reuses the description request — no extra requests when includeDescription is already on. |
onlyNewSinceLastRun | boolean | false | Delta mode. Only output postings not seen on a previous run that also had this flag on. Already-seen postings are dropped before push (not billed) — the cheapest way to poll on a schedule. See Delta mode / monitoring below. |
skipJobId | array of strings | [] | Explicit dedup list — drop any posting whose numeric job id is in this array. Use it to skip ids you already have, in a single call. |
postedSince | integer | 0 | Drop results whose posting date is older than this many days. Items without a known posting date are not filtered out. Set 0 to disable. |
titleExclude | array of strings | [] | Drop a result if its title contains any of these words or phrases (case-insensitive). |
companyExclude | array of strings | [] | Drop a result if its company name contains any of these words or phrases (case-insensitive). |
cacheTtlSeconds | integer | 1800 | Reuses the last fetch for this many seconds so rapid re-runs don't hit LinkedIn again. Set 0 to always fetch live. |
Every filter and the two dedup modes default to off/empty, so existing integrations see no behavior change unless you opt in.
Delta mode / monitoring
Turn on onlyNewSinceLastRun to poll LinkedIn on a schedule and only pay for genuinely new postings. The Actor remembers the job ids it emitted on previous flagged runs in a dedicated per-Actor key-value store; on the next run it drops anything already seen before pushing (so those rows are never billed) and stamps each surviving record isNew: true.
- Perfect for job-alert bots and cron-scheduled runs — a repeat run that finds nothing new pushes one non-billed diagnostic row instead of re-charging you.
- Because LinkedIn's guest search is a windowed query (newest ~200 in your time filter), delta mode detects only new postings — it does not try to report "closed" postings (an absent posting may simply have scrolled past the window).
skipJobIddoes the same job for a one-off call: pass the ids you already have and they're skipped without touching the delta store.
The delta store is best-effort: if it can't be read, the run degrades to "everything is new" rather than failing.
Output example
{"id": "4429472960","title": "Senior Frontend Engineer (React)","company": "Acme Software","location": "Berlin, Germany (Remote)","postedAt": "2026-06-30","url": "https://www.linkedin.com/jobs/view/4429472960","source": "linkedin","description": "We are hiring a Senior Frontend Engineer...","snippet": "We are hiring a Senior Frontend Engineer..."}
id and postedAt are null on the rare card where LinkedIn's markup doesn't include them — the rest of the record is unaffected.
Pricing
$0.0009 per job returned — and nothing else. No start fee, no subscription, no rental. 100 jobs ≈ $0.09. In delta mode, already-seen postings are never billed, so a scheduled run that finds nothing new costs $0.
Integrations
Export results as JSON, CSV or Excel; connect via Make, Zapier or n8n; call directly with run-sync-get-dataset-items; or plug into AI agents through the Apify MCP server.
Use cases
- Job-alert bots and job boards that need fresh LinkedIn postings
- Recruiting and sourcing pipelines tracking who is hiring
- Salary and hiring-market research by role or region
- AI agents that match candidates to live openings
FAQ
Is it legal to scrape LinkedIn jobs? This Actor reads only publicly available job postings — data any visitor can see without logging in. No personal data behind authentication is touched. Review the target site's terms and your local regulations for your specific use case.
Do I need an account on the target site? No. Postings are fetched from public pages/APIs — no login, cookies or session tokens.
How fresh is the data?
Every run fetches live listings. Results are cached for cacheTtlSeconds (default 30 min, set 0 to always hit the source live).
How many jobs can I get?
maxItems caps the run (set 0 where supported for no cap). Most sources paginate from newest to oldest.
Does it work without a LinkedIn account? Yes. The scraper reads LinkedIn's public guest job-search endpoint, so no login, cookies or session tokens are needed.
Something broken or missing? Open an issue on the Actor's Issues tab — it is monitored and reliability fixes ship fast.
Is this Actor useful to you? A quick ⭐ review on the Actor's Reviews tab helps other job-alert and recruiting users find it — and tells us what to build next.