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.
Input parameters
| PARAMETER | TYPE | REQ | DEFAULT | DESCRIPTION |
|---|---|---|---|---|
| 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
| FIELD | TYPE | DESCRIPTION | NULLABLE |
|---|---|---|---|
| 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
{"search_mode": "search", "keywords": "software engineer", "location": "United States", "datePosted": "past24h", "maxItems": 50}
{"search_mode": "search", "keywords": "engineer", "companyIds": ["1441"], "under10Applicants": true, "maxItems": 50}
{"search_mode": "companySuggestions", "query": "nvidia"}
{"search_mode": "job", "jobUrls": ["https://www.linkedin.com/jobs/view/4443416712"]}
Coverage
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 →