LinkedIn Job Scraper API - Salary, Skills & Full Descriptions
Pricing
from $1.00 / 1,000 results
LinkedIn Job Scraper API - Salary, Skills & Full Descriptions
LinkedIn job scraper tool that searches by keyword and location and returns every posting with the full description, salary, skills, and applicant count. No login or cookies.
Pricing
from $1.00 / 1,000 results
Rating
0.0
(0)
Developer
Thodor
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
14 days ago
Last modified
Categories
Share
A LinkedIn job scraper tool that searches by keyword and location, then opens every posting it finds. Search python developer in Amsterdam, Netherlands and load the jobs into a spreadsheet as CSV, Excel, or JSON, full description included. No login, no cookies, no LinkedIn account.
Most LinkedIn job scrapers return what the search results page shows: title, company, location, date. This one opens each job and returns what is inside it: the full description, remote flag, seniority, applicant count, and expiry date. Tracking specific employers instead of keywords? The LinkedIn Company Jobs Scraper takes a company and returns every opening it has.
๐ How to export LinkedIn jobs to a spreadsheet
- Type your search terms into Search keywords, one per line. Each term runs as its own search.
- Set Location the way you would type it on LinkedIn:
Berlin, Germany,London, United Kingdom,United States. Empty means worldwide. - Click Start.
- Open the Output tab and click Export for CSV, Excel, JSON, or HTML.
A 10-job run across two keywords takes about 20 seconds. Already built your search on LinkedIn? Paste the URL into Search URLs and the keywords and location are read from it.
๐ So what do you get?
| ๐ผ Job title | ๐ Full job description | ๐ฅ Amount of applicants |
|---|---|---|
| ๐ Remote flag | ๐๏ธ Seniority level | ๐ Posted and expiry dates |
| ๐ข Employer firmographics, optional | ๐ถ Salary, when published | ๐ ๏ธ Job function |
| โณ Employment type | ๐ Required skills and education | ๐ Months of experience required |
| ๐ Original LinkedIn URL | ๐ Stable job and requisition IDs | ๐ Which search produced the row |
โ๏ธ Compared to search-page scrapers
| Search-page scrapers | This actor | |
|---|---|---|
| ๐ Description | โ Snippet or none | โ Full, as HTML and plain text |
| ๐ Remote and seniority | โ ๏ธ From filters LinkedIn ignores | โ Read from each job's own page |
| ๐ Expiry date | โ Not on the results page | โ
valid_through on every row |
| ๐ฅ Applicant count | โ | โ A competition signal per job |
| ๐ Your LinkedIn account | โ ๏ธ Cookie scrapers put it at risk | โ Never used, nothing to ban |
๐ฏ Three things people run this for
| How | |
|---|---|
| ๐ฐ A daily feed of new jobs in a market | Daily run on a Schedule with Posted within at Past 24 hours. A webhook drops new rows into Slack, Airtable, or your CRM |
| ๐ Hiring-intent lists for sales | Group the feed by company and you know who is staffing a team this month. Switch on Add company details for industry, headcount, and HQ on every row |
| ๐ถ Salary and stack research | base_salary when the employer publishes it; the rest of the time the range sits in description_text, ready to mine with an LLM |
๐ฅ Input
{"keywords": ["python developer", "data engineer"],"location": "Amsterdam, Netherlands","postedWithin": "30d","maxJobsPerSearch": 25}
keywords: one search per term. A job matched by two terms is returned oncelocation: as you would type it on LinkedIn. Empty means worldwidepostedWithin: defaultany, or24h,7d,30djobType: defaultany, orfulltime,parttime,contract,temporary,internship,volunteer,othermaxJobsPerSearch:0= everything LinkedIn serves. The default25keeps a first run quickscrapeDetails: defaulttrue, opens each job for the full record. Off = much faster, search-results fields onlyenrichCompanies: defaultfalse. Attaches employer firmographics, one lookup per company per run, billed on topsearchUrls: paste LinkedIn job search URLs instead of typing keywords
๐ฐ A daily feed of new postings
Put this on a daily Schedule and each run returns only what appeared since the last one.
{"keywords": ["data engineer"], "location": "Amsterdam, Netherlands", "postedWithin": "24h", "maxJobsPerSearch": 0}
๐๏ธ Several roles in one market
{"keywords": ["python developer", "data engineer", "ml engineer"], "location": "Amsterdam, Netherlands", "maxJobsPerSearch": 0}
โก A fast headcount sweep
When the question is "how many jobs match", not "what is in them", switching details off makes the run several times faster.
{"keywords": ["rust developer"], "location": "Germany", "scrapeDetails": false, "maxJobsPerSearch": 0}
๐ค Output
One flat row per job. Results stream into the dataset while the run is going, so partial exports work immediately.

{"search_keywords": "python developer","search_location": "Amsterdam, Netherlands","job_id": "4445431410","title": "Python Software Engineer","company": "IMC Trading","company_url": "https://www.linkedin.com/company/imc-trading","location": "Amsterdam, North Holland, Netherlands","is_remote": false,"posted": "2026-07-26","date_posted": "2026-07-26T02:18:40.000Z","valid_through": "2026-08-26T02:18:40.000Z","employment_type": "FULL_TIME","seniority": "Mid-Senior level","job_function": "Engineering and Information Technology","industries": "Financial Services","applicants": 200,"employer_job_id": "f862f028-49d1-4487-96d8-47b7e73fd508","education": { "@type": "EducationalOccupationalCredential", "credentialCategory": "bachelor degree" },"description_html": "<p>...</p>","description_text": "...","view_url": "https://nl.linkedin.com/jobs/view/python-software-engineer-at-imc-trading-4445431410"// HIDDEN: months_of_experience, applicants_raw, base_salary, skills,// hiring_org, job_location, company_details, detail_error}
โ ๏ธ Jobs are not returned newest first. LinkedIn orders them by its own relevance ranking and ignores any request to sort by date. Posted within is the way to bias a run toward recent postings; sort on
date_postedfor strict chronology.
Fields
| Field | Notes |
|---|---|
job_id, view_url | Stable LinkedIn identifiers. Use job_id to deduplicate across runs and detect new postings |
search_keywords, search_location | Which of your searches produced this row, so multi-keyword runs stay traceable |
title, company, location, posted | The basics, on every row even with details switched off |
is_remote | true when LinkedIn flags the posting as remote, read from the job's own page rather than the search results |
date_posted, valid_through | Exact timestamps from LinkedIn's structured data, including when the posting expires |
applicants | Capped by LinkedIn at 200. A row showing 200 means "200 or more", and applicants_raw keeps the original wording |
seniority, job_function, industries, employment_type | Always in English, for every job in every country |
months_of_experience | Required experience as a number, so 24 means two years. Filter on it instead of parsing prose |
employer_job_id | The employer's own requisition ID, separate from LinkedIn's job_id. Joins against a careers page or ATS |
base_salary | Populated when LinkedIn publishes structured pay, which is uncommon. Usually null |
description_html, description_text | Full posting body as HTML and as plain text. Feed either to an LLM to pull out tech stack, salary, or team structure |
skills, education, hiring_org, job_location | Present when LinkedIn publishes them, null when it does not |
company_details | Employer firmographics, when Add company details is on. null otherwise |
detail_error | null on success. Set when a detail page failed, so you can retry those rows |
โ๏ธ Use it as a LinkedIn jobs API
Every run is an HTTP endpoint: POST the same JSON as the form and the jobs come back in the response body.
Python
import requestsresp = requests.post("https://api.apify.com/v2/acts/thodor~linkedin-job-scraper-api/run-sync-get-dataset-items",params={"token": "YOUR_APIFY_TOKEN"},json={"keywords": ["python developer"], "location": "Amsterdam, Netherlands", "maxJobsPerSearch": 50},)for job in resp.json():print(job["title"], job["company"], job["location"])
Node.js
import axios from "axios";const { data } = await axios.post("https://api.apify.com/v2/acts/thodor~linkedin-job-scraper-api/run-sync-get-dataset-items",{ keywords: ["python developer"], location: "Amsterdam, Netherlands", maxJobsPerSearch: 50 },{ params: { token: process.env.APIFY_TOKEN } });console.log(data.length, data[0].title, data[0].company);
curl
curl -X POST "https://api.apify.com/v2/acts/thodor~linkedin-job-scraper-api/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \-H "Content-Type: application/json" \-d '{"keywords":["python developer"],"location":"Amsterdam, Netherlands","maxJobsPerSearch":50}'
Swap run-sync-get-dataset-items for runs to fire async and collect results by webhook; broad searches can outlive the 5-minute sync window. The apify-client SDK works too, in Python and JavaScript, and the n8n, Make, Zapier, Google Sheets, and Slack integrations take the same input.
๐ก Tip: no need to write the JSON by hand. Fill in the form on the Input tab, switch the editor from Form to JSON, and copy the result into your code.
๐ฐ How much does it cost to scrape LinkedIn jobs?
Billing is per job returned, at the per-1,000 rate on the price card on this page. Searches, result pages, and jobs that fail to load are free, so maxJobsPerSearch doubles as a hard cost cap.
Add company details is the one thing billed on top, because it fetches a company page that produces no row of its own. It bills once per company per run however many of that company's jobs you get, so 40 jobs across 12 employers bills 40 jobs plus 12 companies. A lookup that fails is not billed.
โ FAQ
Do I need a LinkedIn account or cookies? No.
Can I scrape LinkedIn jobs for free? Yes. Registering on Apify comes with $5 of free platform credit every month, no credit card needed, which covers a few thousand jobs here.
Is there an official LinkedIn jobs API? LinkedIn's Talent APIs are open to approved partners only. This actor is the practical alternative: POST keywords and a location, get JSON back, no partnership application.
Why is there no remote or seniority filter?
LinkedIn's public search accepts both parameters and then ignores them, so a toggle here would do nothing. Put remote in the keyword to bias the search, then filter on the is_remote and seniority columns, which are read from each job's own page and accurate.
Why did my search return fewer jobs than LinkedIn shows? LinkedIn caps any public job search at 1,000 results. Split the search by location or by posting window and each search gets its own 1,000; a job found twice is still returned once.
I filtered by job type and got other types anyway?
When too few postings match, LinkedIn relaxes the filter and returns adjacent types rather than an empty page. employment_type on each row is always the truth.
Does it include the recruiter or an external apply URL?
No. Both are shown to logged-in members only, so they are left out rather than guessed at. view_url always gets you to the posting itself.
What happens when LinkedIn blocks a request? The actor retries through a US proxy on a fresh IP, up to 5 times. If 15 requests fail back to back, the run stops with an explanation instead of grinding on, and everything scraped so far stays in the dataset.
Is this legal? It collects only publicly visible job postings, published deliberately so people can find and apply to them. No private data, no logins. You are responsible for how you use the output, and GDPR applies if you store personal data an employer wrote into a description.
๐ Support
Something not working, or a field missing? Message me in the Issues tab and I'll look into it quickly. I'm a solo dev, so don't hesitate.
Watching specific companies rather than keywords? The LinkedIn Company Jobs Scraper takes a company and returns every opening it has, with no search ceiling.
- Thodor