# Changelog of LinkedIn Jobs Scraper — LinkedIn Job Postings, Salary, No Login (`steadyfetch/linkedin-jobs-scraper`) Actor

- **URL**: https://apify.com/steadyfetch/linkedin-jobs-scraper/changelog.md
- **Full Actor documentation**: https://apify.com/steadyfetch/linkedin-jobs-scraper.md

## Changelog

### 1.0.17 — 2026-09-26

- **A run cut off before it finishes is now accounted for.** The run now notes its own start in its internal record, so a run that ends without finishing — a hard abort, a crash or a timeout — is still accounted for on our side. Nothing changes about what is delivered, which rows are charged, the price, the input fields or the output columns.

### 1.0.16 — 2026-09-24

- **The `queries` field is no longer marked required in the input form.** An AI agent or API call that leaves it out gets the same sample run a bare Start always gave, and one that sets it gets the listings for its searches, exactly as before. No price, input field, output column or charged event changed.

### 1.0.15 — 2026-09-24

- **A run that Apify restarts after it has already delivered keeps its own record of that delivery.** If the restarted part of a run delivers nothing new, the run's internal record now keeps what the earlier part delivered and charged, with a note of the restart, instead of being rewritten as if nothing had been delivered; one line in the run log says so. Nothing changes about what is delivered, which rows are charged, the price, the input fields or the output columns.

### 1.0.14 — 2026-09-22

- **A failed internal bookkeeping write no longer prints its storage address in the run log.** Nothing changes about what is delivered, which rows are charged, the price, the input fields or the output columns.

### 1.0.13 — 2026-09-19

- **The whole "Maximum cost per run" you set is now spent on jobs.** Before this build the run held back about a tenth of your cost cap before it started — a cushion for platform running costs which, on this actor's pricing, your cap does not pay for at all: your cap buys jobs, and the running costs are ours. So the cushion was simply cap you had asked to spend and could not get. On a run capped at $0.10 with jobs at $0.0015 the plan asked for 60 jobs where the cap paid for 66 — 6 jobs of your own cap, unspendable by construction. From this build the plan uses your cap in full, so a run asks for as many jobs as your cap can really pay for. Nothing you are charged changes, and nothing can be charged above the cap you typed: every charge is still checked against what is left of your cap at the moment it is posted, and a run that reaches your cap still stops cleanly, ships what it had already fetched, and tells you what is left. No price, input field or output column changes.

### 1.0.12 — 2026-09-19

- **The first line of this page now says what you get and what it costs, instead of what we do not charge for.** It opened on "no login, no cookies, no session to keep alive" and the price sat eight lines down, past the first screen; a buyer arriving from store search read a guarantee before reading whether this actor returns LinkedIn job postings at all. The opening line is now the thing you came for — LinkedIn job postings as rows, from $1.50 per 1,000 listings — with the no-login fact kept in the same sentence and the "never pay for a job we didn't deliver" line directly under it. This build changes nothing about the run: no input field, output column, charged event or price moved.

### 1.0.11 — 2026-09-15

- **A run now stops at the "Max run seconds" you typed, instead of a few seconds past it.** Every limit in this actor decided when a page request could *start*; nothing decided how long one was allowed to *run*. So a request begun in the last stretch of your window was still given its full 30 seconds and could finish after your limit had already passed — measured at 10 seconds over a 60-second limit on a replayed walk, and longer if LinkedIn left the connection hanging. Each request is now sized to what your run actually has left, and is cut off at that moment rather than at its own private timer, with a few seconds kept back so the summary row and the receipt still land inside your window. A run with time to spare is unchanged: the full 30 seconds on every request, the same pages, the same rows and the same charges. Nothing you type, no output column, no event and no price moved, and a run that hits the limit still stops the way it did — it says "Max run seconds", counts what it did not reach, and saves the point to continue from.

### 1.0.10 — 2026-09-14

- **The second charge now carries its number, on both screens you see before you pay.** Turning on **Fetch the full description for every job** adds a **Full description** charge per job, and the page and the input form both said so without ever saying how much — so you had to open the Pricing tab to find out what you had just agreed to. Both now say it outright: a flat $0.003 per description, charged only when a description actually arrives, and off by default, so a run started without it is the per-listing price and nothing else. On the input form that sentence is now the first thing the option says, which is the part an AI agent shopping the store reads. Nothing about the run changed: no input field, output column, charged event or price moved — this is the same charge, named.

### 1.0.9 — 2026-09-14

- **A token that can read your repeat memory but not write to it no longer costs you the same postings — and their full descriptions — all over again.** This actor remembers every posting it delivers for your account so a re-run skips what you already have, never charges for it twice, and never fetches or bills the full description a second time. Permission to **read** a key-value store and permission to **write** one are granted separately, so a run started with a scoped API token could open that memory, recognise your repeats perfectly, and still be refused every update to it. Nothing said so: the run looked completely normal, and your next run collected all of it again, description charges included. The run page now says the memory could not be updated and what that will cost next time, one uncharged row in your dataset says the same thing in full and names the permission to grant, and the run log says it beside the platform's own message — the moment the first refusal happens, not at the end of the run. Give the token key-value store **Write** (and **Create**) under *Settings → API & Integrations*, or set Actor runs to *Full access*. A run whose memory writes normally is unchanged in every respect. Nothing you type, no output column, no event and no price moved.
- **The sentence the run page shows when the repeat check is off is now the same on every actor we publish.** It said the same thing in different words on each one; it now reads identically everywhere, so running two of our actors under one limited token shows you one sentence instead of two. The full wording — the exact permission to grant and where to grant it — is unchanged and still on the uncharged row in your dataset and in the run log.
- **A run started with a limited API token now tells you why it charged you for postings you already had.** This actor remembers every posting it has delivered to your account, in a key-value store in your own account, so a re-run skips what you already have and never charges for it twice. A run started with a scoped API token in restricted-access mode cannot open that store at all unless the token is allowed key-value store **Read, Write and Create** — and when it cannot, nothing can be recognised as a repeat, so everything is collected and charged again. The run page used to say only that the repeat check was unavailable, which gave you nothing to do about it. It now names the cause and the fix, one uncharged row at the top of your dataset says the same thing (so an agent reading only rows sees it too), and the run log says it beside the platform's own message. Grant the token those three permissions under *Settings → API & Integrations*, or set Actor runs to *Full access*, or start the run from the console. Every other reason the store cannot be read reads exactly as before. Nothing you type, no output column, no event and no price moved, and the run still delivers and charges exactly as it did.
- **A dataset we could not read no longer reports as a mistake in your input.** "Continue from an earlier run" stops the run before anything is fetched when the earlier run's dataset cannot be read — that stop exists so a re-start can never hand you a duplicate row or charge you twice for the same listing, and it is unchanged: nothing is fetched, nothing is pushed and nothing is charged. But it could stop for two different reasons and only said one of them. If the id you gave could not be opened at all, that is about the id, and that row is exactly as before. If the id opened fine and the dataset then would not read — our side, and it usually clears on a re-run — the row used to be labelled an input error against the field you filled in. That row now reads `degraded` with `missReason` `SOURCE_UNAVAILABLE` and is marked retryable, which is what it always was. The sentence explaining it, the dataset it names, and the fact that nothing was charged are all unchanged. No input field, event or price moved.
- **A maintenance detail:** the private run-report this actor writes for our own support — counts only, never anything you typed — now states what a run asked for when a re-started run stopped before it could start, because a delivery record would not read back. Those runs used to report an ask of one listing whatever you had asked for, so they read to us like an empty run instead of an outage of ours. This covers both the record of the run's own deliveries and the earlier run's dataset. Nothing you type, nothing you get back and nothing you pay is different.

### 1.0.8 — 2026-09-13

- **A field name this actor does not have is now named on the run page, and recorded as your field name.** A run that arrived carrying only a name this actor neither has on its form nor reads as an alternative spelling already got one uncharged row explaining it — but the run page only said "1 input error", and internally the run was filed against "Search terms", a box you had never typed in. The run page now quotes the name you sent beside the two fields this actor reads, and the record names it properly. The single uncharged count you read is unchanged. A "Search terms" box you set and left empty, and a job link this actor cannot use, still report against those fields — those really are about them. Nothing about pricing, the form, the output columns or what gets charged changed.

### 1.0.7 — 2026-09-13

- **The field name you guessed now works.** Send search terms as `query`, `keyword`, `keywords`,
  `searchQuery`, `searchQueries`, `searchTerm`, `searchTerms`, `jobTitle` or `jobTitles`, or a
  posting as `jobUrl`, `jobLink` or `jobLinks`, and the run reads them instead of running nothing:
  the values are used exactly as sent, a term you put in both places is searched once, and one
  uncharged note names the field to use next time. Nothing about what is charged changes.

### 1.0.6 — 2026-09-13

- **The input form now opens on a first run of 25 listings, not 50.** The form arrives with an example search, and `Max job listings for the whole run` used to sit empty and fall back to 50 — so pressing Start on the example delivered up to 50 listings, about $0.30 on the Apify free plan. The form now opens with that cap set to 25, so the example run returns up to 25 listings for about $0.15 and comes back sooner. This is a starting value in the form only: the cap still falls back to 50 whenever it is left out by an API call, a scheduled task or an integration, so nothing you have already wired up changes. Nothing else moved: no input field, output column, event or price changed.

### 1.0.5 — 2026-09-13

- **A maintenance detail:** the private run-report this actor writes for our own support — counts only, never anything you typed — now states what a run asked for when the run stopped before it could start, because our own billing setup could not charge it honestly. Those runs used to report an ask of one job whatever you had asked for, so they read to us like an empty run instead of an outage of ours. Nothing you type, nothing you get back and nothing you pay is different, and no input field, output column, event or price moved.

### 1.0.4 — 2026-09-13

- **The store card now says that descriptions and salary are a second charge, and the title carries the other phrase buyers search on.** The card promised "full description, salary and seniority on request" without saying the description is charged separately — a buyer met that charge after paying. It is now said on the card, in the first paragraph of the page and in the description section (the per-description figure itself stays on the Pricing tab, where it cannot go stale). The listing is now titled *LinkedIn Jobs Scraper — LinkedIn Job Postings, Salary, No Login*. No price, no input, no output column and no charged event changed.
- **The input form's own description now says how to call this actor and what a listing costs.** An AI agent shopping for actors never opens this page — it reads the input schema, and ours said little more than what the actor does. It now opens with the call that works (`{"queries": ["software engineer"], "location": "London"}`), shows the second call for job links you already hold, says which field is required and that every other one can be left out, gives the per-listing floor price with the arithmetic for a 200-listing run, says that asking for full descriptions adds its own charge, lists what is never charged, and names the run option that caps the bill. The search terms field now names what is never charged and which sibling actor an Indeed search or a company's own careers page belongs in. Nothing else moved: no input field, output column, event or price changed.

### 1.0.3 — 2026-09-12

- **An aborted or migrated run now leaves its receipt from the first moment of the run, instead of only after start-up finishes.** The record that says how a run ended was only put in place once the run had finished starting up — reading your input, opening its stores, working out what a previous attempt had already delivered and charged. A run stopped inside that window ended with nothing written about it at all, which mattered most on a re-started run, where the thing not written about was the earlier attempt's work. It is now in place from the run's first moment. Nothing else moved: no input field, output column, event or price changed, and a run that reaches its work behaves exactly as before.

### 1.0.2 — 2026-09-12

- **Asking for more than this actor can do no longer stops the run before it starts.** A row cap above 10,000, a run time outside 30–3,600 seconds, or a posting window above 30 days used to be refused by Apify itself: no run, no rows, no explanation — just an error, which is what an API call or an AI agent guessing a round number got. The two caps are unchanged and still hard, but they now live in the actor instead of in the form: the run starts, continues at the nearest limit, and writes one extra uncharged row saying what you asked for and what bound it. A posting window LinkedIn does not offer is still never rounded to the nearest one — now the run starts and tells you that on its own uncharged row, naming the three windows, instead of failing to start at all. Nothing about a run inside the limits is different, and the note row is never charged.
- **The links to our other scrapers on this page name them correctly again.** Several of those actors were retitled on the store, and this page still used their old names. The links always pointed at the right actors; only the words were out of date.
- Nothing else moved: no input field, output column, event or price changed.

### 1.0.1 — 2026-09-11

- **First release.** LinkedIn's public job pages, read with no account and no cookie: one row per posting with title, company, location, posted date and the public job link. Ask for the full description and the row also carries the description text, the salary range LinkedIn shows on about a third of postings, and its four criteria — seniority, employment type, job function and industries.
- **Search walks the whole result list, ten postings at a time**, to LinkedIn's own 1,000-result ceiling for any one search. That ceiling is LinkedIn's, not ours. The search row says how many result pages each term walked, and the summary row names the limit that bound the run and carries the dataset ID to continue from.
- **Paste links you already hold** into "Job links" and each one comes back whole — description, salary block and criteria — billed exactly like a posting found through a search: one job listing plus one full description. A link is a request for the whole posting, so it is priced the same way whichever door it came through, and "Fetch the full description" does not change it either way. If LinkedIn serves the page without a description on it, you are charged for the listing only and the row says so.
- **Never pay for a job we did not deliver.** Every row carries `charged` and `missReason`, so the invoice reconciles from the dataset itself without opening the console. A rate limit, a sign-in redirect, a page we could not read, a result list that ran out, a posting that has been taken down, a search term or link we could not read, and your own row cap, time limit or maximum cost per run are every one of them uncharged and named on their own row — and the run still finishes successfully. Only a 404 from LinkedIn's own posting page is ever read as "this posting is gone"; a temporary problem is never dressed up as a permanent verdict.
- **A rate limit is waited out before it is reported.** When LinkedIn rate-limits a search and the run still has real time left, it waits a minute and a half to three and walks the search again on fresh routes — up to three passes — so a limit that lifts within minutes delivers postings instead of a re-run request. The row then says how long the run waited and how many minutes were left. A short run makes its one pass and reports honestly. **A rate limit is read from the status LinkedIn answered with, never from the page it served** — its throttle page is itself a redirect to LinkedIn's sign-in wall, so a reader that trusted the page would drop the route on ordinary traffic and tell you you had been challenged when you had only been rate-limited. Your row says rate-limited, and the run tries the same route again seconds later.
- **A posting already delivered to your account is never charged a second time.** Every run remembers what it delivered, in a key-value store in your own account, and a repeat is skipped before it takes a row slot, a description fetch or a charge. Turn on "Include jobs you already have" and those rows come back anyway, marked and still uncharged.
- **One posting is one row and one charge**, whether it arrives through a search, through a pasted link, on two different result pages of the same walk, or in a run that Apify moved to another server part-way through.
- **A time limit ends the collecting, never the delivering.** A result page already fetched when the clock runs out is still delivered in full, so a run never pays for postings it does not hand you.
- **Run it with no input at all** and you get a real five-posting sample — "software engineer" in the United States — charged like any run. Set only filters and no search terms and that same sample runs under them, with one extra uncharged row naming which settings were yours.
- Nothing is charged for starting a run, and only delivered results are charged.
