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
(1)
Developer
Zyra
Maintained by CommunityActor stats
2
Bookmarked
2
Total users
1
Monthly active users
2 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.
- Incremental by design.
skipJobIdsdrops postings you already have before they cost anything, so polling on a schedule stays cheap.
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. |
experienceLevels | array | internship, entry_level, associate, mid_senior, director, executive |
jobTypes | array | full_time, part_time, contract, temporary, volunteer, internship, other |
workplaceTypes | array | on_site, remote, hybrid |
datePosted | string | any_time, past_month, past_week, past_24_hours |
salary | string | Minimum USD band, 40k … 200k. LinkedIn applies this to US searches only. |
sortBy | string | relevance (LinkedIn's default) or date — use date when polling for what is new. |
easyApply | boolean | Only postings applied to on LinkedIn itself. |
companyIds | array | Numeric LinkedIn company ids, to restrict the search to those employers. |
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. |
Narrowing the results
Applied before each posting is opened, so anything you discard costs nothing.
| Field | Type | Description |
|---|---|---|
skipJobIds | array | Postings you already have. Dropped before they are fetched — this is what makes a repeated run cheap. |
titleInclude | array | Keep only titles containing at least one of these (case insensitive). |
titleExclude | array | Drop titles containing any of these. Applied after the include list. |
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:
{"source": "linkedin","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","workplace_type": "Remote","posted_at": "2026-07-16T00:00: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 |
workplace_type | Remote, On-site or Hybrid, when stated |
posted_at | A real timestamp, not a string you have to parse |
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
Sort by date, keep the ids you have seen, and skip them next time:
{"keywords": "python developer","location": "Berlin","sortBy": "date","datePosted": "past_24_hours","maxItems": 100,"skipJobIds": ["4417748171", "4448412664"]}
Skipped postings are dropped before they are fetched, so a run that finds nothing new costs almost nothing.
Support
Found a bug, or a field you need that is not here? Open an issue on the actor's Issues tab.