LinkedIn Jobs Search Lookup: jobs scraper from $1.50/1k avatar

LinkedIn Jobs Search Lookup: jobs scraper from $1.50/1k

Pricing

from $1.14 / 1,000 job posting returneds

Go to Apify Store
LinkedIn Jobs Search Lookup: jobs scraper from $1.50/1k

LinkedIn Jobs Search Lookup: jobs scraper from $1.50/1k

LinkedIn jobs scraper by keyword and location — run a search on LinkedIn's public job board and get one row per posting: title, company, location, posted date, apply link, and the full description on request. No login, no cookies. Pay per posting returned; empty searches are free.

Pricing

from $1.14 / 1,000 job posting returneds

Rating

0.0

(0)

Developer

Adrian Voss

Adrian Voss

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

10 hours ago

Last modified

Share

LinkedIn Jobs Search Lookup: LinkedIn Jobs Scraper by Keyword & Location

You write job searches the way you'd type them into LinkedIn — data engineer @ Berlin, Germany — and this actor runs each one against LinkedIn's public, logged-out job board and returns one clean row per posting: title, company, location, posted date, and a permanent link. Turn on the description option and each row also carries the full posting text, the pay range when the posting states one, seniority, employment type, job function, industry and applicant count.

No login. No cookies. No LinkedIn account or session token of any kind — see the legal note at the bottom of this page.

Who it's for

The accountable_eel catalogue sells company and hiring intelligence columns for outbound and recruiting. Each actor takes a list of identifiers — domains, company slugs, and here, job searches — and returns one flat, stably-named row per result: the shape a Clay table, an n8n workflow, or an AI agent can consume without post-processing. Pricing is pay-per-event: a fraction of a cent for a row you actually got, and nothing at all for a search that finds nothing. No seat licence, no monthly minimum, no credit system to decode.

This actor is the wide-net end of that catalogue. The ATS lookups in the same family (greenhouse-jobs-lookup, lever-jobs-lookup, ashby-jobs-lookup and friends) answer "what is this company hiring for" — you bring the company list. This one answers "who is hiring for this role, here" when you don't have the list yet.

Why this one

  • Every filter it offers is a filter that works. LinkedIn's logged-out search endpoint accepts a great many f_* parameters and quietly ignores most of them. Measured on 2026-08-24 by running each one 4–6 times and comparing the returned job IDs against an unfiltered search: date-posted, Easy Apply, location, geo ID and company ID are real; job type, experience level, remote, salary band, industry and sort order return the identical result set to no filter at all. So keywords, location and posted-date go to LinkedIn, and role/company/location/remote narrowing happens here on the postings after they arrive — and the page tells you which is which. Paste a LinkedIn search URL that carried filters LinkedIn drops, and the row's ignoredSearchFilters column names them instead of pretending they applied.
  • You are never billed for the same posting twice. LinkedIn hands out ten results at a time, reshuffles between requests, and repeats postings across neighbouring pages — two requests at offsets ten apart shared half their results on one measurement. Every posting is deduplicated by its LinkedIn job ID across the pages of a search and, by default, across every search in the run. On a per-posting price, that is a billing guarantee, not a tidiness feature.
  • The description hop is opt-in and separately priced. Opening every posting's own page is one extra visit per posting and roughly twenty times the bandwidth. It's off by default, and when it's on you're charged for it only on postings whose page actually came back.
  • Partial results survive a rate limit. If LinkedIn throttles the run halfway through a 100-posting search, you get the postings collected so far, flagged with blockedWhilePaging, instead of losing the whole search to a retry.
  • Three input shapes. keywords @ location, bare keywords with a location set once for the whole run, or a LinkedIn jobs search URL pasted straight out of your browser's address bar.

What you get

One row per job posting by default. (Turn off "One row per job posting" in the Input tab to get one row per search instead, with the whole posting list nested in jobs.) Every row carries these fields, whether or not you turned on filters or descriptions — the columns never move:

FieldType / formatDescription
querytextThe search line you passed in, unchanged.
foundbooleantrue if the search returned at least one posting. false rows are never charged.
statustextOK, NOT_FOUND (no public postings for that search), BAD_FORMAT (the line wasn't a search or a LinkedIn URL), or BLOCKED.
searchKeywordstextThe keywords actually sent to LinkedIn.
searchLocationtextThe location actually sent to LinkedIn.
jobCountnumberHow many postings this search returned after your filters — this is exactly what you're charged for.
truncatedbooleantrue if more postings were available than you asked for, or if paging stopped early.
ignoredSearchFiltersarrayFilters present on a pasted LinkedIn URL that the public endpoint does not honour. Empty for a normal search line.
jobsarrayThe full posting list. Present in every row; it's what gets expanded into separate rows in "one row per posting" mode.
jobIdtextLinkedIn's own numeric posting ID — stable, and what deduplication keys on.
titletextJob title.
companytextHiring company as LinkedIn names it.
companyUrllinkThe company's LinkedIn page, with tracking parameters stripped.
companyLogoUrlimageCompany logo image URL.
locationtextLocation as LinkedIn prints it on the posting (e.g. "Fort Wayne, IN", "United States").
remotebooleantrue if the location or title reads as remote.
postedAtdate (ISO)The posting date, read from LinkedIn's own datetime attribute — a real calendar date, not a guess back-derived from "4 days ago".
postedAgotextThe same thing in LinkedIn's words ("4 days ago", "20 hours ago").
jobUrllinkPermanent public link to the posting, with this run's tracking parameters removed.
activelyHiringbooleantrue if LinkedIn shows the "Actively Hiring" badge.
earlyApplicantbooleantrue if LinkedIn says you'd be among the first applicants.
benefitsTexttextThe benefits teaser LinkedIn shows on some cards ("Medical insurance +6 benefits"). Most postings don't have one.
descriptionFetchedbooleantrue if the posting's own page was opened for the fields below.
descriptiontextThe full posting text, as plain text.
salaryTexttextThe pay range, when the posting states one in its description.
seniorityLeveltexte.g. "Associate", "Mid-Senior level".
employmentTypetexte.g. "Full-time", "Contract".
jobFunctiontexte.g. "Engineering and Information Technology".
industriestexte.g. "Software Development".
applicantsTexttexte.g. "Over 200 applicants", "Be among the first 25 applicants". Shown on roughly half of postings.
scrapedAtdate (ISO)When this actor fetched the row.

A search that returns no public postings comes back as a single found: false row with a status/message explaining why, and is never charged. So does a line that isn't a usable search.

Two honest limits, both measured rather than assumed:

  • There is no applyUrl. The Apply button on a logged-out posting opens a LinkedIn sign-up dialog, not an offsite application link — so no such field is offered rather than filled with the sign-up URL. jobUrl is the permanent public link.
  • salaryText comes out of the description. There is no structured compensation field on a logged-out posting, so pay is read from the posting text and is null unless you turn descriptions on and the posting actually states a range. A perk written like pay (a learning budget, a signing bonus) is deliberately not reported as salary.

Pricing

  • Job posting returned: $1.5 per 1,000 job postings
  • Full description added: $1 per 1,000 job postings

Plus a $0.00005 start fee per run. Each event above is billed independently, only when it actually returns data — misses (found:false) are never charged.

You're charged per posting returned, not per search — a search that returns 4 postings costs four, a search that returns none costs nothing, and a BAD_FORMAT line costs nothing. The description option adds a second, separate charge on top, and only on the postings whose page actually loaded.

Because you pay per posting, "Most postings to return per search" is your budget control: leave it at 100 and a five-search run costs at most 500 postings' worth. LinkedIn stops serving results at roughly 1,000 per search regardless, so that is the real ceiling per line.

How to use

  1. In the Apify Console. Open the actor page and click Start — the searches field is already pre-filled with a working example. Results land in the run's dataset as soon as each item is found.
  2. Via the API. Call it directly with a POST request — no Console needed once you have an API token:
    curl "https://api.apify.com/v2/acts/accountable_eel~linkedin-jobs-search-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
    -X POST \
    -H "Content-Type: application/json" \
    -d '{"searches":["marketing manager @ United States"]}'
  3. On a schedule. Save this actor as an Apify Task with the input you want, then add a Schedule (hourly, daily, weekly) so it runs on its own — no server of your own required.

Paste one search per line. All three of these work, and you can mix them in one run:

data engineer @ Berlin, Germany
marketing manager
https://www.linkedin.com/jobs/search?keywords=data%20engineer&location=Berlin%2C%20Germany

A line with no @ location uses the location you set once in 🔍 Search settings. A pasted LinkedIn URL brings its own keywords, location, geo ID, date-posted, Easy Apply and company filters with it; anything else it carries is dropped and named in ignoredSearchFilters.

🔍 Search settings — these are sent to LinkedIn, so they narrow the search before results are returned, which makes runs cheaper as well as more relevant:

InputWhat it does
defaultLocationLocation for any line that doesn't name one. Write it as LinkedIn does — "Berlin, Germany", "United States".
maxJobsPerQueryMost postings to return per search. Default 100, capped at what LinkedIn will serve (about 1,000).
postedWithinPast 24 hours / week / month.
easyApplyOnlyOnly roles you can apply to without leaving LinkedIn.
fetchDescriptionOpen each posting for its full text, pay range, seniority, employment type, function, industry and applicant count.

🎯 Narrow the results — these are applied here, to the postings after they arrive, because LinkedIn's logged-out search ignores the equivalent parameters. They combine with AND across fields and OR within a field:

InputWhat it does
titleKeywordsKeep only titles containing one of these — ["engineer","designer"].
excludeTitleKeywordsDrop titles containing one of these — ["intern","senior"]. Applied after the include list.
companiesKeep only these companies — partial names match.
excludeCompaniesDrop these companies — useful for filtering out staffing agencies you already know.
locationsNarrow a wide search to particular cities or regions.
remoteOnlyKeep only roles whose location or title reads as remote.
skipDuplicateJobsOn by default. Each posting is returned, and billed, once per run even if two searches overlap.

If LinkedIn throttles a run, lower Max concurrency (in ⚙️ Advanced) to 1 and keep the Residential proxy setting on. Rate limits are retried with backoff automatically, and a run that gets limited mid-search returns what it collected rather than failing.

Input

{
"searches": [
"marketing manager @ United States"
]
}

One search per line. Write it as "keywords @ location", or just the keywords and set a location below, or paste a LinkedIn jobs search URL straight from your browser. Accepted formats: data engineer @ Berlin, Germany, marketing manager, https://www.linkedin.com/jobs/search?keywords=data%20engineer&location=Berlin%2C%20Germany.

Output

queryfoundstatussearchKeywordssearchLocationjobCounttruncatedignoredSearchFiltersjobsjobIdtitlecompanycompanyUrlcompanyLogoUrllocationremotepostedAtpostedAgojobUrlactivelyHiringearlyApplicantbenefitsTextdescriptionFetcheddescriptionsalaryTextseniorityLevelemploymentTypejobFunctionindustriesapplicantsTextscrapedAt
marketing manager @ United StatestrueOKmarketing managerUnited States100true<all postings found (full list)>4447045308Marketing ManagerAmcorhttps://ch.linkedin.com/company/amcorhttps://media.licdn.com/dms/image/v2/C4D0BAQF0sed-lbowMg/company-logo_100_100/company-logo_100_100/0/1630499181382/amcor_logoEvansville, INfalse2026-08-201 week agohttps://www.linkedin.com/jobs/view/marketing-manager-at-amcor-4447045308truefalsefalse2026-08-31T06:50:20.789Z

This actor reads public data only. It requests the same two logged-out URLs your browser requests when you open a LinkedIn job search or a job posting while signed out — the ones LinkedIn serves to search engines and to visitors without an account.

It does not log in, does not accept or store a LinkedIn account, password, session cookie or li_at token, and has no input field in which you could give it one. It reads no private profiles, no connection graphs and no personal data beyond what a hiring company chose to publish in its own job advertisement: the role, the company, the location and the posting text. It does not collect names, email addresses or contact details of individuals.

You are responsible for how you use the results. Public-data collection of this kind has been held lawful in the United States (hiQ Labs v. LinkedIn), but that is not the whole picture: LinkedIn's User Agreement prohibits automated collection regardless, and if you are in or handling data from the EU/UK, the GDPR applies to job-posting data that identifies a person (a named hiring manager in the posting text, for instance) even though it is public. Check your own obligations before putting this on a schedule, and don't use it to build profiles of individuals.

Use it from Clay, n8n, Make, or an AI agent

This actor runs synchronously over plain HTTP — call it directly from a script, a workflow tool, or an AI agent, no Apify Console needed once you have an API token.

curl "https://api.apify.com/v2/acts/accountable_eel~linkedin-jobs-search-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
-X POST \
-H "Content-Type: application/json" \
-d '{"searches":["marketing manager @ United States"]}'

n8n. Add an HTTP Request node: Method POST, URL https://api.apify.com/v2/acts/accountable_eel~linkedin-jobs-search-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>, Body Content Type JSON, JSON Body {"searches":["marketing manager @ United States"]} (swap in an expression from an earlier node for a real value).

Clay. Add an "HTTP API" column: Method POST, URL https://api.apify.com/v2/acts/accountable_eel~linkedin-jobs-search-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>, Body {"searches":["{{search}}"]}, mapping the row's search into the searches array.

MCP. In Claude, Cursor, or any MCP client with the Apify MCP server, ask for "LinkedIn Jobs Scraper by Keyword | Apify" — the agent will find and run this actor.