Never pay for a domain we couldn't resolve. Paste a company domain — we find its Greenhouse, Lever, Ashby or Workday board and return every live opening. Nine ATS platforms, one box — no board tokens needed.
1.0.17 — the careers-page lookup's spending allowance counts correctly
The per-run allowance on our careers-page lookup now counts every lookup at its true
cost. A lookup that cost more than one unit could previously be counted as one, 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 cached copy up to two days old, 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 < 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 are free.
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.