{
  "data": {
    "id": "eSNXxyDEG9rSHdVxs",
    "actId": "kkH2KvT3IfbFf6A6P",
    "userId": "9Um4NEFR7z42j35AD",
    "startedAt": "2026-10-20T05:12:43.016Z",
    "finishedAt": "2026-10-20T05:12:46.403Z",
    "status": "SUCCEEDED",
    "meta": {
      "origin": "CLI"
    },
    "stats": {
      "durationMillis": 3299,
      "runTimeSecs": 3.299,
      "computeUnits": 0.0036655555555555556,
      "imageSizeBytes": 563388416
    },
    "options": {
      "useCache": true,
      "betaPackages": false,
      "memoryMbytes": 4096,
      "diskMbytes": 8192
    },
    "exitCode": 0,
    "inputSchema": "{\n  \"title\": \"LinkedIn Jobs Scraper input\",\n  \"type\": \"object\",\n  \"schemaVersion\": 1,\n  \"properties\": {\n    \"searchTerm\": {\n      \"title\": \"Option 1: Keywords - search jobs\",\n      \"type\": \"string\",\n      \"description\": \"Search LinkedIn by job titles/keywords, optionally with locations.\",\n      \"editor\": \"textarea\",\n      \"sectionCaption\": \"Choose jobs\",\n      \"sectionDescription\": \"Use one or more fields below. If mixed, the actor combines results, removes duplicate jobs, and applies one shared Job listings to collect cap.\",\n      \"prefill\": \"software engineer\\ndata analyst\\nproduct manager\"\n    },\n    \"location\": {\n      \"title\": \"Locations\",\n      \"type\": \"string\",\n      \"description\": \"Optional for Keywords. If filled, LinkedIn searches for Keywords AND each Location. Enter one city, region, country, or Remote per line. Leave blank for broad LinkedIn results.\",\n      \"editor\": \"textarea\"\n    },\n    \"startUrls\": {\n      \"title\": \"Option 2: LinkedIn search URLs - keep filters\",\n      \"type\": \"array\",\n      \"description\": \"Paste a LinkedIn Jobs search URL when you already configured filters in LinkedIn and want to preserve them.\",\n      \"editor\": \"requestListSources\",\n      \"default\": [],\n      \"prefill\": []\n    },\n    \"jobIds\": {\n      \"title\": \"Option 3: Job IDs or job URLs - exact listings\",\n      \"type\": \"array\",\n      \"description\": \"Provide known LinkedIn job IDs/URLs to enrich specific listings without search discovery.\",\n      \"editor\": \"stringList\",\n      \"default\": [],\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"maxItems\": {\n      \"title\": \"Job listings to collect (shared cap)\",\n      \"type\": \"integer\",\n      \"description\": \"This cap applies to all 3 ways: Keywords, LinkedIn search URLs, and Job IDs or job URLs. If mixed, the actor stops when this many job listings are saved.\",\n      \"editor\": \"number\",\n      \"default\": 25,\n      \"minimum\": 1,\n      \"maximum\": 10000\n    },\n    \"postedWithin\": {\n      \"title\": \"Posted within\",\n      \"type\": \"string\",\n      \"description\": \"Only keep jobs posted in this time window. For URLs and job IDs, this is checked when LinkedIn shows a posted date.\",\n      \"editor\": \"select\",\n      \"sectionCaption\": \"Filters\",\n      \"sectionDescription\": \"Use filters to narrow the jobs you save. Keep them broad for your first test run.\",\n      \"default\": \"7d\",\n      \"enum\": [\n        \"any\",\n        \"24h\",\n        \"3d\",\n        \"7d\",\n        \"14d\",\n        \"30d\"\n      ],\n      \"enumTitles\": [\n        \"Any time\",\n        \"Last 24 hours\",\n        \"Last 3 days\",\n        \"Last 7 days\",\n        \"Last 14 days\",\n        \"Last 30 days\"\n      ]\n    },\n    \"distance\": {\n      \"title\": \"Search radius\",\n      \"type\": \"integer\",\n      \"description\": \"Search distance in miles when you enter a location.\",\n      \"editor\": \"number\",\n      \"default\": 25,\n      \"minimum\": 0,\n      \"maximum\": 100\n    },\n    \"sortBy\": {\n      \"title\": \"Sort by\",\n      \"type\": \"string\",\n      \"description\": \"Choose the order for keyword and location searches.\",\n      \"editor\": \"select\",\n      \"default\": \"relevance\",\n      \"enum\": [\n        \"relevance\",\n        \"date\"\n      ],\n      \"enumTitles\": [\n        \"Relevance\",\n        \"Date\"\n      ]\n    },\n    \"jobType\": {\n      \"title\": \"Job type\",\n      \"type\": \"string\",\n      \"description\": \"Choose one job type, or keep Any.\",\n      \"editor\": \"select\",\n      \"default\": \"any\",\n      \"enum\": [\n        \"any\",\n        \"fulltime\",\n        \"parttime\",\n        \"contract\",\n        \"temporary\",\n        \"internship\",\n        \"volunteer\"\n      ],\n      \"enumTitles\": [\n        \"Any\",\n        \"Full-time\",\n        \"Part-time\",\n        \"Contract\",\n        \"Temporary\",\n        \"Internship\",\n        \"Volunteer\"\n      ]\n    },\n    \"workplaceType\": {\n      \"title\": \"Workplace\",\n      \"type\": \"string\",\n      \"description\": \"Choose on-site, remote, hybrid, or keep Any.\",\n      \"editor\": \"select\",\n      \"default\": \"any\",\n      \"enum\": [\n        \"any\",\n        \"onsite\",\n        \"remote\",\n        \"hybrid\"\n      ],\n      \"enumTitles\": [\n        \"Any\",\n        \"On-site\",\n        \"Remote\",\n        \"Hybrid\"\n      ]\n    },\n    \"experienceLevel\": {\n      \"title\": \"Experience level\",\n      \"type\": \"string\",\n      \"description\": \"Choose one seniority level, or keep Any.\",\n      \"editor\": \"select\",\n      \"default\": \"any\",\n      \"enum\": [\n        \"any\",\n        \"internship\",\n        \"entry\",\n        \"associate\",\n        \"midsenior\",\n        \"director\",\n        \"executive\"\n      ],\n      \"enumTitles\": [\n        \"Any\",\n        \"Internship\",\n        \"Entry level\",\n        \"Associate\",\n        \"Mid-Senior\",\n        \"Director\",\n        \"Executive\"\n      ]\n    },\n    \"maxPagesPerSearch\": {\n      \"title\": \"Page depth\",\n      \"type\": \"integer\",\n      \"description\": \"How many result pages to check for each keyword/location or search URL. Increase slowly for larger runs.\",\n      \"editor\": \"number\",\n      \"default\": 2,\n      \"minimum\": 1,\n      \"maximum\": 100\n    },\n    \"excludeCompanies\": {\n      \"title\": \"Skip employers\",\n      \"type\": \"string\",\n      \"description\": \"Optional. Enter one employer per line. Matching jobs are skipped and counted in the run summary.\",\n      \"editor\": \"textarea\",\n      \"default\": \"\"\n    },\n    \"titleMustInclude\": {\n      \"title\": \"Title must include\",\n      \"type\": \"string\",\n      \"description\": \"Optional. Enter one word or phrase per line. A job is saved only if its title contains one of these.\",\n      \"editor\": \"textarea\",\n      \"default\": \"\"\n    },\n    \"titleExclude\": {\n      \"title\": \"Skip titles containing\",\n      \"type\": \"string\",\n      \"description\": \"Optional. Enter one word or phrase per line. Jobs with these words in the title are skipped.\",\n      \"editor\": \"textarea\",\n      \"default\": \"\"\n    },\n    \"saveOnlyUniqueItems\": {\n      \"title\": \"Unique jobs only (remove duplicates)\",\n      \"type\": \"boolean\",\n      \"description\": \"Save each LinkedIn job only once, even if it appears from more than one keyword, URL, or ID. Keep this on for normal runs.\",\n      \"editor\": \"checkbox\",\n      \"sectionCaption\": \"Quality controls\",\n      \"sectionDescription\": \"Use these controls to save cleaner jobs or add extra fields. The defaults are best for most users.\",\n      \"default\": true,\n      \"prefill\": true\n    },\n    \"balanceKeywordCoverage\": {\n      \"title\": \"Balance keyword coverage\",\n      \"type\": \"boolean\",\n      \"description\": \"Give each keyword and location a turn before going deeper. Useful when you enter more than one keyword or location.\",\n      \"editor\": \"checkbox\",\n      \"default\": true\n    },\n    \"fetchJobDetails\": {\n      \"title\": \"Full descriptions and details\",\n      \"type\": \"boolean\",\n      \"description\": \"Keep on to collect full descriptions, criteria, applicant text, and Easy Apply signals when LinkedIn shows them.\",\n      \"editor\": \"checkbox\",\n      \"default\": true\n    },\n    \"requestDelayMillis\": {\n      \"title\": \"Delay between requests\",\n      \"type\": \"integer\",\n      \"description\": \"Wait time between LinkedIn requests. Values below 500 ms are raised automatically.\",\n      \"editor\": \"number\",\n      \"sectionCaption\": \"Run tuning\",\n      \"sectionDescription\": \"Tune request pacing for larger runs. Defaults are recommended for most users.\",\n      \"default\": 500,\n      \"minimum\": 500,\n      \"maximum\": 60000\n    }\n  }\n}",
    "readme": "## 💼 What does LinkedIn Jobs Scraper do?\n\nLinkedIn Jobs Scraper extracts clean, deduplicated data from public\n[LinkedIn](https://www.linkedin.com/jobs/) job listings: job title, company\nname, location, posted date, applicant text, employment criteria, salary signals,\nfull description text and HTML when available, Easy Apply status, LinkedIn job\nURLs, and more.\n\nTo get that data, enter a job title or keyword, paste a LinkedIn Jobs search\nURL, or add known LinkedIn job IDs or URLs. Locations are optional, so you can\nrun broad keyword searches or narrow them to specific markets. You can also run\nthe scraper through the Apify API, schedule it, or connect it to other tools.\nIt saves one row per job by default, so overlapping keywords, locations, search\nURLs, job IDs, and pages do not fill your dataset with duplicate listings.\n\nWith LinkedIn Jobs Scraper, you can:\n\n- Scrape LinkedIn job listings by job title, keyword, company, role family, and\n  location.\n- Extract detailed job posting data, including description text and HTML when\n  available.\n- Collect company names, company URLs, logo URLs, locations, salary signals,\n  applicant text, job criteria, Easy Apply status, and LinkedIn job URLs.\n- Filter LinkedIn jobs by posting date, radius, job type, workplace type,\n  experience level, employer exclusions, title filters, and sort order.\n- Run keyword/location searches, paste LinkedIn search URLs, or enrich known job\n  IDs from the form, JSON, or API input.\n- Enrich known LinkedIn job IDs or job URLs without running search discovery.\n- Save clean unique job listings with duplicate skips, title filters, and\n  run-summary counters.\n- Download LinkedIn job data in JSON, JSONL, CSV, Excel, XML, RSS, or HTML.\n- Check the run summary to see how many jobs were found, saved, skipped,\n  duplicated, filtered, blocked, or stopped.\n\n## 📦 What job listing data can I extract?\n\nWith this Actor, you can extract the following data from public LinkedIn job\npostings:\n\n|                          |                       |                       |\n| ------------------------ | --------------------- | --------------------- |\n| 💼 Job title             | 🏢 Company name       | 📍 Job location       |\n| 📝 Full description text | 🧾 Description HTML   | 🔎 Description status |\n| 💰 Salary range          | 💵 Salary currency    | ⏱️ Salary period      |\n| 🧑‍💻 Employment type       | 🪜 Seniority          | 🏠 Workplace / remote |\n| 🧰 Job function          | 🏷️ Industries         | 👥 Applicant text     |\n| 🔗 Company URL           | 🖼️ Company logo URL   | ⚡ Easy Apply status  |\n| 🌐 LinkedIn job URL      | 🔍 Search keyword     | 🧭 Search location    |\n| 📊 Jobs saved            | ♻️ Duplicates skipped | 🚦 Stop reason        |\n\nHere are the main output groups:\n\n| Data group              | Example fields                                                                                            |\n| ----------------------- | --------------------------------------------------------------------------------------------------------- |\n| 💼 Job details          | `jobId`, `title`, `employmentType`, `seniority`, `jobFunction`, `industries`, `isEasyApply`               |\n| 🏢 Company details      | `companyName`, `companyUrl`, `companyLogoUrl`                                                             |\n| 📍 Location             | `locationRaw`, `locationCity`, `locationRegion`, `locationCountry`, `workplaceType`, `isRemote`           |\n| 💰 Salary               | `salaryRaw`, `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod`                                   |\n| 📝 Description          | `descriptionSnippet`, `descriptionText`, `descriptionHtml`, `descriptionStatus`                           |\n| 🔗 Links and enrichment | `jobUrl`, `isEasyApply`, `isExpired`                                                                      |\n| 🧭 Search provenance    | `query`, `searchLocation`, `searchPosition`, `pageNumber`                                                 |\n| 📊 Run summary          | `candidateItemsSeen`, `jobsSaved`, `duplicateCandidatesSeen`, `duplicatesSkipped`, warnings, `stopReason` |\n\nSalary, applicant counts, Easy Apply signals, and full descriptions depend on\nwhat LinkedIn exposes for each public job listing. If full job descriptions\nmatter for your workflow, check the `descriptionStatus` field in the output and\nthe detail counters in the run summary.\n\n## ⚙️ Can I use this Actor through an API?\n\nYes. You can run LinkedIn Jobs Scraper manually in Apify Console or use it as an\nAPI for LinkedIn job data.\n\nDevelopers can call this Actor through:\n\n- Apify API\n- Python SDK\n- Node.js SDK\n- Webhooks\n- Scheduled runs\n- Apify integrations\n\nThis makes it useful for recruiting pipelines, job-board enrichment, hiring\ndashboards, warehouse loads, agents, and automated labor-market research.\n\n## Clean, duplicate-free LinkedIn job datasets\n\nLinkedIn job results can overlap across related keywords, locations, search\nURLs, known job IDs, and pages. For normal datasets, keep\n`saveOnlyUniqueItems` enabled so the Actor saves one row per job and skips\nduplicate candidates before they become saved rows.\n\nThe `OUTPUT` summary shows `candidateItemsSeen`, `duplicateCandidatesSeen`,\n`duplicatesSkipped`, and `jobsSaved`, so you can see how many matching rows were\nfound, how many were duplicates, and how many unique job listings were saved.\nTurn unique-job mode off only when you intentionally want to measure keyword\noverlap or search ranking behavior.\n\n## 📊 Why scrape LinkedIn jobs?\n\nLinkedIn is a major source of job market data. Scraping LinkedIn jobs can help\nyou track hiring demand, role language, salary signals, employer activity, and\nremote-work patterns across markets.\n\nHere are just some of the ways you could use LinkedIn job data:\n\n| Use case                      | How LinkedIn job data helps                                                   |\n| ----------------------------- | ----------------------------------------------------------------------------- |\n| 📈 Monitor hiring trends      | Track demand by role, skill, location, company, seniority, or workplace type. |\n| 💰 Benchmark salaries         | Compare salary signals across markets and job families where available.       |\n| 🧲 Build recruiting workflows | Collect active job listings for sourcing, staffing, and lead generation.      |\n| 🏢 Track competitors          | Watch hiring activity, expansion signals, and technology adoption.            |\n| 🧪 Research labor markets     | Analyze job descriptions, criteria, applicant text, and company demand.       |\n| 🧱 Enrich job boards          | Feed clean LinkedIn job data into apps, databases, and career tools.          |\n\n## LinkedIn jobs scraper with no login or cookies\n\nLinkedIn Jobs Scraper works with public LinkedIn Jobs surfaces. You do not need\nto provide a LinkedIn account, login session, or browser cookies. Paste a role\nand optional location, a LinkedIn Jobs search URL, or known LinkedIn job IDs and run the\nActor from Apify Console or API.\n\n## Scrape LinkedIn jobs by job ID or URL\n\nUse `jobIds` when you already know the LinkedIn job IDs or job URLs you want to\nenrich. This mode skips search discovery and focuses on the known job listings,\nwhich is useful for job-board enrichment, saved lead lists, and repeatable data\nrefreshes.\n\nUse `startUrls` when you already built a LinkedIn Jobs search in the LinkedIn UI\nand want to preserve those search filters in the Actor run.\n\nIf you provide more than one source type in the same run, the Actor combines\nthem, removes duplicate jobs when `saveOnlyUniqueItems` is enabled, and applies\none shared `maxItems` cap across the whole run.\n\n## LinkedIn Easy Apply, salary, and criteria data\n\nWhen public job details are available, the Actor can add Easy Apply status,\nsalary signals, applicant text, seniority, employment type, workplace type, job\nfunction, industries, full description text, and description HTML. Check\n`descriptionStatus` and the run summary to see which detail fields were exposed\nfor each run.\n\n## 💵 How much does scraping LinkedIn jobs cost?\n\nLinkedIn Jobs Scraper uses a Pay-Per-Event pricing model. You are charged for\nsaved job listings.\n\nThe current price is **$1.50 per 1,000 saved job listings**, or **$0.0015 per\njob listing**. For example:\n\n| Saved job listings | Estimated Actor charge |\n| ------------------ | ---------------------- |\n| 100 jobs           | $0.15                  |\n| 1,000 jobs         | $1.50                  |\n| 10,000 jobs        | $15.00                 |\n\nStart with `maxItems` between 10 and 100 if you want to test the output before\nrunning larger searches. The run summary shows how many candidate jobs were\nseen, how many unique jobs were saved, and why the run stopped. For normal runs,\nkeep **Unique jobs only** enabled so duplicate candidates are skipped before\nthey become saved rows.\n\n## 🚀 How do I use LinkedIn Jobs Scraper?\n\nLinkedIn Jobs Scraper was designed for an easy start even if you've never\nextracted data from the web before. Here's how you can scrape LinkedIn job data\nwith this tool:\n\n1. Create or log in to your Apify account.\n2. Open **LinkedIn Jobs Scraper**.\n3. Choose a source: add job titles or keywords, paste LinkedIn Jobs search URLs,\n   or add known LinkedIn job IDs or URLs.\n4. Optionally add one or more locations for keyword searches. Location narrows\n   the keyword search.\n5. Set the maximum number of job listings to collect.\n6. Add filters such as posted date, radius, job type, workplace type, experience\n   level, employer exclusions, or title filters if needed.\n7. Click **Start** and wait for the data to be extracted.\n8. Download your data in JSON, JSONL, CSV, Excel, XML, RSS, or HTML.\n\n## ⬇️ Input\n\nThe input needs at least one way to choose jobs:\n\n- **Option 1: Keywords** - Search LinkedIn by job titles/keywords, optionally\n  with locations.\n- **Option 2: LinkedIn search URLs** - Paste a LinkedIn Jobs search URL when you\n  already configured filters in LinkedIn and want to preserve them.\n- **Option 3: Job IDs or job URLs** - Provide known LinkedIn job IDs/URLs to\n  enrich specific listings without search discovery.\n\nThey can also be mixed in one run. If mixed, the actor combines results,\ndeduplicates them, and applies one shared Job listings to collect cap.\n\nLocations are optional for keyword searches. If filled, LinkedIn searches for\nKeywords AND each Location. LinkedIn search URLs keep their own filters, and job\nIDs or job URLs target exact listings.\n\nYou can also add optional filters for date, radius, job type, workplace type,\nexperience level, page depth, employer exclusions, and title filters.\n\nHere's a simple input example for a broad software engineer search:\n\n```json\n{\n    \"searchTerm\": \"software engineer\",\n    \"location\": \"\",\n    \"postedWithin\": \"7d\",\n    \"distance\": 25,\n    \"sortBy\": \"relevance\",\n    \"maxItems\": 25,\n    \"maxPagesPerSearch\": 2,\n    \"balanceKeywordCoverage\": true,\n    \"saveOnlyUniqueItems\": true,\n    \"fetchJobDetails\": true,\n    \"jobType\": \"any\",\n    \"workplaceType\": \"any\",\n    \"experienceLevel\": \"any\"\n}\n```\n\nClick on the **Input** tab for a full explanation of input in JSON.\n\n### 🔎 Search terms\n\nUse one job title, skill, company, or role family per line. This field is\noptional when you use LinkedIn search URLs or job IDs. Multiple distinct search\nterms can help you collect broader job market data.\n\nGood search terms:\n\n```text\ndata engineer\nanalytics engineer\ndata scientist\nsoftware engineer\nproduct manager\n```\n\nOverlapping search terms can return the same job more than once. Keep\n`saveOnlyUniqueItems` enabled unless you are measuring keyword overlap or search\nranking.\n\nIf LinkedIn returns broad results for a role, add title filters. This is useful\nfor searches like `backend engineer`, `CTO`, or `data analyst`, where broad\nkeyword matching can include off-target roles.\n\n```json\n{\n    \"searchTerm\": \"backend engineer\",\n    \"location\": \"Germany\",\n    \"titleMustInclude\": \"backend engineer\\nplatform engineer\",\n    \"titleExclude\": \"frontend\\nrecruiter\\nsales\",\n    \"maxItems\": 100\n}\n```\n\n### 📍 Locations\n\nAdd one city, region, country, or remote-friendly location per line. Locations\nare only used with keyword searches. Leave the field blank for broader LinkedIn\nresults. LinkedIn handles geography from each location value you provide.\n\nGood locations:\n\n```text\nToronto, ON, Canada\nNew York, NY\nSan Francisco Bay Area\nRemote\nUnited States\n```\n\nJSON/API users can run a location matrix by passing `locations` as an array.\nFor example, three keywords and two locations create six keyword/location\nsearches:\n\n```json\n{\n    \"searchTerm\": \"data scientist\",\n    \"locations\": [\"Toronto, ON, Canada\", \"New York, NY\", \"Seattle, WA\"],\n    \"maxItems\": 300\n}\n```\n\n### 🎛️ Filters, quality controls, and run tuning\n\n| Option                      | What it does                                                                 |\n| --------------------------- | ---------------------------------------------------------------------------- |\n| 📅 `postedWithin`           | Searches jobs from the selected freshness window.                            |\n| 📍 `distance`               | Searches around the selected location.                                       |\n| 🔀 `sortBy`                 | Sorts LinkedIn results by relevance or date.                                 |\n| 🧑‍💻 `jobType`                | Filters full-time, part-time, contract, temporary, internship, or volunteer. |\n| 🏠 `workplaceType`          | Filters on-site, remote, or hybrid jobs.                                     |\n| 🪜 `experienceLevel`        | Filters by LinkedIn seniority level when available.                          |\n| 📄 `maxPagesPerSearch`      | Sets the page depth for each keyword/location or search URL.                 |\n| 🚫 `excludeCompanies`       | Skips matching employers and counts skipped rows in the run summary.         |\n| 🎯 `titleMustInclude`       | Saves jobs whose title contains at least one required word or phrase.        |\n| 🧹 `titleExclude`           | Skips jobs whose title contains off-target words or phrases.                 |\n| ♻️ `saveOnlyUniqueItems`    | Saves one row per job across overlapping keywords, locations, URLs, pages.   |\n| ⚖️ `balanceKeywordCoverage` | Spreads results across keywords and locations before going deeper.           |\n| 📝 `fetchJobDetails`        | Adds full descriptions, criteria, applicant text, and Easy Apply signals.    |\n| ⏱️ `requestDelayMillis`     | Controls delay between requests. The Actor enforces a 500 ms minimum.        |\n\n### 🔗 LinkedIn search URLs and job IDs\n\nYou can also start from LinkedIn search URLs or known job IDs.\n\nUse `startUrls` when you already built a search in LinkedIn and want to preserve\nits filters:\n\n```json\n{\n    \"startUrls\": [\n        {\n            \"url\": \"https://www.linkedin.com/jobs/search/?keywords=data%20engineer&location=Toronto%2C%20ON\"\n        }\n    ],\n    \"maxItems\": 50,\n    \"fetchJobDetails\": true\n}\n```\n\nUse `jobIds` when you already have LinkedIn job IDs or job URLs and only want\ndetail enrichment:\n\n```json\n{\n    \"jobIds\": [\"<linkedin-job-id-or-url>\"],\n    \"maxItems\": 1,\n    \"fetchJobDetails\": true\n}\n```\n\nLeave `jobIds` empty unless you already have known listings to enrich. The form\ndoes not include seeded job IDs.\n\n## ⬆️ Output sample\n\nThe results will be wrapped into a dataset which you can find in the **Output**\ntab. Each result is one saved LinkedIn job listing.\n\nFor easier postprocessing, export the results to one of the supported formats:\nJSON, JSONL, CSV, Excel spreadsheet, XML, RSS, or HTML table.\n\nHere's an example of the exported JSON output:\n\n```json\n{\n    \"jobId\": \"4428089436\",\n    \"title\": \"Data Engineer - Python, ETL\",\n    \"companyName\": \"Example Company\",\n    \"companyUrl\": \"https://www.linkedin.com/company/example\",\n    \"companyLogoUrl\": \"https://media.licdn.com/example-logo\",\n    \"locationRaw\": \"Toronto, Ontario, Canada\",\n    \"locationCity\": \"Toronto\",\n    \"locationRegion\": \"Ontario\",\n    \"locationCountry\": \"Canada\",\n    \"workplaceType\": \"hybrid\",\n    \"isRemote\": false,\n    \"salaryRaw\": \"$110,000-$130,000 CAD\",\n    \"salaryMin\": 110000,\n    \"salaryMax\": 130000,\n    \"salaryCurrency\": \"CAD\",\n    \"salaryPeriod\": \"year\",\n    \"employmentType\": \"fulltime\",\n    \"seniority\": \"Mid-Senior level\",\n    \"jobFunction\": \"Information Technology\",\n    \"industries\": [\"IT Services and IT Consulting\"],\n    \"postedAtRaw\": \"5 days ago\",\n    \"postedAt\": \"2026-06-16T20:35:49.490Z\",\n    \"applicantsRaw\": \"Be among the first 25 applicants\",\n    \"descriptionSnippet\": \"Build reliable data systems...\",\n    \"descriptionText\": \"Full job description text...\",\n    \"descriptionHtml\": \"<p>Full job description HTML...</p>\",\n    \"descriptionStatus\": \"full\",\n    \"jobUrl\": \"https://www.linkedin.com/jobs/view/example-4428089436\",\n    \"query\": \"data engineer\",\n    \"searchLocation\": \"Toronto, ON, Canada\",\n    \"verifiedAt\": \"2026-06-21T20:35:49.490Z\",\n    \"isExpired\": false,\n    \"verificationSource\": \"detail\",\n    \"scrapedAt\": \"2026-06-21T20:35:49.490Z\"\n}\n```\n\n### 📌 Run summary\n\nLinkedIn Jobs Scraper also saves an `OUTPUT` summary in the default key-value\nstore. Use it to see why the final number of saved jobs changed.\n\n```json\n{\n    \"stopReason\": \"max_items_reached\",\n    \"searchesQueued\": 6,\n    \"pagesParsed\": 18,\n    \"requestedMaxItems\": 100,\n    \"candidateItemsSeen\": 142,\n    \"jobsSaved\": 100,\n    \"detailRequests\": 100,\n    \"detailPagesParsed\": 99,\n    \"detailFailures\": 1,\n    \"duplicateCandidatesSeen\": 12,\n    \"duplicatesSkipped\": 12,\n    \"rowsExcludedByCompany\": 0,\n    \"rowsExcludedByDate\": 0,\n    \"rowsExcludedByJobType\": 0,\n    \"rowsExcludedByWorkplace\": 0,\n    \"rowsExcludedByExperience\": 0,\n    \"rowsExcludedByTitle\": 0,\n    \"blockedRequests\": 1,\n    \"failedRequests\": 0,\n    \"warnings\": [],\n    \"statusMessage\": \"Saved 100 LinkedIn job listings.\"\n}\n```\n\n## ❓ Why did I get fewer jobs than requested?\n\n`maxItems` is a cap on saved rows, not a guarantee that every search has that\nmany matching jobs.\n\nIf your run saves fewer jobs than requested, check the `OUTPUT` summary. It\nshows whether rows were skipped because of duplicates, employer exclusions, date\nfilters, job-type filters, workplace filters, experience filters, title filters,\nsource exhaustion, page limits, failed requests, or blocked detail pages.\nIf the run setup is incomplete or uses an unsupported option, the Actor still\nfinishes and writes an `OUTPUT` summary. Check `OUTPUT.statusMessage` and\n`OUTPUT.validationErrors` to see what needs to be fixed before running again.\nThis usually happens when running through the API, an integration, a saved task,\nor a form submission where all job source fields were left empty.\n\nExample setup issue:\n\n```json\n{\n    \"stopReason\": \"invalid_input\",\n    \"jobsSaved\": 0,\n    \"validationErrors\": [\n        {\n            \"field\": \"source\",\n            \"code\": \"missing_source\",\n            \"message\": \"Provide at least one keyword, LinkedIn search URL, or LinkedIn job ID.\"\n        }\n    ],\n    \"statusMessage\": \"Input was not valid: Provide at least one keyword, LinkedIn search URL, or LinkedIn job ID.\"\n}\n```\n\nCommon reasons include:\n\n- The selected role and location have fewer available jobs than `maxItems`.\n- Strict filters removed many candidate rows.\n- Title include/exclude filters removed broad or off-target LinkedIn matches.\n- Multiple keywords returned the same jobs and unique-job mode skipped\n  duplicates.\n- LinkedIn did not expose a detail page for every discovered job.\n- The search depth limit was reached before enough unique matching jobs were\n  found.\n\nIf you need more rows, increase `maxPagesPerSearch`, relax strict filters, add\nmore distinct keywords, expand the search radius, or search broader locations.\n\n## ✅ Tips for scraping LinkedIn jobs\n\n- Start with a small test run and inspect the dataset before increasing\n  `maxItems`.\n- Use distinct keywords instead of near-duplicates.\n- Keep `saveOnlyUniqueItems` enabled for normal datasets.\n- Use `balanceKeywordCoverage` for market maps across multiple role families.\n- Use `titleMustInclude` and `titleExclude` when LinkedIn returns broad or\n  off-target roles.\n- Use `postedWithin` for fresh job monitoring.\n- Keep `fetchJobDetails` enabled when full descriptions matter.\n- Increase `maxPagesPerSearch` gradually for larger runs.\n- Check `descriptionStatus` when full job descriptions are important.\n- Check the run summary for duplicates, detail failures, blocked requests, and\n  stop reason.\n\n## ❓ FAQ\n\n### Does LinkedIn Jobs Scraper need login or cookies?\n\nNo. You do not need to provide a LinkedIn account, login session, or browser\ncookies.\n\n### Can I use LinkedIn Jobs Scraper through an API?\n\nYes. Run it with the Apify API, Python SDK, Node.js SDK, webhooks, schedules, or\nintegrations. The dataset contains saved job rows and the `OUTPUT` key-value\nstore record contains the run summary.\n\n### Can I scrape LinkedIn jobs by job ID?\n\nYes. Use the `jobIds` input with LinkedIn job IDs or job URLs when you already\nknow which listings you want to enrich.\n\n### Why are salaries, applicant text, or full descriptions sometimes missing?\n\nThose fields depend on what LinkedIn exposes publicly for each listing. Use\n`descriptionStatus`, salary fields, and run-summary detail counters to separate\ncomplete rows from partial rows.\n\n### How do I avoid duplicate jobs?\n\nKeep `saveOnlyUniqueItems` enabled. The Actor saves one row per job across\noverlapping keywords, locations, search URLs, job IDs, and pages. The `OUTPUT`\nsummary reports `candidateItemsSeen`, `duplicateCandidatesSeen`,\n`duplicatesSkipped`, and `jobsSaved`, so you can audit how many candidates were\nfound, skipped as duplicates, and saved as unique job listings.\n\n### How much does it cost?\n\nThe current Actor charge is **$1.50 per 1,000 saved job listings**. Check the\nPricing tab before production runs because live Store pricing is the source of\ntruth.\n\n## ⚖️ Is it legal to scrape LinkedIn jobs?\n\nThis Actor extracts public job listing data. You are responsible for making sure\nyour use case complies with applicable laws, platform terms, and privacy rules.\n\nJob listings can sometimes include personal data or contact details. You should\nnot scrape, store, or process personal data unless you have a legitimate reason\nand the right legal basis. If you're unsure whether your reason is legitimate,\nconsult your lawyers.\n\nLinkedIn is a trademark of its owner. This Actor is independent and is not\naffiliated with, endorsed by, or sponsored by LinkedIn.\n\n## 🛟 Support\n\nIf a run does not return what you expected, open an issue with the run ID and\nthe input you used. The run summary shows saved rows, skipped rows, filters,\nwarnings, detail counters, and stop reason so support can be specific.\n\nRelated searches: LinkedIn scraper, LinkedIn jobs scraper, scrape LinkedIn jobs,\nLinkedIn job listings, remove duplicate jobs, duplicate-free job scraper, unique\njob listings, deduped job listings, job board deduplication, LinkedIn job\ndescriptions, LinkedIn jobs no cookies, LinkedIn job ID scraper, LinkedIn jobs\nsearch URL scraper, LinkedIn Easy Apply scraper, LinkedIn salary data, job\nmarket data, recruiting data, HR analytics, salary benchmarking, job board data,\ncompany hiring signals.\n\n## 🔗 Related job scrapers\n\nCompare public job and freelance demand across these sources.\n\n### Compare jobs across sources\n\n- [Indeed Jobs Scraper](https://apify.com/chronometrica/indeed-jobs-scraper)\n  — find public Indeed jobs, salaries, skills, and apply links.\n- [Google Jobs Scraper](https://apify.com/chronometrica/google-jobs-scraper)\n  — find public job listings, employers, locations, and apply links.\n- [Naukri Jobs Scraper](https://apify.com/chronometrica/naukri-jobs-scraper)\n  — find public India job listings, employers, locations, and skills.\n- [Upwork Job Scraper](https://apify.com/chronometrica/upwork-job-scraper)\n  — find fresh freelance jobs, skills, budgets, and client signals.\n\n### Continue on LinkedIn\n\n- [LinkedIn Profile Scraper](https://apify.com/chronometrica/linkedin-profile-scraper)\n  — collect public profile identity, headlines, and visible profile facts.\n",
    "changelog": "# Changelog\n\n## 2026-10-20\n\n- Fixed an issue that could stop valid job searches.\n- Runs now show a failed status if an error prevents any jobs from being saved.\n\n## 2026-07-01 - Public Run Reliability\n\n### Changed\n\n- Improved run reliability for public users: runs with incomplete setup or no\n  matching jobs now finish with a clear run summary instead of ending as failed\n  runs.\n- Added clearer run-summary details for setup issues, so users can see what to\n  fix in the `OUTPUT` record.\n\n## 2026-06-24 - Public Surface Alignment\n\n### Changed\n\n- Aligned the public example input, Store metadata, and README first-run flow.\n- Kept Actor metadata focused on buyer-facing fields.\n- Confirmed public rows omit request URLs and external apply-link details that\n  are not part of the advertised output contract.\n\n## 2026-06-22 - Input Form QOL Pass\n\n### Changed\n\n- Reorganized the input form around keyword search, direct LinkedIn sources,\n  filters, quality controls, and advanced run controls.\n- Removed saved sample defaults from keyword, location, and job-ID fields while\n  keeping keyword, search-URL, and job-ID workflows supported.\n- Kept location blank and optional for broad LinkedIn job searches.\n- Added a clear validation path for inputs that do not include any keyword,\n  LinkedIn search URL, or LinkedIn job ID.\n- Clarified that mixed keyword, search-URL, and job-ID inputs are combined,\n  deduplicated, and limited by one saved-job cap.\n- Rewrote input form help text in simpler language around the three ways to\n  choose jobs.\n- Folded all three collection methods and the shared job-listing cap into one\n  form section.\n- Moved the collection-method explanation into the individual field labels to\n  reduce repeated text in the form.\n- Renamed the three collection labels to Option 1, Option 2, and Option 3 so\n  they read as choices rather than required steps.\n- Clarified that Locations narrow keyword searches rather than acting as a\n  separate collection method.\n- Moved page depth, employer filters, and title filters into the Filters\n  section, and moved request pacing into Run tuning.\n\n## 2026-06-22 - Duplicate-Free Jobs Positioning Pass\n\n### Changed\n\n- Updated Store SEO metadata, README positioning, pricing copy, FAQ, and input\n  form microcopy to make unique saved job listings and duplicate-skip accounting\n  clearer.\n- Kept the public Actor title, slug, input keys, output schema, dataset schema,\n  pricing, and runtime behavior unchanged.\n\n## 2026-06-22 - Store Ranking And Quality Score Pass\n\n### Changed\n\n- Added SEO title, SEO description, Store category metadata, and the official\n  changelog reference for stronger Store congruency.\n- Reduced default run size and search depth so first runs and automated Store\n  health checks complete faster while larger production runs remain supported.\n- Expanded the README with long-tail use-case sections, no-login positioning,\n  FAQ entries, pricing examples, and related job-scraper links.\n\n## 2026-06-21 - LinkedIn Jobs Scraper MVP\n\n### Added\n\n- Created the initial LinkedIn jobs actor.\n- Added public LinkedIn guest search discovery by keyword/location or search URL.\n- Added LinkedIn job-ID detail enrichment from public job detail fragments.\n- Added normalized dataset rows with job, company, location, description,\n  criteria, search provenance, and run metadata fields.\n- Added run-summary accounting for candidates, details, saved rows, duplicates,\n  filters, failures, blocks, warnings, stop reason, and normalized input.\n- Added input, output, and dataset schemas for Apify Console testing.\n- Added parser/unit tests for URL construction, search cards, and detail pages.\n\n## 2026-06-21 - Reliability Pass 1\n\n### Changed\n\n- Made balanced keyword coverage actually distribute saved rows across active\n  keyword/location slots when `maxItems` is small.\n- Preserved source-applied LinkedIn filters on discovered rows so remote,\n  job-type, and experience-level filters do not get erased when detail pages\n  omit those labels.\n- Kept `isRemote` consistent with `workplaceType: \"remote\"` after detail merge.\n- Kept source seniority when detail pages only report `Not Applicable`.\n\n### Validated\n\n- Build `0.1.4` passed local tests, lint, formatting, and Apify schema\n  validation.\n- Cloud probes covered job-ID enrichment, basic search, remote filter, job-type\n  filter, experience-level filter, search URL input, balanced keyword coverage,\n  duplicate overlap, and employer exclusion.\n- Final remote-filter smoke run `qRarDHlsVcmfQhOQd` saved 2 full-description\n  remote rows with no blocks or failures.\n",
    "actorDefinition": {
      "$schema": "https://apify.com/schemas/v1/actor.ide.json",
      "actorSpecification": 1,
      "name": "linkedin-jobs-scraper",
      "title": "LinkedIn Jobs Scraper",
      "description": "Scrape LinkedIn job listings by keyword, location, search URL, or job ID. Save clean unique job rows by default, skip duplicates across overlapping searches and URLs, and extract companies, posted dates, full descriptions, salary signals, criteria, applicant text, Easy Apply status, and job URLs.",
      "seoTitle": "LinkedIn Jobs Scraper | Unique Jobs API",
      "seoDescription": "Scrape LinkedIn jobs by keyword, location, search URL, or job ID. Save unique job listings, skip duplicates, and get descriptions, criteria, salary signals, and run-summary counts.",
      "categories": [
        "JOBS",
        "LEAD_GENERATION"
      ],
      "version": "0.2",
      "buildTag": "latest",
      "defaultRunOptions": {
        "build": "latest",
        "timeoutSecs": 3600,
        "memoryMbytes": 512
      },
      "exampleRunInput": {
        "contentType": "application/json; charset=utf-8",
        "body": "{\n    \"searchTerm\": \"software engineer\",\n    \"location\": \"\",\n    \"postedWithin\": \"7d\",\n    \"distance\": 25,\n    \"sortBy\": \"relevance\",\n    \"maxItems\": 25,\n    \"maxPagesPerSearch\": 2,\n    \"balanceKeywordCoverage\": true,\n    \"saveOnlyUniqueItems\": true,\n    \"fetchJobDetails\": true,\n    \"jobType\": \"any\",\n    \"workplaceType\": \"any\",\n    \"experienceLevel\": \"any\"\n}"
      },
      "input": {
        "title": "LinkedIn Jobs Scraper input",
        "type": "object",
        "schemaVersion": 1,
        "properties": {
          "searchTerm": {
            "title": "Option 1: Keywords - search jobs",
            "type": "string",
            "description": "Search LinkedIn by job titles/keywords, optionally with locations.",
            "editor": "textarea",
            "sectionCaption": "Choose jobs",
            "sectionDescription": "Use one or more fields below. If mixed, the actor combines results, removes duplicate jobs, and applies one shared Job listings to collect cap.",
            "prefill": "software engineer\ndata analyst\nproduct manager"
          },
          "location": {
            "title": "Locations",
            "type": "string",
            "description": "Optional for Keywords. If filled, LinkedIn searches for Keywords AND each Location. Enter one city, region, country, or Remote per line. Leave blank for broad LinkedIn results.",
            "editor": "textarea"
          },
          "startUrls": {
            "title": "Option 2: LinkedIn search URLs - keep filters",
            "type": "array",
            "description": "Paste a LinkedIn Jobs search URL when you already configured filters in LinkedIn and want to preserve them.",
            "editor": "requestListSources",
            "default": [],
            "prefill": []
          },
          "jobIds": {
            "title": "Option 3: Job IDs or job URLs - exact listings",
            "type": "array",
            "description": "Provide known LinkedIn job IDs/URLs to enrich specific listings without search discovery.",
            "editor": "stringList",
            "default": [],
            "items": {
              "type": "string"
            }
          },
          "maxItems": {
            "title": "Job listings to collect (shared cap)",
            "type": "integer",
            "description": "This cap applies to all 3 ways: Keywords, LinkedIn search URLs, and Job IDs or job URLs. If mixed, the actor stops when this many job listings are saved.",
            "editor": "number",
            "default": 25,
            "minimum": 1,
            "maximum": 10000
          },
          "postedWithin": {
            "title": "Posted within",
            "type": "string",
            "description": "Only keep jobs posted in this time window. For URLs and job IDs, this is checked when LinkedIn shows a posted date.",
            "editor": "select",
            "sectionCaption": "Filters",
            "sectionDescription": "Use filters to narrow the jobs you save. Keep them broad for your first test run.",
            "default": "7d",
            "enum": [
              "any",
              "24h",
              "3d",
              "7d",
              "14d",
              "30d"
            ],
            "enumTitles": [
              "Any time",
              "Last 24 hours",
              "Last 3 days",
              "Last 7 days",
              "Last 14 days",
              "Last 30 days"
            ]
          },
          "distance": {
            "title": "Search radius",
            "type": "integer",
            "description": "Search distance in miles when you enter a location.",
            "editor": "number",
            "default": 25,
            "minimum": 0,
            "maximum": 100
          },
          "sortBy": {
            "title": "Sort by",
            "type": "string",
            "description": "Choose the order for keyword and location searches.",
            "editor": "select",
            "default": "relevance",
            "enum": [
              "relevance",
              "date"
            ],
            "enumTitles": [
              "Relevance",
              "Date"
            ]
          },
          "jobType": {
            "title": "Job type",
            "type": "string",
            "description": "Choose one job type, or keep Any.",
            "editor": "select",
            "default": "any",
            "enum": [
              "any",
              "fulltime",
              "parttime",
              "contract",
              "temporary",
              "internship",
              "volunteer"
            ],
            "enumTitles": [
              "Any",
              "Full-time",
              "Part-time",
              "Contract",
              "Temporary",
              "Internship",
              "Volunteer"
            ]
          },
          "workplaceType": {
            "title": "Workplace",
            "type": "string",
            "description": "Choose on-site, remote, hybrid, or keep Any.",
            "editor": "select",
            "default": "any",
            "enum": [
              "any",
              "onsite",
              "remote",
              "hybrid"
            ],
            "enumTitles": [
              "Any",
              "On-site",
              "Remote",
              "Hybrid"
            ]
          },
          "experienceLevel": {
            "title": "Experience level",
            "type": "string",
            "description": "Choose one seniority level, or keep Any.",
            "editor": "select",
            "default": "any",
            "enum": [
              "any",
              "internship",
              "entry",
              "associate",
              "midsenior",
              "director",
              "executive"
            ],
            "enumTitles": [
              "Any",
              "Internship",
              "Entry level",
              "Associate",
              "Mid-Senior",
              "Director",
              "Executive"
            ]
          },
          "maxPagesPerSearch": {
            "title": "Page depth",
            "type": "integer",
            "description": "How many result pages to check for each keyword/location or search URL. Increase slowly for larger runs.",
            "editor": "number",
            "default": 2,
            "minimum": 1,
            "maximum": 100
          },
          "excludeCompanies": {
            "title": "Skip employers",
            "type": "string",
            "description": "Optional. Enter one employer per line. Matching jobs are skipped and counted in the run summary.",
            "editor": "textarea",
            "default": ""
          },
          "titleMustInclude": {
            "title": "Title must include",
            "type": "string",
            "description": "Optional. Enter one word or phrase per line. A job is saved only if its title contains one of these.",
            "editor": "textarea",
            "default": ""
          },
          "titleExclude": {
            "title": "Skip titles containing",
            "type": "string",
            "description": "Optional. Enter one word or phrase per line. Jobs with these words in the title are skipped.",
            "editor": "textarea",
            "default": ""
          },
          "saveOnlyUniqueItems": {
            "title": "Unique jobs only (remove duplicates)",
            "type": "boolean",
            "description": "Save each LinkedIn job only once, even if it appears from more than one keyword, URL, or ID. Keep this on for normal runs.",
            "editor": "checkbox",
            "sectionCaption": "Quality controls",
            "sectionDescription": "Use these controls to save cleaner jobs or add extra fields. The defaults are best for most users.",
            "default": true,
            "prefill": true
          },
          "balanceKeywordCoverage": {
            "title": "Balance keyword coverage",
            "type": "boolean",
            "description": "Give each keyword and location a turn before going deeper. Useful when you enter more than one keyword or location.",
            "editor": "checkbox",
            "default": true
          },
          "fetchJobDetails": {
            "title": "Full descriptions and details",
            "type": "boolean",
            "description": "Keep on to collect full descriptions, criteria, applicant text, and Easy Apply signals when LinkedIn shows them.",
            "editor": "checkbox",
            "default": true
          },
          "requestDelayMillis": {
            "title": "Delay between requests",
            "type": "integer",
            "description": "Wait time between LinkedIn requests. Values below 500 ms are raised automatically.",
            "editor": "number",
            "sectionCaption": "Run tuning",
            "sectionDescription": "Tune request pacing for larger runs. Defaults are recommended for most users.",
            "default": 500,
            "minimum": 500,
            "maximum": 60000
          }
        }
      },
      "output": {
        "actorOutputSchemaVersion": 1,
        "title": "LinkedIn Jobs Scraper output",
        "description": "Normalized job rows are stored in the default dataset. OUTPUT contains a compact run summary, warnings, and input provenance.",
        "properties": {
          "results": {
            "type": "string",
            "title": "Job results",
            "template": "{{links.apiDefaultDatasetUrl}}/items"
          },
          "output": {
            "type": "string",
            "title": "Run summary",
            "template": "{{links.apiDefaultKeyValueStoreUrl}}/records/OUTPUT"
          }
        }
      },
      "storages": {
        "dataset": {
          "actorSpecification": 1,
          "fields": {
            "$schema": "http://json-schema.org/draft-07/schema#",
            "type": "object",
            "properties": {
              "jobId": {
                "type": [
                  "string",
                  "null"
                ],
                "title": "Job ID",
                "description": "Stable LinkedIn job identifier.",
                "example": "4428089436"
              },
              "title": {
                "type": [
                  "string",
                  "null"
                ],
                "title": "Job title",
                "example": "Senior Data Engineer"
              },
              "companyName": {
                "type": [
                  "string",
                  "null"
                ],
                "title": "Company",
                "example": "Example Company"
              },
              "companyUrl": {
                "type": [
                  "string",
                  "null"
                ],
                "title": "Company URL",
                "example": "https://www.linkedin.com/company/example"
              },
              "companyLogoUrl": {
                "type": [
                  "string",
                  "null"
                ],
                "title": "Company logo"
              },
              "locationRaw": {
                "type": [
                  "string",
                  "null"
                ],
                "title": "Location",
                "example": "Toronto, Ontario, Canada"
              },
              "locationCity": {
                "type": [
                  "string",
                  "null"
                ],
                "title": "City"
              },
              "locationRegion": {
                "type": [
                  "string",
                  "null"
                ],
                "title": "Region"
              },
              "locationCountry": {
                "type": [
                  "string",
                  "null"
                ],
                "title": "Country"
              },
              "workplaceType": {
                "type": [
                  "string",
                  "null"
                ],
                "title": "Workplace",
                "example": "remote"
              },
              "isRemote": {
                "type": [
                  "boolean",
                  "null"
                ],
                "title": "Remote"
              },
              "salaryRaw": {
                "type": [
                  "string",
                  "null"
                ],
                "title": "Salary"
              },
              "salaryMin": {
                "type": [
                  "number",
                  "null"
                ],
                "title": "Minimum salary"
              },
              "salaryMax": {
                "type": [
                  "number",
                  "null"
                ],
                "title": "Maximum salary"
              },
              "salaryCurrency": {
                "type": [
                  "string",
                  "null"
                ],
                "title": "Salary currency"
              },
              "salaryPeriod": {
                "type": [
                  "string",
                  "null"
                ],
                "title": "Salary period"
              },
              "employmentType": {
                "type": [
                  "string",
                  "null"
                ],
                "title": "Employment type",
                "example": "fulltime"
              },
              "seniority": {
                "type": [
                  "string",
                  "null"
                ],
                "title": "Seniority"
              },
              "jobFunction": {
                "type": [
                  "string",
                  "null"
                ],
                "title": "Job function"
              },
              "industries": {
                "type": "array",
                "title": "Industries",
                "items": {
                  "type": "string"
                }
              },
              "postedAtRaw": {
                "type": [
                  "string",
                  "null"
                ],
                "title": "Posted"
              },
              "postedAt": {
                "type": [
                  "string",
                  "null"
                ],
                "title": "Posted at",
                "format": "date-time"
              },
              "applicantsRaw": {
                "type": [
                  "string",
                  "null"
                ],
                "title": "Applicants"
              },
              "descriptionSnippet": {
                "type": [
                  "string",
                  "null"
                ],
                "title": "Description"
              },
              "descriptionText": {
                "type": [
                  "string",
                  "null"
                ],
                "title": "Description text"
              },
              "descriptionHtml": {
                "type": [
                  "string",
                  "null"
                ],
                "title": "Description HTML"
              },
              "descriptionStatus": {
                "type": [
                  "string",
                  "null"
                ],
                "title": "Description status",
                "example": "full"
              },
              "jobUrl": {
                "type": [
                  "string",
                  "null"
                ],
                "title": "Job URL"
              },
              "query": {
                "type": [
                  "string",
                  "null"
                ],
                "title": "Search keyword"
              },
              "searchLocation": {
                "type": [
                  "string",
                  "null"
                ],
                "title": "Search location"
              },
              "searchPosition": {
                "type": [
                  "integer",
                  "null"
                ],
                "title": "Search position"
              },
              "pageNumber": {
                "type": [
                  "integer",
                  "null"
                ],
                "title": "Search page"
              },
              "isSponsored": {
                "type": [
                  "boolean",
                  "null"
                ],
                "title": "Sponsored"
              },
              "isEasyApply": {
                "type": [
                  "boolean",
                  "null"
                ],
                "title": "Easy Apply"
              },
              "isExpired": {
                "type": [
                  "boolean",
                  "null"
                ],
                "title": "Expired"
              },
              "verificationSource": {
                "type": [
                  "string",
                  "null"
                ],
                "title": "Verification source"
              },
              "verifiedAt": {
                "type": [
                  "string",
                  "null"
                ],
                "title": "Verified at",
                "format": "date-time"
              },
              "source": {
                "type": [
                  "string",
                  "null"
                ],
                "title": "Source"
              },
              "scrapedAt": {
                "type": "string",
                "title": "Scraped at",
                "format": "date-time",
                "example": "2026-06-21T18:00:00.000Z"
              },
              "schemaVersion": {
                "type": [
                  "string",
                  "null"
                ],
                "title": "Schema version"
              }
            },
            "required": [
              "jobId",
              "title",
              "companyName",
              "jobUrl",
              "scrapedAt"
            ]
          },
          "views": {
            "overview": {
              "title": "Job listings",
              "description": "One row per saved LinkedIn job listing.",
              "transformation": {
                "fields": [
                  "title",
                  "companyName",
                  "locationRaw",
                  "salaryRaw",
                  "employmentType",
                  "workplaceType",
                  "isRemote",
                  "postedAtRaw",
                  "descriptionSnippet",
                  "jobUrl",
                  "query",
                  "scrapedAt"
                ]
              },
              "display": {
                "component": "table",
                "properties": {
                  "title": {
                    "label": "Title",
                    "format": "text"
                  },
                  "companyName": {
                    "label": "Company",
                    "format": "text"
                  },
                  "locationRaw": {
                    "label": "Location",
                    "format": "text"
                  },
                  "salaryRaw": {
                    "label": "Salary",
                    "format": "text"
                  },
                  "employmentType": {
                    "label": "Type",
                    "format": "text"
                  },
                  "workplaceType": {
                    "label": "Workplace",
                    "format": "text"
                  },
                  "isRemote": {
                    "label": "Remote",
                    "format": "boolean"
                  },
                  "postedAtRaw": {
                    "label": "Posted",
                    "format": "text"
                  },
                  "descriptionSnippet": {
                    "label": "Description",
                    "format": "text"
                  },
                  "jobUrl": {
                    "label": "Job URL",
                    "format": "link"
                  },
                  "query": {
                    "label": "Search",
                    "format": "text"
                  },
                  "scrapedAt": {
                    "label": "Scraped at",
                    "format": "date"
                  }
                }
              }
            }
          }
        }
      },
      "dockerfile": "../Dockerfile",
      "changelog": "# Changelog\n\n## 2026-10-20\n\n- Fixed an issue that could stop valid job searches.\n- Runs now show a failed status if an error prevents any jobs from being saved.\n\n## 2026-07-01 - Public Run Reliability\n\n### Changed\n\n- Improved run reliability for public users: runs with incomplete setup or no\n  matching jobs now finish with a clear run summary instead of ending as failed\n  runs.\n- Added clearer run-summary details for setup issues, so users can see what to\n  fix in the `OUTPUT` record.\n\n## 2026-06-24 - Public Surface Alignment\n\n### Changed\n\n- Aligned the public example input, Store metadata, and README first-run flow.\n- Kept Actor metadata focused on buyer-facing fields.\n- Confirmed public rows omit request URLs and external apply-link details that\n  are not part of the advertised output contract.\n\n## 2026-06-22 - Input Form QOL Pass\n\n### Changed\n\n- Reorganized the input form around keyword search, direct LinkedIn sources,\n  filters, quality controls, and advanced run controls.\n- Removed saved sample defaults from keyword, location, and job-ID fields while\n  keeping keyword, search-URL, and job-ID workflows supported.\n- Kept location blank and optional for broad LinkedIn job searches.\n- Added a clear validation path for inputs that do not include any keyword,\n  LinkedIn search URL, or LinkedIn job ID.\n- Clarified that mixed keyword, search-URL, and job-ID inputs are combined,\n  deduplicated, and limited by one saved-job cap.\n- Rewrote input form help text in simpler language around the three ways to\n  choose jobs.\n- Folded all three collection methods and the shared job-listing cap into one\n  form section.\n- Moved the collection-method explanation into the individual field labels to\n  reduce repeated text in the form.\n- Renamed the three collection labels to Option 1, Option 2, and Option 3 so\n  they read as choices rather than required steps.\n- Clarified that Locations narrow keyword searches rather than acting as a\n  separate collection method.\n- Moved page depth, employer filters, and title filters into the Filters\n  section, and moved request pacing into Run tuning.\n\n## 2026-06-22 - Duplicate-Free Jobs Positioning Pass\n\n### Changed\n\n- Updated Store SEO metadata, README positioning, pricing copy, FAQ, and input\n  form microcopy to make unique saved job listings and duplicate-skip accounting\n  clearer.\n- Kept the public Actor title, slug, input keys, output schema, dataset schema,\n  pricing, and runtime behavior unchanged.\n\n## 2026-06-22 - Store Ranking And Quality Score Pass\n\n### Changed\n\n- Added SEO title, SEO description, Store category metadata, and the official\n  changelog reference for stronger Store congruency.\n- Reduced default run size and search depth so first runs and automated Store\n  health checks complete faster while larger production runs remain supported.\n- Expanded the README with long-tail use-case sections, no-login positioning,\n  FAQ entries, pricing examples, and related job-scraper links.\n\n## 2026-06-21 - LinkedIn Jobs Scraper MVP\n\n### Added\n\n- Created the initial LinkedIn jobs actor.\n- Added public LinkedIn guest search discovery by keyword/location or search URL.\n- Added LinkedIn job-ID detail enrichment from public job detail fragments.\n- Added normalized dataset rows with job, company, location, description,\n  criteria, search provenance, and run metadata fields.\n- Added run-summary accounting for candidates, details, saved rows, duplicates,\n  filters, failures, blocks, warnings, stop reason, and normalized input.\n- Added input, output, and dataset schemas for Apify Console testing.\n- Added parser/unit tests for URL construction, search cards, and detail pages.\n\n## 2026-06-21 - Reliability Pass 1\n\n### Changed\n\n- Made balanced keyword coverage actually distribute saved rows across active\n  keyword/location slots when `maxItems` is small.\n- Preserved source-applied LinkedIn filters on discovered rows so remote,\n  job-type, and experience-level filters do not get erased when detail pages\n  omit those labels.\n- Kept `isRemote` consistent with `workplaceType: \"remote\"` after detail merge.\n- Kept source seniority when detail pages only report `Not Applicable`.\n\n### Validated\n\n- Build `0.1.4` passed local tests, lint, formatting, and Apify schema\n  validation.\n- Cloud probes covered job-ID enrichment, basic search, remote filter, job-type\n  filter, experience-level filter, search URL input, balanced keyword coverage,\n  duplicate overlap, and employer exclusion.\n- Final remote-filter smoke run `qRarDHlsVcmfQhOQd` saved 2 full-description\n  remote rows with no blocks or failures.\n",
      "readme": "## 💼 What does LinkedIn Jobs Scraper do?\n\nLinkedIn Jobs Scraper extracts clean, deduplicated data from public\n[LinkedIn](https://www.linkedin.com/jobs/) job listings: job title, company\nname, location, posted date, applicant text, employment criteria, salary signals,\nfull description text and HTML when available, Easy Apply status, LinkedIn job\nURLs, and more.\n\nTo get that data, enter a job title or keyword, paste a LinkedIn Jobs search\nURL, or add known LinkedIn job IDs or URLs. Locations are optional, so you can\nrun broad keyword searches or narrow them to specific markets. You can also run\nthe scraper through the Apify API, schedule it, or connect it to other tools.\nIt saves one row per job by default, so overlapping keywords, locations, search\nURLs, job IDs, and pages do not fill your dataset with duplicate listings.\n\nWith LinkedIn Jobs Scraper, you can:\n\n- Scrape LinkedIn job listings by job title, keyword, company, role family, and\n  location.\n- Extract detailed job posting data, including description text and HTML when\n  available.\n- Collect company names, company URLs, logo URLs, locations, salary signals,\n  applicant text, job criteria, Easy Apply status, and LinkedIn job URLs.\n- Filter LinkedIn jobs by posting date, radius, job type, workplace type,\n  experience level, employer exclusions, title filters, and sort order.\n- Run keyword/location searches, paste LinkedIn search URLs, or enrich known job\n  IDs from the form, JSON, or API input.\n- Enrich known LinkedIn job IDs or job URLs without running search discovery.\n- Save clean unique job listings with duplicate skips, title filters, and\n  run-summary counters.\n- Download LinkedIn job data in JSON, JSONL, CSV, Excel, XML, RSS, or HTML.\n- Check the run summary to see how many jobs were found, saved, skipped,\n  duplicated, filtered, blocked, or stopped.\n\n## 📦 What job listing data can I extract?\n\nWith this Actor, you can extract the following data from public LinkedIn job\npostings:\n\n|                          |                       |                       |\n| ------------------------ | --------------------- | --------------------- |\n| 💼 Job title             | 🏢 Company name       | 📍 Job location       |\n| 📝 Full description text | 🧾 Description HTML   | 🔎 Description status |\n| 💰 Salary range          | 💵 Salary currency    | ⏱️ Salary period      |\n| 🧑‍💻 Employment type       | 🪜 Seniority          | 🏠 Workplace / remote |\n| 🧰 Job function          | 🏷️ Industries         | 👥 Applicant text     |\n| 🔗 Company URL           | 🖼️ Company logo URL   | ⚡ Easy Apply status  |\n| 🌐 LinkedIn job URL      | 🔍 Search keyword     | 🧭 Search location    |\n| 📊 Jobs saved            | ♻️ Duplicates skipped | 🚦 Stop reason        |\n\nHere are the main output groups:\n\n| Data group              | Example fields                                                                                            |\n| ----------------------- | --------------------------------------------------------------------------------------------------------- |\n| 💼 Job details          | `jobId`, `title`, `employmentType`, `seniority`, `jobFunction`, `industries`, `isEasyApply`               |\n| 🏢 Company details      | `companyName`, `companyUrl`, `companyLogoUrl`                                                             |\n| 📍 Location             | `locationRaw`, `locationCity`, `locationRegion`, `locationCountry`, `workplaceType`, `isRemote`           |\n| 💰 Salary               | `salaryRaw`, `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod`                                   |\n| 📝 Description          | `descriptionSnippet`, `descriptionText`, `descriptionHtml`, `descriptionStatus`                           |\n| 🔗 Links and enrichment | `jobUrl`, `isEasyApply`, `isExpired`                                                                      |\n| 🧭 Search provenance    | `query`, `searchLocation`, `searchPosition`, `pageNumber`                                                 |\n| 📊 Run summary          | `candidateItemsSeen`, `jobsSaved`, `duplicateCandidatesSeen`, `duplicatesSkipped`, warnings, `stopReason` |\n\nSalary, applicant counts, Easy Apply signals, and full descriptions depend on\nwhat LinkedIn exposes for each public job listing. If full job descriptions\nmatter for your workflow, check the `descriptionStatus` field in the output and\nthe detail counters in the run summary.\n\n## ⚙️ Can I use this Actor through an API?\n\nYes. You can run LinkedIn Jobs Scraper manually in Apify Console or use it as an\nAPI for LinkedIn job data.\n\nDevelopers can call this Actor through:\n\n- Apify API\n- Python SDK\n- Node.js SDK\n- Webhooks\n- Scheduled runs\n- Apify integrations\n\nThis makes it useful for recruiting pipelines, job-board enrichment, hiring\ndashboards, warehouse loads, agents, and automated labor-market research.\n\n## Clean, duplicate-free LinkedIn job datasets\n\nLinkedIn job results can overlap across related keywords, locations, search\nURLs, known job IDs, and pages. For normal datasets, keep\n`saveOnlyUniqueItems` enabled so the Actor saves one row per job and skips\nduplicate candidates before they become saved rows.\n\nThe `OUTPUT` summary shows `candidateItemsSeen`, `duplicateCandidatesSeen`,\n`duplicatesSkipped`, and `jobsSaved`, so you can see how many matching rows were\nfound, how many were duplicates, and how many unique job listings were saved.\nTurn unique-job mode off only when you intentionally want to measure keyword\noverlap or search ranking behavior.\n\n## 📊 Why scrape LinkedIn jobs?\n\nLinkedIn is a major source of job market data. Scraping LinkedIn jobs can help\nyou track hiring demand, role language, salary signals, employer activity, and\nremote-work patterns across markets.\n\nHere are just some of the ways you could use LinkedIn job data:\n\n| Use case                      | How LinkedIn job data helps                                                   |\n| ----------------------------- | ----------------------------------------------------------------------------- |\n| 📈 Monitor hiring trends      | Track demand by role, skill, location, company, seniority, or workplace type. |\n| 💰 Benchmark salaries         | Compare salary signals across markets and job families where available.       |\n| 🧲 Build recruiting workflows | Collect active job listings for sourcing, staffing, and lead generation.      |\n| 🏢 Track competitors          | Watch hiring activity, expansion signals, and technology adoption.            |\n| 🧪 Research labor markets     | Analyze job descriptions, criteria, applicant text, and company demand.       |\n| 🧱 Enrich job boards          | Feed clean LinkedIn job data into apps, databases, and career tools.          |\n\n## LinkedIn jobs scraper with no login or cookies\n\nLinkedIn Jobs Scraper works with public LinkedIn Jobs surfaces. You do not need\nto provide a LinkedIn account, login session, or browser cookies. Paste a role\nand optional location, a LinkedIn Jobs search URL, or known LinkedIn job IDs and run the\nActor from Apify Console or API.\n\n## Scrape LinkedIn jobs by job ID or URL\n\nUse `jobIds` when you already know the LinkedIn job IDs or job URLs you want to\nenrich. This mode skips search discovery and focuses on the known job listings,\nwhich is useful for job-board enrichment, saved lead lists, and repeatable data\nrefreshes.\n\nUse `startUrls` when you already built a LinkedIn Jobs search in the LinkedIn UI\nand want to preserve those search filters in the Actor run.\n\nIf you provide more than one source type in the same run, the Actor combines\nthem, removes duplicate jobs when `saveOnlyUniqueItems` is enabled, and applies\none shared `maxItems` cap across the whole run.\n\n## LinkedIn Easy Apply, salary, and criteria data\n\nWhen public job details are available, the Actor can add Easy Apply status,\nsalary signals, applicant text, seniority, employment type, workplace type, job\nfunction, industries, full description text, and description HTML. Check\n`descriptionStatus` and the run summary to see which detail fields were exposed\nfor each run.\n\n## 💵 How much does scraping LinkedIn jobs cost?\n\nLinkedIn Jobs Scraper uses a Pay-Per-Event pricing model. You are charged for\nsaved job listings.\n\nThe current price is **$1.50 per 1,000 saved job listings**, or **$0.0015 per\njob listing**. For example:\n\n| Saved job listings | Estimated Actor charge |\n| ------------------ | ---------------------- |\n| 100 jobs           | $0.15                  |\n| 1,000 jobs         | $1.50                  |\n| 10,000 jobs        | $15.00                 |\n\nStart with `maxItems` between 10 and 100 if you want to test the output before\nrunning larger searches. The run summary shows how many candidate jobs were\nseen, how many unique jobs were saved, and why the run stopped. For normal runs,\nkeep **Unique jobs only** enabled so duplicate candidates are skipped before\nthey become saved rows.\n\n## 🚀 How do I use LinkedIn Jobs Scraper?\n\nLinkedIn Jobs Scraper was designed for an easy start even if you've never\nextracted data from the web before. Here's how you can scrape LinkedIn job data\nwith this tool:\n\n1. Create or log in to your Apify account.\n2. Open **LinkedIn Jobs Scraper**.\n3. Choose a source: add job titles or keywords, paste LinkedIn Jobs search URLs,\n   or add known LinkedIn job IDs or URLs.\n4. Optionally add one or more locations for keyword searches. Location narrows\n   the keyword search.\n5. Set the maximum number of job listings to collect.\n6. Add filters such as posted date, radius, job type, workplace type, experience\n   level, employer exclusions, or title filters if needed.\n7. Click **Start** and wait for the data to be extracted.\n8. Download your data in JSON, JSONL, CSV, Excel, XML, RSS, or HTML.\n\n## ⬇️ Input\n\nThe input needs at least one way to choose jobs:\n\n- **Option 1: Keywords** - Search LinkedIn by job titles/keywords, optionally\n  with locations.\n- **Option 2: LinkedIn search URLs** - Paste a LinkedIn Jobs search URL when you\n  already configured filters in LinkedIn and want to preserve them.\n- **Option 3: Job IDs or job URLs** - Provide known LinkedIn job IDs/URLs to\n  enrich specific listings without search discovery.\n\nThey can also be mixed in one run. If mixed, the actor combines results,\ndeduplicates them, and applies one shared Job listings to collect cap.\n\nLocations are optional for keyword searches. If filled, LinkedIn searches for\nKeywords AND each Location. LinkedIn search URLs keep their own filters, and job\nIDs or job URLs target exact listings.\n\nYou can also add optional filters for date, radius, job type, workplace type,\nexperience level, page depth, employer exclusions, and title filters.\n\nHere's a simple input example for a broad software engineer search:\n\n```json\n{\n    \"searchTerm\": \"software engineer\",\n    \"location\": \"\",\n    \"postedWithin\": \"7d\",\n    \"distance\": 25,\n    \"sortBy\": \"relevance\",\n    \"maxItems\": 25,\n    \"maxPagesPerSearch\": 2,\n    \"balanceKeywordCoverage\": true,\n    \"saveOnlyUniqueItems\": true,\n    \"fetchJobDetails\": true,\n    \"jobType\": \"any\",\n    \"workplaceType\": \"any\",\n    \"experienceLevel\": \"any\"\n}\n```\n\nClick on the **Input** tab for a full explanation of input in JSON.\n\n### 🔎 Search terms\n\nUse one job title, skill, company, or role family per line. This field is\noptional when you use LinkedIn search URLs or job IDs. Multiple distinct search\nterms can help you collect broader job market data.\n\nGood search terms:\n\n```text\ndata engineer\nanalytics engineer\ndata scientist\nsoftware engineer\nproduct manager\n```\n\nOverlapping search terms can return the same job more than once. Keep\n`saveOnlyUniqueItems` enabled unless you are measuring keyword overlap or search\nranking.\n\nIf LinkedIn returns broad results for a role, add title filters. This is useful\nfor searches like `backend engineer`, `CTO`, or `data analyst`, where broad\nkeyword matching can include off-target roles.\n\n```json\n{\n    \"searchTerm\": \"backend engineer\",\n    \"location\": \"Germany\",\n    \"titleMustInclude\": \"backend engineer\\nplatform engineer\",\n    \"titleExclude\": \"frontend\\nrecruiter\\nsales\",\n    \"maxItems\": 100\n}\n```\n\n### 📍 Locations\n\nAdd one city, region, country, or remote-friendly location per line. Locations\nare only used with keyword searches. Leave the field blank for broader LinkedIn\nresults. LinkedIn handles geography from each location value you provide.\n\nGood locations:\n\n```text\nToronto, ON, Canada\nNew York, NY\nSan Francisco Bay Area\nRemote\nUnited States\n```\n\nJSON/API users can run a location matrix by passing `locations` as an array.\nFor example, three keywords and two locations create six keyword/location\nsearches:\n\n```json\n{\n    \"searchTerm\": \"data scientist\",\n    \"locations\": [\"Toronto, ON, Canada\", \"New York, NY\", \"Seattle, WA\"],\n    \"maxItems\": 300\n}\n```\n\n### 🎛️ Filters, quality controls, and run tuning\n\n| Option                      | What it does                                                                 |\n| --------------------------- | ---------------------------------------------------------------------------- |\n| 📅 `postedWithin`           | Searches jobs from the selected freshness window.                            |\n| 📍 `distance`               | Searches around the selected location.                                       |\n| 🔀 `sortBy`                 | Sorts LinkedIn results by relevance or date.                                 |\n| 🧑‍💻 `jobType`                | Filters full-time, part-time, contract, temporary, internship, or volunteer. |\n| 🏠 `workplaceType`          | Filters on-site, remote, or hybrid jobs.                                     |\n| 🪜 `experienceLevel`        | Filters by LinkedIn seniority level when available.                          |\n| 📄 `maxPagesPerSearch`      | Sets the page depth for each keyword/location or search URL.                 |\n| 🚫 `excludeCompanies`       | Skips matching employers and counts skipped rows in the run summary.         |\n| 🎯 `titleMustInclude`       | Saves jobs whose title contains at least one required word or phrase.        |\n| 🧹 `titleExclude`           | Skips jobs whose title contains off-target words or phrases.                 |\n| ♻️ `saveOnlyUniqueItems`    | Saves one row per job across overlapping keywords, locations, URLs, pages.   |\n| ⚖️ `balanceKeywordCoverage` | Spreads results across keywords and locations before going deeper.           |\n| 📝 `fetchJobDetails`        | Adds full descriptions, criteria, applicant text, and Easy Apply signals.    |\n| ⏱️ `requestDelayMillis`     | Controls delay between requests. The Actor enforces a 500 ms minimum.        |\n\n### 🔗 LinkedIn search URLs and job IDs\n\nYou can also start from LinkedIn search URLs or known job IDs.\n\nUse `startUrls` when you already built a search in LinkedIn and want to preserve\nits filters:\n\n```json\n{\n    \"startUrls\": [\n        {\n            \"url\": \"https://www.linkedin.com/jobs/search/?keywords=data%20engineer&location=Toronto%2C%20ON\"\n        }\n    ],\n    \"maxItems\": 50,\n    \"fetchJobDetails\": true\n}\n```\n\nUse `jobIds` when you already have LinkedIn job IDs or job URLs and only want\ndetail enrichment:\n\n```json\n{\n    \"jobIds\": [\"<linkedin-job-id-or-url>\"],\n    \"maxItems\": 1,\n    \"fetchJobDetails\": true\n}\n```\n\nLeave `jobIds` empty unless you already have known listings to enrich. The form\ndoes not include seeded job IDs.\n\n## ⬆️ Output sample\n\nThe results will be wrapped into a dataset which you can find in the **Output**\ntab. Each result is one saved LinkedIn job listing.\n\nFor easier postprocessing, export the results to one of the supported formats:\nJSON, JSONL, CSV, Excel spreadsheet, XML, RSS, or HTML table.\n\nHere's an example of the exported JSON output:\n\n```json\n{\n    \"jobId\": \"4428089436\",\n    \"title\": \"Data Engineer - Python, ETL\",\n    \"companyName\": \"Example Company\",\n    \"companyUrl\": \"https://www.linkedin.com/company/example\",\n    \"companyLogoUrl\": \"https://media.licdn.com/example-logo\",\n    \"locationRaw\": \"Toronto, Ontario, Canada\",\n    \"locationCity\": \"Toronto\",\n    \"locationRegion\": \"Ontario\",\n    \"locationCountry\": \"Canada\",\n    \"workplaceType\": \"hybrid\",\n    \"isRemote\": false,\n    \"salaryRaw\": \"$110,000-$130,000 CAD\",\n    \"salaryMin\": 110000,\n    \"salaryMax\": 130000,\n    \"salaryCurrency\": \"CAD\",\n    \"salaryPeriod\": \"year\",\n    \"employmentType\": \"fulltime\",\n    \"seniority\": \"Mid-Senior level\",\n    \"jobFunction\": \"Information Technology\",\n    \"industries\": [\"IT Services and IT Consulting\"],\n    \"postedAtRaw\": \"5 days ago\",\n    \"postedAt\": \"2026-06-16T20:35:49.490Z\",\n    \"applicantsRaw\": \"Be among the first 25 applicants\",\n    \"descriptionSnippet\": \"Build reliable data systems...\",\n    \"descriptionText\": \"Full job description text...\",\n    \"descriptionHtml\": \"<p>Full job description HTML...</p>\",\n    \"descriptionStatus\": \"full\",\n    \"jobUrl\": \"https://www.linkedin.com/jobs/view/example-4428089436\",\n    \"query\": \"data engineer\",\n    \"searchLocation\": \"Toronto, ON, Canada\",\n    \"verifiedAt\": \"2026-06-21T20:35:49.490Z\",\n    \"isExpired\": false,\n    \"verificationSource\": \"detail\",\n    \"scrapedAt\": \"2026-06-21T20:35:49.490Z\"\n}\n```\n\n### 📌 Run summary\n\nLinkedIn Jobs Scraper also saves an `OUTPUT` summary in the default key-value\nstore. Use it to see why the final number of saved jobs changed.\n\n```json\n{\n    \"stopReason\": \"max_items_reached\",\n    \"searchesQueued\": 6,\n    \"pagesParsed\": 18,\n    \"requestedMaxItems\": 100,\n    \"candidateItemsSeen\": 142,\n    \"jobsSaved\": 100,\n    \"detailRequests\": 100,\n    \"detailPagesParsed\": 99,\n    \"detailFailures\": 1,\n    \"duplicateCandidatesSeen\": 12,\n    \"duplicatesSkipped\": 12,\n    \"rowsExcludedByCompany\": 0,\n    \"rowsExcludedByDate\": 0,\n    \"rowsExcludedByJobType\": 0,\n    \"rowsExcludedByWorkplace\": 0,\n    \"rowsExcludedByExperience\": 0,\n    \"rowsExcludedByTitle\": 0,\n    \"blockedRequests\": 1,\n    \"failedRequests\": 0,\n    \"warnings\": [],\n    \"statusMessage\": \"Saved 100 LinkedIn job listings.\"\n}\n```\n\n## ❓ Why did I get fewer jobs than requested?\n\n`maxItems` is a cap on saved rows, not a guarantee that every search has that\nmany matching jobs.\n\nIf your run saves fewer jobs than requested, check the `OUTPUT` summary. It\nshows whether rows were skipped because of duplicates, employer exclusions, date\nfilters, job-type filters, workplace filters, experience filters, title filters,\nsource exhaustion, page limits, failed requests, or blocked detail pages.\nIf the run setup is incomplete or uses an unsupported option, the Actor still\nfinishes and writes an `OUTPUT` summary. Check `OUTPUT.statusMessage` and\n`OUTPUT.validationErrors` to see what needs to be fixed before running again.\nThis usually happens when running through the API, an integration, a saved task,\nor a form submission where all job source fields were left empty.\n\nExample setup issue:\n\n```json\n{\n    \"stopReason\": \"invalid_input\",\n    \"jobsSaved\": 0,\n    \"validationErrors\": [\n        {\n            \"field\": \"source\",\n            \"code\": \"missing_source\",\n            \"message\": \"Provide at least one keyword, LinkedIn search URL, or LinkedIn job ID.\"\n        }\n    ],\n    \"statusMessage\": \"Input was not valid: Provide at least one keyword, LinkedIn search URL, or LinkedIn job ID.\"\n}\n```\n\nCommon reasons include:\n\n- The selected role and location have fewer available jobs than `maxItems`.\n- Strict filters removed many candidate rows.\n- Title include/exclude filters removed broad or off-target LinkedIn matches.\n- Multiple keywords returned the same jobs and unique-job mode skipped\n  duplicates.\n- LinkedIn did not expose a detail page for every discovered job.\n- The search depth limit was reached before enough unique matching jobs were\n  found.\n\nIf you need more rows, increase `maxPagesPerSearch`, relax strict filters, add\nmore distinct keywords, expand the search radius, or search broader locations.\n\n## ✅ Tips for scraping LinkedIn jobs\n\n- Start with a small test run and inspect the dataset before increasing\n  `maxItems`.\n- Use distinct keywords instead of near-duplicates.\n- Keep `saveOnlyUniqueItems` enabled for normal datasets.\n- Use `balanceKeywordCoverage` for market maps across multiple role families.\n- Use `titleMustInclude` and `titleExclude` when LinkedIn returns broad or\n  off-target roles.\n- Use `postedWithin` for fresh job monitoring.\n- Keep `fetchJobDetails` enabled when full descriptions matter.\n- Increase `maxPagesPerSearch` gradually for larger runs.\n- Check `descriptionStatus` when full job descriptions are important.\n- Check the run summary for duplicates, detail failures, blocked requests, and\n  stop reason.\n\n## ❓ FAQ\n\n### Does LinkedIn Jobs Scraper need login or cookies?\n\nNo. You do not need to provide a LinkedIn account, login session, or browser\ncookies.\n\n### Can I use LinkedIn Jobs Scraper through an API?\n\nYes. Run it with the Apify API, Python SDK, Node.js SDK, webhooks, schedules, or\nintegrations. The dataset contains saved job rows and the `OUTPUT` key-value\nstore record contains the run summary.\n\n### Can I scrape LinkedIn jobs by job ID?\n\nYes. Use the `jobIds` input with LinkedIn job IDs or job URLs when you already\nknow which listings you want to enrich.\n\n### Why are salaries, applicant text, or full descriptions sometimes missing?\n\nThose fields depend on what LinkedIn exposes publicly for each listing. Use\n`descriptionStatus`, salary fields, and run-summary detail counters to separate\ncomplete rows from partial rows.\n\n### How do I avoid duplicate jobs?\n\nKeep `saveOnlyUniqueItems` enabled. The Actor saves one row per job across\noverlapping keywords, locations, search URLs, job IDs, and pages. The `OUTPUT`\nsummary reports `candidateItemsSeen`, `duplicateCandidatesSeen`,\n`duplicatesSkipped`, and `jobsSaved`, so you can audit how many candidates were\nfound, skipped as duplicates, and saved as unique job listings.\n\n### How much does it cost?\n\nThe current Actor charge is **$1.50 per 1,000 saved job listings**. Check the\nPricing tab before production runs because live Store pricing is the source of\ntruth.\n\n## ⚖️ Is it legal to scrape LinkedIn jobs?\n\nThis Actor extracts public job listing data. You are responsible for making sure\nyour use case complies with applicable laws, platform terms, and privacy rules.\n\nJob listings can sometimes include personal data or contact details. You should\nnot scrape, store, or process personal data unless you have a legitimate reason\nand the right legal basis. If you're unsure whether your reason is legitimate,\nconsult your lawyers.\n\nLinkedIn is a trademark of its owner. This Actor is independent and is not\naffiliated with, endorsed by, or sponsored by LinkedIn.\n\n## 🛟 Support\n\nIf a run does not return what you expected, open an issue with the run ID and\nthe input you used. The run summary shows saved rows, skipped rows, filters,\nwarnings, detail counters, and stop reason so support can be specific.\n\nRelated searches: LinkedIn scraper, LinkedIn jobs scraper, scrape LinkedIn jobs,\nLinkedIn job listings, remove duplicate jobs, duplicate-free job scraper, unique\njob listings, deduped job listings, job board deduplication, LinkedIn job\ndescriptions, LinkedIn jobs no cookies, LinkedIn job ID scraper, LinkedIn jobs\nsearch URL scraper, LinkedIn Easy Apply scraper, LinkedIn salary data, job\nmarket data, recruiting data, HR analytics, salary benchmarking, job board data,\ncompany hiring signals.\n\n## 🔗 Related job scrapers\n\nCompare public job and freelance demand across these sources.\n\n### Compare jobs across sources\n\n- [Indeed Jobs Scraper](https://apify.com/chronometrica/indeed-jobs-scraper)\n  — find public Indeed jobs, salaries, skills, and apply links.\n- [Google Jobs Scraper](https://apify.com/chronometrica/google-jobs-scraper)\n  — find public job listings, employers, locations, and apply links.\n- [Naukri Jobs Scraper](https://apify.com/chronometrica/naukri-jobs-scraper)\n  — find public India job listings, employers, locations, and skills.\n- [Upwork Job Scraper](https://apify.com/chronometrica/upwork-job-scraper)\n  — find fresh freelance jobs, skills, budgets, and client signals.\n\n### Continue on LinkedIn\n\n- [LinkedIn Profile Scraper](https://apify.com/chronometrica/linkedin-profile-scraper)\n  — collect public profile identity, headlines, and visible profile facts.\n"
    },
    "buildNumber": "0.2.26",
    "imageDigest": "8cc47385c02b94ef096b4cdc1a4a7ddf11d158506df2300e7763adb156d183d4",
    "actVersion": {
      "sourceType": "SOURCE_FILES",
      "buildTag": "release-candidate",
      "versionNumber": "0.2"
    },
    "usage": {
      "ACTOR_COMPUTE_UNITS": 0.0036655555555555556
    }
  }
}