LinkedIn Job Scraper

A LinkedIn job scraper and job search API that needs no LinkedIn account and no cookie. Search by keyword and location with the date posted, company, Easy Apply and under-10-applicants filters, and every search also returns LinkedIn's own total results count as a free row. Give it job URLs or ids for full job records: description, seniority, employment type, job function, industries, applicant count and salary where the employer publishes it. Three suggestion modes return job titles, the numeric company ids the company filter needs, and the geoId values for precise locations. LinkedIn's guest search stops near 1,000 results per query, so deeper pulls are sliced by date, place or company and deduplicated by job id.

MEDIAN LAGon demand
FIELDS31
TOTAL USERS46
MONTHLY ACTIVE31
TOTAL RUNS2,785
SUCCESS (30D)99.8%
RATING5.0 (2)
LAST MODIFIED2026-10-07
PUBLISHED2026-08

Input parameters

PARAMETERTYPEREQDEFAULTDESCRIPTION
search_mode enum yes — What to run: search (job listings plus the free results count), job (full records for job URLs or ids), autocomplete (job titles), companySuggestions (company names with the ids the company filter takes) or locationSuggestions (places with their geoId).
keywords string no — What to search for: a job title, skill or any phrase, for example semiconductor engineer. Search mode.
location string no — A free-text place, for example United States, Texas or Philadelphia, PA. LinkedIn resolves it. Search mode.
geoId string no — A LinkedIn geo id from the locationSuggestions mode for precise targeting, best used together with the location text.
datePosted enum no any Only jobs posted within this window: any, past24h, pastWeek or pastMonth.
companyIds string[] no — Numeric LinkedIn company ids to restrict the search to, for example 1441 for Google. Several ids combine as OR. Get them from the companySuggestions mode.
easyApplyOnly boolean no false Only jobs with LinkedIn Easy Apply. The filter is passed to LinkedIn as is.
under10Applicants boolean no false Only jobs with fewer than 10 applicants so far.
jobUrls string[] no — linkedin.com/jobs/view/ URLs or bare numeric job ids, mixed freely; duplicates collapse into one job. Job mode. — drives your bill
query string no — The text to complete in the three suggestion modes, for example semic for job titles or phila for locations.
maxItems integer no 100 Most billable rows to return, 1 to 1,000. The free results-count row and error rows never count. — drives your bill
maxAgeDays integer no — Serve rows fetched within this many days from the shared cache, up to 90. 0 always fetches fresh; empty uses each record type's default.

Output schema

FIELDTYPEDESCRIPTIONNULLABLE
resultType string What this row is: a free search summary with the results count, a billed job listing from a search, a billed full job record, a billed typeahead suggestion, or a free in-band error. no
jobId string LinkedIn's numeric job posting id. Dedupe key within a run. yes
jobTitle string The listing title as the search card shows it. yes
companyName string The hiring company's name. yes
companyLinkedinUrl string The company's LinkedIn page, tracking parameters stripped. yes
location string The job's location string. yes
postedDate string ISO date from the card's time element. yes
jobUrl string Canonical linkedin.com/jobs/view/ URL, tracking parameters stripped. yes
resultsCountCaption string LinkedIn's own results caption, verbatim. The number can be capped with a plus sign and varies between LinkedIn's serving variants, so treat it as a floor. yes
resultsCountParsed integer The caption's number parsed to an integer. A floor when resultsCountIsCapped is true, never a pagination total. yes
resultsCountIsCapped boolean True when the caption carried a plus sign, meaning at least this many results. yes
descriptionText string The full job description as readable text, including the content behind Show more. yes
seniorityLevel string The Seniority level criteria value. yes
employmentType string The Employment type criteria value. yes
jobFunction string The Job function criteria value. yes
industries string The Industries criteria value. yes
numApplicants integer The applicants number parsed from the caption, when present. yes
salaryText string The published salary string, verbatim; null when the job does not publish one. yes
salaryMin number Lower bound parsed from the salary string. yes
salaryMax number Upper bound parsed from the salary string. yes
salaryCurrency string Currency code inferred from the salary string. yes
applyType string Where applying happens, on job rows: external (the employer's own site) or linkedin (Easy Apply). Null when the page does not say. yes
externalApplyUrl string The employer's off-LinkedIn apply URL when the page embeds one; usually null. yes
suggestionType string TITLE, COMPANY, or GEO. yes
displayName string The suggestion's display text. yes
suggestionId string The suggestion's LinkedIn id. COMPANY ids feed the companyIds filter; GEO ids feed the geoId field. yes
viaResidentialProxy boolean True when this row was fetched through the residential fallback tier. The surcharge event is charged only when LinkedIn actually blocked the standard route first; a residential fetch for any other reason is never surcharged. Billing transparency field. yes
fromCache boolean True when this billed row was served from the shared result cache instead of a new request. A cached row keeps the time it was originally fetched in fetchedAt and is billed like a fresh row; the residential surcharge is never charged on it. Set maxAgeDays to 0 to always fetch fresh. yes
fetchedAt string UTC timestamp when this row was collected. no
errorCode string Machine-readable error class for error rows. yes
errorMessage string Human-readable explanation of the error. yes

Worked examples

Software engineering jobs posted in the last 24 hours — the free results-count row comes back too
{"search_mode": "search", "keywords": "software engineer", "location": "United States", "datePosted": "past24h", "maxItems": 50}
Roles with fewer than 10 applicants at one company — company ids come from the companySuggestions mode
{"search_mode": "search", "keywords": "engineer", "companyIds": ["1441"], "under10Applicants": true, "maxItems": 50}
Look up a company's id for the company filter — one suggestion row per matching company
{"search_mode": "companySuggestions", "query": "nvidia"}
Full job records from job URLs — description, seniority, applicants and salary where published
{"search_mode": "job", "jobUrls": ["https://www.linkedin.com/jobs/view/4443416712"]}
POWER-USER TIP
Go past 1,000 results by slicing — LinkedIn's guest search stops near 1,000 results per query and the run then stops with depthCap. Run the query per week with datePosted, per geoId or per company id, and deduplicate across runs by jobId.
POWER-USER TIP
Read the results count as a floor — The count is LinkedIn's own caption, and a plus sign (7,000+) means at least that many. Every search row carries the verbatim caption, the parsed number and resultsCountIsCapped, so a daily run is a free demand index for a title and place.
POWER-USER TIP
Get the ids first, then search precisely — The companySuggestions and locationSuggestions modes return the numeric company ids and geoId values the search filters take, at the suggestion price, so a precise search never depends on guessing an id.
POWER-USER TIP
Filters run at the source — Date posted, company, Easy Apply and under-10-applicants filters are applied by LinkedIn before anything is billed. Filters LinkedIn's guest search does not reliably honor are not offered at all.
POWER-USER TIP
The rare residential row — When LinkedIn blocks the standard route, a row is fetched through a residential proxy, marked viaResidentialProxy and carries a small extra charge. Cached rows never carry it.

Coverage

about 1000
results per search query, at most — LinkedIn's guest search stops there; slice by date, place or company to go deeper
5
modes in one API — job search, job details, and title, company and location suggestions
1,000
billable rows per run — maxItems, 100 by default
7
days a full job record is reused from the cache — search pages for a few hours, suggestions for 30 days; maxAgeDays 0 always fetches fresh

Code

Search in one call

curl -X POST "https://api.apify.com/v2/acts/johnvc~linkedin-job-search-scraper/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"search_mode": "search", "keywords": "data engineer", "location": "Austin, Texas", "datePosted": "pastWeek", "maxItems": 50}'

Python

from apify_client import ApifyClient

client = ApifyClient("APIFY_TOKEN")
run = client.actor("johnvc/linkedin-job-search-scraper").call(
    run_input={"search_mode": "search", "keywords": "data engineer", "location": "Austin, Texas", "datePosted": "pastWeek", "maxItems": 50}
)
for row in client.dataset(run.default_dataset_id).iterate_items():
    if row.get("resultType") == "searchMeta":
        print("results:", row.get("resultsCountCaption"))
    else:
        print(row.get("jobTitle"), "|", row.get("companyName"), "|", row.get("location"), "|", row.get("jobUrl"))

MCP

claude mcp add --transport http linkedin-job-scraper \
  "https://mcp.apify.com/?tools=actors,docs,johnvc/linkedin-job-search-scraper"

Sample listing row (synthetic)

{
  "resultType": "jobListing",
  "jobId": "4400000001",
  "jobTitle": "Senior Data Engineer",
  "companyName": "Example Corp",
  "companyLinkedinUrl": "https://www.linkedin.com/company/example-corp",
  "location": "Austin, TX",
  "postedDate": "2026-10-01",
  "jobUrl": "https://www.linkedin.com/jobs/view/4400000001",
  "position": 1,
  "fromCache": false,
  "fetchedAt": "2026-10-03T09:15:00Z"
}

What people use it for

  • Competitor hiring velocity tracking
  • Feeding a job board with fresh listings
  • Sales prospecting on hiring signals
  • Tracking job demand by keyword and city from LinkedIn's own results count
  • Finding open roles with fewer than 10 applicants
  • Feeding an AI agent a company's open roles

More sources for Hiring signals and talent intelligence, Grounding AI agents and MCP tools →

Alternatives