LinkedIn Jobs Scraper
Pricing
from $0.25 / 1,000 job postings
LinkedIn Jobs Scraper
Reliable LinkedIn jobs data API for production applications. Typed, structured postings -- descriptions, salaries, applicant counts and the job poster -- by search or by URL. No login, no cookies, no account of yours involved.
Pricing
from $0.25 / 1,000 job postings
Rating
5.0
(2)
Developer
Zyra
Maintained by CommunityActor stats
3
Bookmarked
13
Total users
7
Monthly active users
9.3 hours
Issues response
6 days ago
Last modified
Categories
Share
A reliable LinkedIn jobs data API for production applications.
Query LinkedIn's public job listings and get back typed, structured records — job titles, companies, locations, salaries, full descriptions, applicant counts, and the person who posted the job where LinkedIn names them.
Built to be scheduled and depended on, not run once by hand:
- Typed output.
applicantsis a number,posted_atis an ISO 8601 datetime,industriesis a list. Nothing downstream has to parse strings. - Stable field names. Documented in the output schema and versioned, so the table you load into does not break under you.
- Honest nulls.
has_detailstells you whether a posting's page was actually read, so a blank salary means "this job does not state one" rather than "we never looked". - Safe to re-run. A run that gets moved between machines mid-way resumes where it left off and never writes the same posting twice.
- Only real filters. Every filter here is one LinkedIn actually applies. It accepts a dozen more and quietly ignores them; this actor refuses those rather than return a full page of postings that ignore what you asked for.
No login. No cookies. No LinkedIn account of yours is involved at any point. Everything returned is what LinkedIn shows a logged-out visitor.
What you can do with it
- Job boards and aggregators — keep a feed of fresh listings for a role, a city, or a set of companies.
- Recruitment and talent research — watch who your competitors are hiring and what they are paying.
- Lead generation — a company hiring three sales engineers is a company with a budget. The poster's name and profile come with the listing, where LinkedIn shows one.
- Market research — salary bands, remote-work adoption, and which skills keep appearing, over time.
Quick start
Paste a LinkedIn search URL, or fill in the fields:
{"keywords": "python developer","location": "Berlin","maxItems": 100,"fetchDetails": true}
That returns 100 job postings with their full descriptions. To also get who posted each one:
{"keywords": "recruiter","location": "United States","maxItems": 100,"fetchDetails": true,"withRecruiter": true}
Input
Everything is optional, but a run needs something to search for: either
jobUrls, or at least one of keywords, location, geoId, companyIds —
or a searchUrl that carries them. A bad input fails immediately and tells
you why, before any work is done.
What to search for
| Field | Type | Description |
|---|---|---|
searchUrl | string | Paste a LinkedIn jobs search URL and its filters are read out of it. Anything you also set below wins. |
keywords | string | Free text, as you would type it into LinkedIn's search box. |
location | string | A place name, e.g. Berlin or United Kingdom. Resolved to LinkedIn's own place id when the name is unambiguous. |
geoId | string | LinkedIn's numeric id for a place. Use it when you already know exactly which "London" you mean. |
distance | integer | Radius in miles around the location, 0–100. LinkedIn ignores it for remote searches. |
datePosted | string | any_time, past_month, past_week, past_24_hours |
easyApply | boolean | Only postings applied to on LinkedIn itself. |
under10Applicants | boolean | LinkedIn's own "under 10 applicants" filter. LinkedIn applies it itself, so it costs nothing extra — but the applicants field still usually reads 25, because the logged-out count is approximate and floors there. |
companyIds | array | Numeric LinkedIn company ids, to restrict the search to those employers. |
The filters LinkedIn dropped
LinkedIn's public job search accepts every filter and quietly applies only some of them. As of September 2026 just 9 of the 24 parameters it accepts do anything at all; experience level, job type, job function, industry, workplace type, salary and sort-by are among the ones it discards. This is not subtle: search LinkedIn for volunteer roles and it returns full-time ones.
So this actor does not offer them. Sending one is a clear error naming what to do instead, because a filter that is accepted and then narrows nothing is worse than no filter: you get a full page of postings that ignore it, with nothing anywhere saying so.
You can still get the answer — from the data rather than the search. Switch Fetch full postings on and every row carries the facts those filters used to test, so you can filter the dataset yourself:
| Instead of | Filter the dataset on |
|---|---|
experienceLevels | seniority — Mid-Senior level, Entry level, … |
jobTypes | employment_type — Full-time, Contract, … |
jobFunctions | job_function — but match a substring: LinkedIn writes Engineering and Information Technology |
industries | industries — a list, e.g. ["Software Development"] |
salary | salary — about one posting in ten states pay |
workplaceTypes is the one with no answer: nothing on a public posting says
whether it is remote, hybrid or on-site. Put remote in keywords instead —
postings that offer it nearly always say so in the title or description.
A LinkedIn search URL pasted into searchUrl may carry any of these codes;
they are read past rather than rejected, since they were already doing
nothing in the tab the URL came from.
Or: specific postings
| Field | Type | Description |
|---|---|---|
jobUrls | array | Fetch these exact postings instead of searching. A posting URL, a share link, or the bare numeric id all work. Every search field above is ignored. |
How much, and how detailed
| Field | Type | Default | Description |
|---|---|---|---|
maxItems | integer | 100 | How many postings to return, up to 1000. |
fetchDetails | boolean | true | Open each posting for its description, seniority, employment type, job function, industries and applicant count. |
withRecruiter | boolean | false | Also return who posted the job. Needs fetchDetails. |
includeDescriptionHtml | boolean | false | Keep the description's original formatting alongside the plain text. |
Performance
| Field | Type | Default | Description |
|---|---|---|---|
concurrency | integer | 5 | How many postings to fetch at once. |
maxRetries | integer | 2 | Retries when a request comes back blocked or rate limited. |
requestTimeoutSecs | integer | 120 | Searches read LinkedIn live, so this is generous by default. |
Output
One row per posting:
{"job_id": "4417748171","job_url": "https://www.linkedin.com/jobs/view/software-engineer-python-at-acme-4417748171","title": "Software Engineer, Python","company": "Acme Inc.","company_url": "https://www.linkedin.com/company/acme","location": "Berlin, Berlin, Germany","posted_at": "2026-07-16T09:42:00Z","posted_text": "1 month ago","salary": null,"seniority": "Mid-Senior level","employment_type": "Full-time","job_function": "Engineering","industries": ["IT Services and IT Consulting"],"applicants": 171,"benefits": "Be an early applicant","description": "About the role…","logo_url": "https://media.licdn.com/…","recruiter_name": "Jane Doe","recruiter_url": "https://www.linkedin.com/in/jane-doe","recruiter_headline": "Talent Acquisition Leader @ Acme","scraped_at": "2026-08-31T10:20:20.838463Z","has_details": true}
Fields
| Field | Notes |
|---|---|
job_id, job_url | LinkedIn's own id, and the posting's public URL |
title, company, company_url, location | From the search result |
posted_at | A real timestamp, not a string you have to parse. LinkedIn states only a date, so this is midnight UTC for anything older than a day; inside the last day the time of day is recovered from posted_text and is good to the minute or hour it names. |
posted_text | What LinkedIn displayed, e.g. 3 days ago |
logo_url, benefits | The company logo, and lines like "Be among the first 25 applicants" |
salary | As written in the posting. Most postings do not state one. |
seniority, employment_type, job_function | With fetchDetails |
industries | A list, not a comma-joined string |
applicants | A number, not "64 applicants" |
description | Plain text. Add includeDescriptionHtml for the markup too. |
recruiter_name, recruiter_url, recruiter_headline | With withRecruiter |
has_details | Whether the posting itself was opened — see below |
scraped_at | When this row was read |
Types are real types. applicants is 171, not "171 applicants".
posted_at is an ISO 8601 timestamp. industries is a list. You should not
have to write a parser for a scraper's output.
A field LinkedIn did not state comes back null — never an empty string,
never a guess. has_details tells you whether the posting was actually
opened, so you can tell "this job has no salary" apart from "this row was
never enriched".
Things worth knowing
About 1,000 results per search. LinkedIn's public job search stops
producing new listings somewhere past a thousand, whatever is asked for. For
more than that, split the search — by location, by datePosted, or by
company — and run it more often.
Roughly half of postings name a poster. withRecruiter returns the
person LinkedIn shows under "Direct message the job poster". Many postings do
not show one, and for those the three recruiter fields are null. That means
"this posting names nobody", not "we failed to look".
easy_apply and apply_url are null. LinkedIn stopped showing
logged-out visitors whether a posting is Easy Apply or where an off-site
application goes. Rather than guess, these come back empty. The easyApply
filter still works — you can restrict a search to Easy Apply postings,
you just are not told which ones they are afterwards.
The same search twice returns a slightly different slice. LinkedIn rotates results for logged-out visitors. Runs are de-duplicated internally by job id, so you never get the same posting twice in one dataset, but two runs minutes apart will not match exactly.
Public data only. What a logged-out visitor can see. Recruiter contact details, applicant lists and anything behind a login are not included.
Location names can be ambiguous. "Turkey" is a town in North Carolina as
well as a country. When a name is resolved to a place, the run summary records
which place was chosen — check it if results look geographically odd, and set
geoId to settle it.
Polling for new jobs
Poll for what is new, on a schedule:
{"keywords": "python developer","location": "Berlin","datePosted": "past_24_hours","maxItems": 100}
datePosted is applied by LinkedIn itself, so a run that finds nothing new
costs almost nothing. job_id is stable across runs, so keep the ones you
have and drop the repeats on your side; sort on posted_at, which is a real
timestamp, if you need the newest first.
Support
Found a bug, or a field you need that is not here? Open an issue on the actor's Issues tab.