# Changelog of Career Page Scraper — Company Jobs by Domain, 9 ATS Boards (`steadyfetch/company-jobs-by-domain`) Actor

- **URL**: https://apify.com/steadyfetch/company-jobs-by-domain/changelog.md
- **Full Actor documentation**: https://apify.com/steadyfetch/company-jobs-by-domain.md

## Changelog

### 1.0.68 — 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.0008 the plan asked for 113 jobs where the cap paid for 125 — 12 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.67 — 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 "never pay for a domain we couldn't resolve" and the price sat below the AI-agent block; a buyer arriving from store search read a guarantee before reading that this is a career page scraper that takes a domain instead of a board token. The opening line now says that first, with the price — from $0.80 per 1,000 jobs — and the "never pay for a domain we couldn't resolve" promise kept immediately under it, word for word. This build changes nothing about the run: no input field, output column, charged event or price moved.

### 1.0.66 — 2026-09-18

- **The run page no longer runs past its own limit and loses the tail — the half that says what you were not charged for.** At the counts a real run reaches, the status line measured up to 656 characters against the run page's 500-character cut, and the platform trims the END: the repeat-check clause, the cap that stopped the run and the Issues ask all sat there. Over the 128 shapes a run can end on, 57 were over the cut and 113 had already lost a money sentence. The line now has a floor it cannot pass, and the order it gives way in is fixed: a clamped setting and a settled charge go first, then the "open an issue" ask, then the carried-over count shortens to its row total, and the sample preamble shrinks — the sentences saying what was uncharged, what your account already had and what the repeat check did are kept to the end.

### 1.0.65 — 2026-09-18

- **The shelf note about Google Jobs' search fee now reads as a live charge, not a coming one.** Nothing on this actor moved: no price, input field, output column or charged event changed, and a run costs what it cost yesterday. What changed is a neighbour this page points at — the Google Jobs actor's search fee of $0.004 per search that returns listings was announced ahead of time and is now charging, so the sentence that said it was coming says it is here. A search there that returns nothing still pays nothing, and the same block's note about the three actors that add a small delivery-conditional fee is stated in the present tense for the same reason.

### 1.0.64 — 2026-09-15

- **A run now ends on the time limit you set, even when a company's careers page stalls.** When a company's own careers page blocks us, we pay to have it rendered so your run still gets an answer. That rendered read was allowed its full 60 seconds however little of your run's limit was left — so a page that stalled could sit for up to a minute past the limit you set, and the run could end without the closing summary that tells you what was found and what was charged. A rendered read is now given only the time your run actually has left, keeping back the few seconds it takes to write the rows and the summary, so the run finishes inside your limit and still reports. A run with time to spare is unchanged: the read still gets its full 60 seconds. A run with no time limit set is unchanged in every respect.
- Nothing else moved: no input field, output column, event or price changed, and what a run charges is unchanged.

### 1.0.63 — 2026-09-14

- **A hiring board that is not this company's is no longer opened, delivered or charged.** When we read a company's careers page we take the hiring board it links. But a page can link several boards — a portfolio page, a partner list, an agency site, a "we're hiring at our clients" page — and we took whichever one appeared FIRST in the page's source, which is arbitrary. When that was another company's board, its openings were delivered under the company you asked about and charged as theirs, and the company check was charged too. The board carrying the company's own name is now the one we take, wherever it sits on the page. If a page names several boards and none of them belongs to the company you asked about, none of them is opened and none is charged — and the row names the addresses we found, so you can pass the right one in directly as a target. A page that names a single board is unchanged: that is the company pointing at its own board, and its tenant name often is not the domain name (yeti.com hires on `yeticoolers`), so nothing there had to move. This can only ever remove a charge, never add one.
- **A careers address that redirects somewhere else is no longer read as this company's.** Some domains redirect to a different company's careers site, and we read whatever answered — so a redirect could hand us a stranger's board and a company check was charged for it. We now re-check the address that actually answered, before anything is read off it. When it belongs to somebody else, nothing on it is fetched and nothing is charged, and the row says which address answered so you can see what happened. A redirect that stays with the company — `www.`, a `careers.` or `jobs.` host, the company's own country domain — is unchanged and still resolves exactly as before.
- **A board we will not attribute is never read as "this company is not hiring".** Those are two different facts and the row now says which one it is: a board we found and would not tie to this company, named, uncharged and unopened — not a company with no hiring board and not a company with no openings.
- Nothing else moved: no input field, output column, event or price changed, and no run charges more than it did.

### 1.0.62 — 2026-09-14

- **If your API token cannot open key-value stores, the run now says so — and says which permission to grant.** The check that stops you paying twice for an opening you already have lives in a key-value store in your own account. A run started from the API with a token whose permissions are limited to this Actor's scope cannot open that store, so the check silently did not run and openings your account already had were charged again — while the run page said only "repeat check unavailable" and named no cause and no fix. The run page now names both: the token cannot open key-value stores, openings you already had may have been charged again, and the fix is key-value store Read, Write and Create (or Actor runs set to Full access) under Settings → API & Integrations, or running from the console. The same sentence is now the first row of your dataset, uncharged, so a script or an AI agent that reads rows and never the run page sees it too, and the run log says it as well. A store that is merely unreadable for any other reason is unchanged and still says what it always said.
- **The run page no longer loses the tail of a long status line.** On a run with many counts to report the line could run past the 500 characters the run page shows, and the half that was cut was the one worth reading — the limit that bound, the repeat clause and where to reach us. Counts that are also in `OUTPUT` and on the rows now give way first, and the line says they did, so the invoice, the uncharged items, the binding limit and the contact line always survive.
- **If your API token can read but not write key-value stores, the run now says so before you are charged twice.** The memory that stops you paying again for an opening you already have has to be written to, not only read. A run started from the API with a token that may read key-value stores but not write them (or may write but not create the store on your first run) opened the memory, read your existing openings correctly, and then quietly recorded nothing — so the run looked perfect and your next run was charged for the whole delivery all over again. The run page now says it as soon as the first opening is delivered: the token cannot write key-value stores, the openings delivered now will be charged again next run, and the log says what to grant — key-value store Write (and Create) under Settings → API & Integrations, or Actor runs set to Full access. The same sentence lands once as an uncharged row in your dataset, so a script or an AI agent that never reads the run page sees it too. A run whose writes land is unchanged in every respect, and nothing about what this run charges has moved. The shortened form of the older “cannot open key-value stores” line now reads the same on every one of our actors, so running two of them shows you one wording and not two.
- Nothing else moved: no input field, output column, event or price changed, and what a run charges is unchanged.

### 1.0.61 — 2026-09-13

- **A hiring board that refuses the request we send no longer tells you to re-run.** When a company's board answered with an error about the request we sent rather than about the company — a page size it does not serve, a parameter it will not accept — the row said "The board was unavailable for this run … please re-run", and a re-run came back with the identical answer. That advice cost you a second run for nothing. Those answers now get their own row: it says the board refused the request we sent, that the answer is about our request and not about the company, and that it comes back the same on every re-run — so you know the run is not worth repeating. Nothing was charged for those rows before and nothing is charged now, and a board that is genuinely rate-limited, walled or down is unchanged and still says to re-run.
- Nothing else moved: no input field, output column, event or price changed.

### 1.0.60 — 2026-09-13

- **The input form's own description now says how to call this actor and what a run 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 (`{"domains": ["stripe.com"]}`), says which field is required and that every other one can be left out, names the thirteen hiring platforms it reads, gives the price of one delivered opening and of the company check beside it with the arithmetic for a twenty-company run, lists what is never charged, and names the run option that caps the bill. The company list field now says which sibling actor an Indeed search or a merged feed belongs in. Nothing else moved: no input field, output column, event or price changed.

### 1.0.59 — 2026-09-13

- **Workday boards work again.** Paste a Workday careers link — `yourcompany.wd5.myworkdayjobs.com/...` — and the run now reads the board and returns its openings. Until this build it came straight back saying "the board was unavailable for this run, please re-run", in about a second, for every Workday company. Nothing was ever charged for those rows, but the advice was wrong: the board was fine and re-running got the same answer. We were asking Workday for 100 openings at a time and Workday serves at most 20, so it refused the request outright and we read that as the board being down. Each hiring platform's own page size is now respected, so we ask for a page it will actually serve.
- **The run log now names what every hiring board answered.** A request we had got wrong came back looking exactly like a platform outage, and the run log showed nothing at all — which is why the problem above lasted as long as it did. Each board request is now written to the run log with the platform's answer, so the next time something goes wrong the run itself says so. (Run your actor at log level DEBUG to see these lines.)
- Nothing else moved: no input field, output column, event or price changed, and what a run charges is unchanged.

### 1.0.58 — 2026-09-13

- **A company board that stopped answering part-way through no longer reads as the whole board.** When a hiring platform blocked or rate-limited us in the middle of reading one company's openings, the run kept the openings it already had — that part was right — and then reported them exactly as if it had read the board to the end: "Resolved to this company's hiring board and returned 3 live openings", nothing to re-run, while the count on the same row said the board publishes 2,000. There was no way to tell "the site cut us off" from "that is all there is". That company's row now names the hiring platform that stopped answering, says this is not the whole board and how many openings it publishes, and is marked as worth re-running, which it is. A run cut short by your own time limit says that instead and names the setting to raise. Nothing was charged for the openings we could not read, and nothing about what you were charged for the ones we did read has changed.
- **A board we did not finish reading can no longer be reported as a company with no live openings.** The openings we did not get to are exactly the ones that would disprove that, so a cut-short board now says it was cut short instead of giving a verdict about the company.
- Nothing else moved: no input field, output column, event or price changed.

### 1.0.57 — 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.56 — 2026-09-12

- **Four more hiring platforms: Breezy, BambooHR, ADP WorkforceNow and Phenom People.** Thirteen boards now, up from nine. A company on one of them used to come back as "we could not positively identify a hiring board" — an honest answer you were never charged for, and no jobs either. Two of the four are a different shape: on ADP WorkforceNow and Phenom People the company's own careers site *is* the board, so those are found from the careers page rather than guessed from the domain, exactly as Workday, SmartRecruiters and BambooHR already were. Nothing about an existing platform, price or output column changed, and a board we cannot positively identify is still never charged for.
- **Two new filters: job-title keywords, and one location.** Paste a company list and narrow it to the roles you actually want. `keywords` matches the job title — any one of the words is enough — and `location` matches the place the board published, one place rather than a list. Both match whole words and the start of a word, never a bare substring: "engineer" keeps "Engineering Manager" and "ny" keeps "NY" but not "Sunnyvale", so you are not paying for openings that only look like a match. Accents fold, so "Munchen" finds "München". Openings a filter leaves out are never charged, and asking for more than 25 keywords continues the run at 25 with one uncharged row saying so.
- **A company's row now says which field a board did not publish, not just how many openings it could not check.** If you filter on location and a board publishes none, the row says so in those words instead of naming a remote flag you never asked about. A board emptied by your filters is still never reported as a company with no openings.
- **Asking for more than this actor can do no longer stops the run before it starts.** "Posted within (days)" above 30, a row cap above 100,000, a per-company cap above 5,000, or a run time outside 30–3,600 seconds 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. Those ceilings 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. 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.
- **The cost of the try-it run is now stated as a real ceiling, not a guess.** Start this actor with nothing filled in and it reads one hiring board and delivers up to five openings; this page used to call that "under a cent". On the Apify free plan those five openings plus the one company check come to about two cents. The page now says at most $0.02 on the Apify free plan and less on paid plans — an upper bound you can hold us to. Nothing about what a run charges changed: the same openings at the same price, and still nothing charged for a domain we could not resolve.
- Nothing else moved: no input field, output column, event or price changed.

### 1.0.55 — 2026-09-11

- **This actor is now listed as Career Page Scraper.** It is the same actor at the same address, with the same input, the same output and the same price — only the name on the store listing changed. A saved task, a schedule, an API call or an integration pointing at it keeps working exactly as it did, and there is nothing for you to update.
- **The one-link agent pin now sits at the top of this page.** An AI agent that reads this listing sees only its opening, and the pin — along with the actor id, the single input field a run needs, and the run option that caps what a run can spend — used to sit thousands of characters below it. All four are now in the first few lines, so an agent can pin this actor and start a capped run without reading further. The page heading now matches the listing name, and the links to our other job actors here carry the names those actors actually go by on the store. Nothing about the actor, its input or its output changed.
- **A maintenance change besides that: nothing about your runs changes.** The private run-report this actor writes for our own support — counts only, never anything you typed — now also records the per-event prices the run was charged under, so a question about a bill can be answered from the run's own record rather than from a price table read afterwards. It records the prices that already applied; it does not set them.
- Nothing else moved: no price, event or output column changed, and only delivered results are charged.

### 1.0.54 — 2026-09-10

- **A maintenance build: nothing about your runs changes.** The private run-report this actor writes for our own support — counts only, never anything you typed — gained room for four figures it does not fill in yet: how many targets a run was given, how long it waited on a blocked source, how many of those waits recovered, and a reason code for an item our own size limit refused. The build carries the shared contract so a later one can report them; on this actor every one of them is left blank, and nothing a run does or costs is different.
- Nothing else moved: no price, event or output column changed, and only delivered results are charged.

### 1.0.53 — 2026-09-09

- **Two runs started at the same time no longer erase each other's memory.** The account's repeat memory is now merged on every write, so an item one run delivered stays remembered, and a later re-run hands it back instead of charging it again.
- **A run can no longer spend past the time cap you set.** Every board request, careers-page look and platform probe now starts only if the run has room to finish it and still write the result — so a short `maxRunSeconds` costs you an immediate, uncharged row that names the cap instead of work that runs over it. A company we ran out of time to check is never reported as a company with no hiring board, and a platform we never got to is no longer listed as one we checked.
- Nothing else moved: no price, event or output column changed, and only delivered results are charged.

### 1.0.52 — 2026-09-09

- **A company board that answers "nothing here" in a way we cannot verify is no longer reported as "this company is not hiring", and is no longer charged for.** One board platform answers an empty page identically for a company with no openings and for a company id it has never heard of — so a stale or mistyped board link came back as a confident, permanent "we reached this company's hiring board and it has no live openings right now". You now get an uncharged row that says exactly that: the board answered, its answer cannot tell those two apart, and nothing was billed for it.
- **A board that returns an empty page while its own count says it holds thousands of roles is no longer sold as a definitive answer.** That contradiction used to ship as a charged company row reading "no live openings" beside a four-figure total. It is now an uncharged row, and the run says the board could not be read.
- **A board that answers "not found" is still a definitive answer, and is still charged.** Nothing changed for the platforms that turn away a company they do not know — a genuinely quiet board is still the answer you asked for.
- **A block page that arrives with a "not found" status is no longer called a dead board address.** The reason a board gave is now read from its reply, not guessed from the status alone, so a temporary wall is reported as temporary and never as "there is no hiring board at that address".
- **A run that reached every company and found their boards quiet no longer ends by asking whether something went wrong.** The run page's closing line asks you to open an issue when a run ends badly. It was deciding that from the number of job rows alone, so a run that reached every company you named, answered each one, and charged you for those answers still ended by pointing you at the Issues tab because no job rows came back. It now says so only when nothing that happened explains the empty result: an answered company, openings your own filters removed, and openings already in your account are all clean endings.
- **The same correction applies to the record the run writes back to us**, which reads that closing line: a clean run is no longer recorded as a problem run.
- Nothing else moved: no price, event or output column changed, and only delivered results are charged.

### 1.0.51 — 2026-09-06

- **A block of links pasted into one row is now read as the list you meant, whatever separates them.** "Company domains" takes one company per row. A whole block pasted into a single row was read as ONE company — the first host in it — so a paste of fifty companies checked one, charged for that one, and never said the other forty-nine had been dropped. A pasted block is now split back into its individual links whether they are separated by spaces, line breaks, tabs, commas, semicolons, pipes or nothing at all, so a column copied straight out of a spreadsheet works. Each one is then read, deduplicated and charged on its own, exactly as if you had pasted them one per row.
- **A link that carries another link inside it is still one link.** An address holding a second address in its query or its path is left whole, not broken in two, and a single link pasted on its own is never rewritten.
- **Text pasted around a link no longer breaks it.** A number, a bullet or a note sitting beside a link is ignored and the link itself is used. A row holding no link at all is still answered as one row, not one row per word.
- Nothing else moved: no price, event, output column or charge changed, and only delivered results are charged.

### 1.0.50 — 2026-09-06

- **The run's own record now counts the companies your limits left unvisited.** When your row cap filled, your maximum cost per run was reached, or the run's clock ended the walk, the companies still ahead of it were simply absent from the report this run writes back to us: a run sent two domains that delivered from one closed its books with the other named by nothing at all. Each is now counted under the limit that stopped it, so a run that stops short of your list is visible to us without you having to report it.
- **A run that stopped at your row cap now also records whether that cap really filled.** It is one true-or-false fact about the run, never the number you typed, and it lets us see a run that claimed your cap stopped it while it had in fact delivered fewer rows than you allowed.
- **Nothing you can see changed.** Your rows, your columns, your receipt row, the resume cursor and your bill are exactly as they were — only delivered rows are charged, and a company that was not visited is not charged.

### 1.0.49 — 2026-09-06

- **Openings on a board already fetched when the run's time limit lands are now delivered, never dropped.** Reading a company's hiring board is the expensive step; writing its openings out costs nothing more. When the clock ran out on that fetch, the run used to stop mid-board and hand you a truncated list — while still charging the company-check for the board it had read in full. Every opening on a board this run fetched is now delivered, charged once each exactly as before.
- **What a time limit means has not changed.** It still ends the *collecting* — no further company is looked up and no further board is fetched after it — and the summary still names `maxRunSeconds` and lists the companies that were not reached. Your row cap (run-wide and per company) and your maximum cost per run still cut the delivery exactly where you set them, and the run keeps back enough time to write the receipt.

### 1.0.48 — 2026-09-05

- **A company whose own site would not let us in is now waited out, the way a rate-limited hiring board already was.** Finding a company's board is the step everything else depends on: if this actor cannot read the company's careers page and every board it checks turns the request away, the run gives up on that company and its board is never read at all. That verdict used to arrive within a couple of seconds — on a run with fifteen minutes still in hand — as an uncharged "temporary, please re-run" row, asking you to spend the very minutes the run was holding. For a company you named yourself (a domain or a board link you pasted), the run now pauses once for a minute and a half to three minutes and looks the whole company up again on a fresh connection before any such row ships.
- **A final answer is still immediate.** A domain with no website at all, a board address that answers "no such page", and a search that reached the company's own site and found no board are all answers, not weather: they are reported at once and never waited on, exactly as before. A company given only by name is a guess at which board belongs to it, and a guess never buys the run's minutes.
- **The uncharged company row now says what was tried.** When the run waited, the row states how long it waited and across how many tries before it was still refused — placed before the charge sentence, so "nothing was charged" stays the last word.
- **Waiting is bounded and uncharged.** Both the company-lookup step and the board fetch draw on one ceiling for the whole run, never take more than the share of remaining time the size of your request allows, always keep back enough time to finish and report, and are only taken when the run can still afford the work the pause is for. Nothing about your charges changed.

### 1.0.47 — 2026-09-05

- **An opening your own filters remove, and a company whose board could not be read, are now counted against what the run asked for.** Every run of this actor keeps its own arithmetic of what was asked and what came back. That arithmetic used the row limit as the ask, so a run with the default limit of 1,000 that delivered 45 rows recorded itself as owing 955 rows nobody had ever asked for — and an opening your "Remote only" or "Posted within" setting removed, or one already in your account from an earlier run, was recorded as nothing at all. The run now asks for exactly what its boards listed, plus one row for each company's own answer, and names every opening it did not deliver. Nothing about your rows or your bill changed: the same openings are delivered, the same ones are charged, and a company with no readable board is still an uncharged row that says so.
- **A company stopped by a platform filter this actor does not read is now counted too.** You already got an uncharged row for each such company; the run's own arithmetic counted the setting once instead of the companies it stopped, so a run with twenty domains and one unreadable filter looked smaller to us than it was.

### 1.0.46 — 2026-09-05

- **A run started with no input at all now has room to wait out a rate limit.** Press Start on the untouched form and this actor runs a small real sample — one company's own hiring board, up to 5 openings, charged like any run. A board that keeps answering "too many requests" costs about a minute between the first look, the short cooldown and the second look, and the sample's own limit was three minutes — so the wait-and-look-again this actor gained in the last build could never happen on the most common first run of all: it reported "please re-run" while the limit was still clearing. The sample's time limit is now six minutes, sized to hold both looks, one pause, one more look, and enough time left over to stop cleanly and report. A sample the board answers still finishes in seconds; only a rate-limited one uses the extra time, and nothing is charged unless rows are delivered.
- **How much of its remaining time one rate-limited board may spend waiting now follows the size of what you asked for.** A run with other companies still to check keeps exactly the limit it had — no single board may spend more than half the time left on waiting — so a wide rate limit still leaves the later companies their share of the run. A run with a single company to check — the no-input sample, or one domain — may spend what is left on its one pause, because nothing else is waiting for that time. Every other bound is unchanged: one wait per board, never into the time kept back for reporting, and never when the run could not afford the idle minutes.
- **A no-input sample is now always a real, fresh look at the board.** It no longer checks, or adds to, the record of openings this account has already been given. That record is what stops you being charged twice for the same opening on a real run, and it works exactly as before on any run where you set the company domains. On the no-input sample it was making the shop window hand back openings from an earlier press of Start instead of fetching new ones, so pressing Start twice showed rows that cost nothing but also proved nothing. Every no-input sample now reads the board live and charges for the few openings it delivers.

### 1.0.45 — 2026-09-05

- **A job board that keeps rate-limiting us is now waited out inside your own time budget, instead of being handed straight back for a re-run.** Until now a board that answered "too many requests" got one more look 15 seconds later, and if that was refused too the company shipped an uncharged "please re-run" row about half a minute into a run that still held almost all of its time — on a limit that usually clears in minutes. Now, while the run still holds real time and the openings still to collect can pay for the idle minutes, it waits a minute and a half to three and looks once more. A board that answers after the wait delivers its openings exactly as before, charged as usual. A board still refused ships the same honest uncharged row, which now says what was tried: how long the run waited and how many passes it made.
- **Only a board we know exists ever waits.** When this actor has to guess which platform a company uses, one of the platforms it tries answers "too many requests" for every name it does not recognise — so a wait there would buy minutes for companies that have no board at all. Guessed boards keep the single look they always had; a board you pasted, or one a company's own careers page links to, is the one that waits.
- **What never waits:** an address that is not there, a company page that says no openings, a refusal this connection will get again, a name that is not in DNS — those are answers, and a wait cannot change them.
- **Short runs are unchanged.** A wait has to fit inside what the run has left, with time kept back to finish and report, so a 60-second run makes its one look as before and the no-input sample keeps its own three-minute window.
- **Charges are unchanged:** waiting is uncharged, a refused board is uncharged, and only delivered openings and answered companies are charged.
- The summary row carries what the waits bought — passes, seconds waited, how many limits lifted — and the run log says it in one line.

### 1.0.44 — 2026-09-04

- **Fixes a wrong number on the summary row in 1.0.43.** That build's summary row read "across N requested companies" using your *row* cap instead of the number of companies you asked for — so a one-company run said "4 requested companies". The summary row counts companies again; the run's own record keeps counting rows. Nothing about what is collected, delivered or charged changed in either build.

### 1.0.43 — 2026-09-04

- **Every run now reports what you asked for in the same unit it reports what it delivered.** The run's own record counted your ask in *companies* while it counted the result in *rows* — the job rows plus the company row each resolved company gets — so a run that returned 40 rows from 2 companies recorded an ask of 2, an ask smaller than its own delivery. Your ask is now your row cap ("Max jobs for the whole run"). Nothing about what is collected, delivered or charged changed.

### 1.0.42 — 2026-09-04

- **A run that kept your settings no longer opens by telling you nothing was set.** Every uncharged row on a filter-only sample run — the note, the company row, the summary — used to be prefixed "Nothing was set, so this run tried the default sample" and then say "You set …" in its own next breath. It now says which half was missing: you set filters and no companies, so the sample ran under them.

### 1.0.41 — 2026-09-04

- **Narrowing one setting no longer costs you the sample run.** Call this actor with no input at all and it reads vercel.com's own hiring board — 5 openings, charged like any run — so you see real rows. Set one thing first, though — a posted-within window, "Remote jobs only", a row cap — and leave **Company domains** empty, and you used to get a single row asking for companies instead of any output at all: the caller who set nothing was served better than the caller who narrowed a field. Now your settings are kept and the same sample runs under them, charged like any run. Everything you did not set takes the sample's own value, and one extra uncharged row names which settings were yours and how to run your own companies.
- **A limit you set can only shrink that sample, never grow it.** "Max jobs for the whole run", "Max jobs per company" and "Max run seconds" are ceilings: ask for at most 5,000 jobs and you get the sample's own 5; ask for at most 2 and you get 2.
- **One setting still answers with guidance instead of rows, and says why.** The sample company hires on Greenhouse, so **Only these hiring platforms** set to anything that excludes Greenhouse leaves the sample no board it is allowed to read. That case keeps its uncharged row, and the row names the field, the reason and the two ways out — name your own companies, or clear the platform filter.
- **Two other things still answer with guidance**, unchanged: a field name this actor does not recognise, and a company list you set and left empty — your list, answered with a sample, would read as if it had worked.
- A hiring-platform name this actor does not read is still refused before anything is fetched, exactly as before.

### 1.0.40 — 2026-09-04

- **The summary row now reports the same ask as the run status line.** On a run where a setting refused the companies you sent, the summary row's `requested` counted what survived the refusal — zero — while the status line and the run's own record counted what you asked for. One run, one number.

### 1.0.39 — 2026-09-04

- **A platform filter this actor cannot honour no longer swallows your whole company list.** Refusing the run is unchanged and deliberate — checking every platform anyway would bill you for exactly the boards you asked to exclude — but the domains you pasted used to vanish with it, so twenty companies came back as one guidance row. Every company you sent now gets its own uncharged row saying its board was not read and which input field stopped it.
- **Every input the run refuses now names the input field it is about** — `domains`, `atsFilter` or `resumeFromDatasetId` — on the row itself and in the run's own record, so a run that delivered nothing can say which field to fix rather than "some input was wrong".
- **The run's record now counts every row it delivered, company receipts included**, and reports the companies you asked for, counted before anything was refused. A company row is charged as its own event, so leaving it out of the count under-reported what a run actually delivered.

### 1.0.38 — 2026-09-04

- **An opening already delivered to your account is never charged a second time.** Every run now remembers what it delivered for your account, in a key-value store called `company-jobs-watch-account` in your own Apify account. Re-run the same domains and those openings are skipped before they take a row slot — no row, no charge, no bite out of your row cap — and both the status line and each company's own row say how many. The per-company answer is unchanged: "this board has N live openings today" is a fresh fact, so it is still delivered and charged every run. Delete that store to forget everything; entries older than 90 days count as new again.
- **New option: "Include openings you already have" (`includeSeen`).** Off by default. Turn it on and rows your account already has come back in every run's dataset anyway, marked `repeat: true` with `firstSeenAt` and `firstSeenRunId` naming the run that first delivered them — and still not charged.
- **A hiring-platform name we do not recognise is answered, not ignored.** The console's dropdown and Apify's own input check already refuse a bad value before a run starts, so this is a belt on top of a brace: should an unrecognised name ever reach the actor another way, it now ships one uncharged row naming it and the platforms the field accepts, and the run stops — rather than quietly reading your filter as "check all of them" and billing you for exactly the boards you asked to leave out. A single name is also accepted where a list was required (`"greenhouse"`), and capitalisation no longer matters.
- **A memory that cannot be read never stops a run.** The run delivers and charges as usual and the status line says the repeat check was unavailable, so you know a repeat may have been charged that once.
- **A mistyped "Continue from an earlier run" ID no longer creates an empty dataset in your account.** The ID is now checked and looked up read-only before anything else happens, so a typo, a pasted link or pasted rows come straight back as one honest uncharged row naming what the value looked like — instead of quietly making a storage under that name and leaving you to find it.

### 1.0.36 — 2026-09-04

- **A run that stops before it starts now reports its outcome too** — a run refused for its memory setting now reaches us the same way every other run does: counts and reason codes only, never your input or your rows.

### 1.0.35 — 2026-09-04

- **Every run now reports its own outcome to us** — counts and reason codes only, never your input or your rows — so a run that goes wrong reaches us even when nobody shares it.
- **Stop guidance names the run-wide row cap first.**

### 1.0.34 — 2026-09-04

- **The "continue from an earlier run" message now shows both fixes and the charge line**, which the old longer wording pushed off the end.
- **Run status lines fit the run page again; sample-run wording shortened.**

### 1.0.33 — 2026-09-04

- **A run that filled the row cap you set is no longer treated as a run that went wrong.** The Issues-tab line now appears only when something actually did.

### 1.0.32 — 2026-09-04

- **The run page points at the Issues tab on every ending that went wrong**, including a run refused before it started.
- **One support promise on this page** — the older three-hours line is gone.

### 1.0.31 — 2026-09-04

- **A run that ends with a problem now says where to reach us.** A miss, an input error, an early stop or a failed run closes by pointing at the Issues tab and naming the reply time; a fully delivered run is left alone.
- **One support promise across this page** — issues are answered in a couple of hours, always within a day.

### 1.0.30 — the screenshots load from Apify's own storage

The two console screenshots on this page — the input form and the dataset table a real run delivers — are now served from Apify's own storage instead of an outside host, so every picture and every link on this page stays on Apify. Same pictures, same page. What the actor does and what it charges are unchanged.

### 1.0.29 — the suite blocks name the sibling fees that start 16 September 2026

The suite blocks at the bottom of this page now state the three delivery-conditional fees that start 16 September 2026 on sibling actors — keyword volume's fresh lookup, profile posts' profile lookup, and Google Jobs' search fee. This page also now says "nothing charged for starting a run" wherever it used to say "no start fee" — the same promise, in the words the whole shelf uses. Nothing changes on this actor: same price, same events, same rows.

### 1.0.28 — real screenshots of the input form and the delivered openings

Two pictures from the Apify console are now on this page: the input form you fill in, and the dataset table a real run delivers, straight from a company's own hiring board. The suite links at the bottom were re-checked against the live store listings, and one FAQ answer that still described the retired placeholder sample was corrected to match the rest of the page. What the actor does and what it charges are unchanged.

### 1.0.27 — run it with no input and get a real sample, not placeholder rows

Running the actor with an empty input (`{}` from the API, an AI agent, or a saved task that was never filled in) used to return uncharged placeholder rows and no openings. It now reads one real hiring board — vercel.com's, up to 5 openings, inside three minutes — charged like any run, so the first thing you see is actual output. The status line and the summary row say it was the sample and how to run your own companies. If that board cannot be reached at that moment, you still get one uncharged company row explaining it, never an empty result. Real runs are unchanged.

### 1.0.26 — run it from an AI agent, and where feedback goes

Two additions to this page, nothing else: a one-line setup for running this actor from an AI agent over MCP (pin it by URL, no store search needed), and a note on where issues and feedback go. What the actor does and what it charges are unchanged.

### 1.0.25 — continue an earlier run without paying twice

- **A run that stopped at a cap can now be continued, and nothing you already have is charged
  again.** Until this build the summary row's advice after a cap was to raise the cap and re-run —
  and the re-run, starting from an empty dataset, fetched and charged the whole head of the list a
  second time. There is now a "Continue from an earlier run" input (`resumeFromDatasetId`): give it
  the earlier run's dataset ID and every opening already in that dataset is skipped — never fetched,
  never charged — so a higher cap collects only what is new. The summary row now carries the run's
  own `datasetId` (what you paste next time) and, on a continued run, `carriedOver`: how many rows
  were skipped because you already had them; the company rows say the same per company (`carriedRoleCount`).
- **The advice is honest again.** A capped run's summary now tells you exactly how to collect the
  rest without paying for it twice, instead of telling you to re-run.
- **A dataset that cannot be read stops the run before it fetches anything, uncharged.** Continuing
  blind could have billed you twice, so the run refuses, says why on an uncharged row, and asks you
  to check the ID.

### 1.0.24 — "New York, NY (HQ)" is New York, US

- **A note in brackets after the state no longer costs you the country.** Boards commonly
  write an opening's location as "New York, NY (HQ)" or "Springfield, IL (Hybrid)" — the
  bracketed word is an office note, not part of the place. Yesterday's build read the whole
  tail as one unknown fragment and left `location.country` empty; it now reads the state and
  the country underneath it, so those openings arrive as region `NY` / country `US`. A line
  where nothing outside the brackets is a place we recognise is still left exactly as the
  board wrote it.

### 1.0.23 — the country on an opening is a country, and an optional field can be sent as null

- **`location.country` is now a real two-letter country code, or empty — never a state and
  never a postcode.** Until now the board's location line was split on its commas and
  whatever came last was called the country, so an opening in "Kemmerer, WY 83101" arrived
  with a country of `"WY 83101"`, and one in "Los Angeles, CA" arrived as `"CA"` — which is
  Canada's country code, not California's. Both now read country `"US"`, with the state in
  `location.region` and the postcode in the new `location.postcode`. Country names are read
  too: "Oxford, England, United Kingdom" is country `"GB"`, and Workday's own
  "US, CA, Santa Clara" order is understood. When a line names no country we can prove, the
  field is empty rather than a guess — and the board's untouched line is always in
  `location.raw`, so nothing is lost either way.
- **Optional fields now accept `null`.** Templated callers — n8n, agent frameworks, anything
  that fills a request from a form — send `null` for every field left unset, and the platform
  used to refuse those runs before they started. Every optional field now accepts `null` and
  reads it as "use the default". Required fields are unchanged.

### 1.0.22 — a throttled board gets a second look, and a blocked careers page a second try

- **A hiring board that rate-limits or drops the request is now given one short pause and
  one more look before the run calls it unreachable.** That verdict used to go into your
  dataset the first time a board said "too many requests", and the re-run it asked for was
  yours to start. Boards that answer normally are untouched, and a 404 or a dead address
  still gets its immediate permanent answer with no delay at all.
- **The careers-page lookup we fall back on when a company's own site blocks us now
  retries a dropped connection once.** A single network drop used to end that lookup, so
  the company came back "please re-run" even though the page was readable a second later.
  What that lookup costs you is unchanged: it has never been billed to you.
- **Both new retries stand down when your run is nearly out of time**, so a run close to
  its limit spends its last seconds delivering rows and writing the summary, never waiting.
- **Several companies are read at once, as before, and your row cap still binds exactly.**
- **The sample rows a run with no input returns now say they are samples.** The demo
  summary read "0 jobs delivered" underneath a set of sample rows, which looked like a
  failed run; every demo row now says what it is and what a real run puts in its place.

### 1.0.21 — the company check is now named in the console

- **The Company check event is now spelled out in the input and output descriptions.** The
  input field and the Company answers output now say what this page and the Pricing tab
  already did: each company we positively identify is charged once as a Company check
  ($0.002) — a verified zero-openings answer included — on top of the per-job price, and
  domains we cannot resolve are never charged. Nothing about what this actor delivers or
  charges changed.

### 1.0.20 — clearer release notes

- **This page's notes now read more plainly.** Nothing about what this actor delivers or
  charges changed.

### 1.0.19 — suite links now point at the full live shelf

- **Every actor named in this README is now a live store link.** All 25 steadyfetch
  actors are published, so the suite tables link straight to each store page. Nothing
  about what this actor delivers or charges changed.

### 1.0.18 — interrupted billing settles safely, and a revived run keeps its time window

- **Finishing an interrupted run's billing can never risk a double charge.** A run that
  resumes after an interruption first settles anything its earlier attempt delivered but had
  not yet billed. If that settlement is itself interrupted part-way, the run now stops at the
  exact point of failure and records only what actually went through — a charge that may
  already have landed is never sent again, and anything genuinely unbilled is finished by the
  next resume. As always, `charged` never exceeds what was delivered.
- **A run revived long after an interruption now gets a working window under your own "Max
  run seconds" too.** The revival window added in 1.0.13 covered the run's overall ceiling;
  a very late revival could still land past the limit you set and stop at once, pointing at
  that setting. It now gets the same bounded window to finish, and your setting is never
  blamed for a stop it did not cause.
- **Every kind of account-side refusal from our careers-page lookup provider now stops the
  lookups straight away.** 1.0.16 stopped asking when our key was refused; one further
  refusal shape could still be retried once per company. All such refusals now stand down
  immediately — that lookup was never billed to you either way.
- **A numeric setting sent as `null` or an empty value now means "use the default".**
  Automation tools that template their inputs often send `null` for a knob they leave alone;
  that used to be read as the smallest allowed value — `maxItems: null` became 1. It now
  behaves exactly like leaving the field out.

### 1.0.17 — the careers-page lookup's spending allowance counts correctly

- **The per-run allowance on our careers-page lookup is now measured exactly.** Some
  lookups could previously be under-counted, so a
  lookup-heavy run could spend past the allowance before its clean stop engaged. That
  allowance is ours and was never billed to you — nothing changes about what a run delivers
  or charges.

### 1.0.16 — two new filters, and shorter answers when an input field is misspelled

- **"Remote jobs only" and "Posted within (days)" are now input options.** Openings they leave
  out are never charged. They lean on what each hiring board actually publishes: Greenhouse
  and Workday publish no remote flag at all, and some boards publish no posting date — an
  opening we cannot check against your filter is left out too, and the company's own row says
  how many and why. A board emptied by your filters is never reported as a company with no
  openings.
- **The row you get for a misspelled input field no longer says "Nothing was charged" twice.**
- **If our careers-page lookup provider ever refuses our key, the run stops asking straight
  away** rather than retrying it for every company. That lookup was never billed to you.

### 1.0.15 — a blocked careers page no longer counts as proof there is no hiring board

- **A careers page that answers with a bot check or a security screen no longer counts as a look at that company.** Such a page used to be read as "we saw this company's careers page and it links to no board", which could end the run with a final "no hiring board found". Blocked pages now come back as temporarily unreachable and ask you to re-run, and the deeper careers lookup runs on them the way it was meant to.
- **A board link that no longer exists now says so and names the address** — a retired board, a moved company, a mistyped URL — instead of reporting that the board answered with no openings. A board that really is empty reads exactly as before.

### 1.0.14 — a typo in an input field name now gets a helpful pointer instead of sample rows

- **A misspelled input field is named back to you instead of quietly running the sample.** If you
  send your companies under a field this actor does not have — `company` instead of `domains`, say —
  the run used to look identical to an empty run and answered with the uncharged sample rows, so a
  typo looked like it had worked. It now returns one uncharged row that names the field it did not
  recognise, names the field your companies belong in, and shows the exact shape to send. Sample
  rows are reserved for a genuinely untouched input, and nothing is charged either way.

### 1.0.13 — the alternate careers route always reads the live page, and a revived run gets time to finish

- **A blocked careers page is always re-checked live, never from a stale cache.** The alternate
  route used to be allowed to answer from a stale cached copy, so a company whose
  site had already stopped blocking automated visitors could keep coming back "temporarily
  unreachable" until that copy expired. The alternate route now always reads the page as it is
  right now, so a re-run after a block sees the current site.
- **When the alternate route stops answering, the run stops asking.** If the route becomes
  unavailable mid-run, later blocked companies in the same run no longer each retry it — their
  rows go straight to the same honest "temporarily unreachable", still costing you nothing.
- **The alternate route's internal spending cap is now enforced exactly**, even while several
  companies are being checked at the same time. Nothing about this check is billed to you;
  this only tightens our own spending, and no row or charge behaves differently.
- **A run revived after its time window had already passed can now actually run.** Reviving a
  run hours later used to compute a deadline that was already behind it, so it stopped on its
  first step, delivered nothing, and blamed your `maxRunSeconds`. It now gets a short fresh
  window — enough to finish the work, never a second full one.
- **Encoded text in job descriptions is decoded exactly once.** A description that literally
  spells out a code like `&lt;` no longer has it decoded a second time into a stray `<`; the
  text now reads exactly as the board published it.

### 1.0.12 — companies whose careers page blocks automated visitors now get checked through an alternate route

- **A careers page that blocks every direct read is no longer a dead end.** Some company sites
  answer every automated visitor with a block page. Those companies used to always come back
  "temporarily unreachable — please re-run", even though re-running hit the same wall. The run
  now checks such a page through an alternate route: if it links a hiring board, the company
  resolves and its openings are delivered as normal; if it genuinely has none, the row can now
  say so definitively instead of suggesting another try.
- **A block page on the alternate route is never mistaken for an answer.** If the alternate
  route is walled off too, the row stays "temporarily unreachable" — exactly as before — rather
  than turning a blocked read into a false "no board found".
- Charging is unchanged: jobs and definitive company answers are charged exactly as before, and
  nothing about this check is ever billed to you.

### 1.0.11 — companies with no public job board now get a clear final answer instead of a retry suggestion

- **When a company has no public job board, the row now says so.** Most of these used to come
  back marked "temporarily unreachable — please re-run", because one of the platforms we check
  answers every unknown company with a block page instead of a plain "no such company". Re-running
  never changed the answer. The row now gives the final verdict, names the platforms that were
  checked, and tells you to paste the board URL directly if you know it. Still never charged.
- **A platform that genuinely could not be reached is named in the row**, so you can see how
  complete that answer is rather than taking it on trust.
- **A real outage is still a re-run.** If most of the platforms never answered, or the company's
  own careers page was blocked outright, the row still reads "temporarily unreachable" and still
  costs nothing.

### 1.0.10 — when two of your domains share a hiring board, both company rows tell the truth

- **A company row now only claims the openings actually filed under it.** Two domains that
  resolve to the same board (a parent and its brand, say) used to have the shared openings
  counted twice — once in each company's `openRoleCount` — even though only one set of job
  rows was ever delivered and charged. The second company's row now reports them in a new
  **`sharedRoleCount`** field, so the two numbers add up to what is in your dataset.
- **The merge that never happened is gone.** The run used to "merge" a second sighting into
  the row it had already delivered. Nothing came of it — a pushed row cannot be changed — so
  the code no longer pretends otherwise, and the field values you get are exactly the ones
  from the board that shipped the opening.
- **A board whose openings all shipped under another company is no longer reported as
  empty.** It used to be marked as a verified zero ("no live openings right now"), which was
  the opposite of true. It now says how many openings were delivered under the other company,
  one row and one charge each.
- A run that stopped at your row cap or deadline no longer tells you to re-run with
  `resumeCursor` — it is a field on the summary row, not an input this actor accepts. The
  summary now says what actually works: raise the caps and run again.

### 1.0.9 — a run that gets interrupted never delivers or charges the same opening twice

- **Restarts are safe now.** If the platform moves your run to another server part-way
  through — or you resurrect a run yourself — it reads back what it already delivered
  and continues from there. No duplicate rows, and no second charge for an opening you
  already have. Resurrecting a run that already finished fetches nothing, charges
  nothing, and says so.
- **A charge interrupted between delivery and billing is settled, once.** A row that
  says `charged: true` is now always a row that was actually billed, even if the run was
  stopped in the half-second between the two.
- **`maxItems` still means `maxItems` after a restart.** The row cap used to start
  counting again from zero on the new server, so an interrupted run could return up to
  twice the cap you set. It now counts what the run has already delivered.
- **`maxRunSeconds` no longer restarts either.** A restarted run inherits the time it
  has left instead of beginning its whole window again.
- If a restarted run cannot read back its own results, it stops and says so rather than
  risk billing you twice — nothing is fetched and nothing is charged.
- **A restarted run's counts cover the whole run**, not just the part after the restart:
  a resumed container used to report "0 companies" while its company rows sat in the
  dataset. A company answered before the interruption is counted exactly once, and one
  that could not be resolved keeps its "not resolved" verdict.
- Status lines count correctly at one ("1 job", "1 company") wherever they are printed.
- The README now links the full output of a real verified run, so you can see every
  field before spending anything.

### 1.0.4 — review-fix build: every Workday opening keeps its own row, and a company row only claims a charge that landed

- Workday boards: each opening's id now comes from its own job URL. On a board whose job
  cards all carry the same label — "Full time", say — several different openings used to
  end up sharing one id, and all but the first were dropped as duplicates. Every opening
  on those boards is now delivered, once.
- A company row can no longer report `charged: true` when the company check is not
  actually set up to bill. The answer itself is unchanged and still delivered: the row is
  simply marked uncharged, it no longer draws down the budget your job rows need, and the
  run says so once instead of once per company.

### 1.0.3 — pre-publish sweep: honest `charged`, a one-row cap that delivers one row

- The charging regime is now read from the run's own price list, so an actor that is not
  set up to bill — or is set up to bill an event that does not exist — never pushes a row
  claiming it was charged, and never calls the charge API. `charged` on every row now
  reports what actually happened instead of what was intended.
- A run whose maximum cost was worth exactly one job row used to deliver nothing: the
  usage reserve floored the single slot to zero. A cap worth one row now returns one row.
- `maxItems` is now claimed before a row is fetched rather than counted after it is
  pushed, so several companies being checked at once can no longer overshoot the row cap
  you set. "Limits are hard limits" is now true under concurrency.
- If the actor is ever mis-priced so that pushing a row would itself bill you, the run
  now stops before pushing anything and says so, instead of billing for rows this actor
  promises carry no result fee.
- README now leads with a real output row, and carries the full steadyfetch shelf.

### 1.0.2 — initial release

Paste a company domain and get that company's live openings from its own hiring board.

- Finds the board four ways: a board URL you paste, a link on the company's own careers
  page, the domain name matched against a board token, or a company name.
- Reads nine hiring platforms through their own public job-board APIs: Greenhouse, Lever,
  Ashby, Workday, Workable, SmartRecruiters, Rippling, Personio and Teamtailor.
- One row per opening with title, company, location, remote flag, employment type,
  department, salary, posted date, full description, job URL and apply URL — plus one
  company row per domain and one summary row per run.
- Charged once per delivered opening, and once per company we can give a definitive
  answer about — including a verified "not hiring right now".
- Nothing is charged for a domain we could not resolve, a board that rate-limited or
  blocked us, an input we could not read, or a run that stopped at one of your limits.
- `maxItems`, `maxItemsPerCompany` and `maxRunSeconds` are hard limits: the run stops
  cleanly, tells you which limit bound, and hands back a resume cursor.
- Every row carries `charged` and `missReason` so the invoice reconciles from the dataset.
- Running with no input returns uncharged sample rows showing the exact output shape.
