Ats Job Scraper Change Tracker avatar

Ats Job Scraper Change Tracker

Pricing

Pay per usage

Go to Apify Store
Ats Job Scraper Change Tracker

Ats Job Scraper Change Tracker

Pricing

Pay per usage

Rating

0.0

(0)

Developer

Maximilian Waibel

Maximilian Waibel

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

Categories

Share

ATS Job Scraper with Change Tracking

Scrape company job boards on Workday, Greenhouse, Lever, Ashby, SmartRecruiters and Personio — then run it on a schedule and get only what moved: roles opened, edited, closed, or re-listed.

Every closed role tells you how many days it was open. That is time-to-fill, per company, per team — and it is a number no plain job scraper can give you, because it only exists if something remembered yesterday.


Why not just re-scrape the board every day?

You can. Every other ATS job scraper in the store will hand you all 4,000 roles every morning for about $6, and leave you to work out which two of them changed.

This one does the diff, and charges accordingly:

This ActorPlain ATS scraper
20 companies, first run$2.11~$6.00
20 companies, every day after$0.41~$6.00
Per month, on a daily schedule~$12~$180
Rows you have to read~30/day4,000/day
Roles that closedreported, with tenurenot reported
What changed in an edited rolenamed, with before/afterwork it out yourself

Watching is cheap because watching is cheap. Reading a board that has not moved costs half a cent, and unchanged roles are never billed at all.


Quick start

1. Type company names. One per line, into Companies to watch. Up to 100.

stripe
linear
Bosch Group

You do not need to find their job board — the Actor probes Greenhouse, Lever, Ashby, SmartRecruiters and Personio, works out which one hosts them, and remembers the answer so it never looks again.

2. Workday is the exception. Paste the careers page URL:

https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite

A Workday board is identified by a tenant, a shard number (wd5) and a site name that the company chose. None of the three can be guessed from "nvidia", so there is nothing to probe. Every other provider also accepts greenhouse:stripe if you already know the board.

3. Leave Only report movement on and run it.

The first run on each company returns every open role, because nothing is known yet. That is the baseline, and it is priced like the plain list it is — $0.50 per 1,000 roles, below the going rate in this store. Every run after that returns only what changed.

4. Put it on a schedule. Daily is the sweet spot for hiring. In Apify Console: Actor → Schedules → Create. That is the whole product — a scheduled run that stays quiet until something happens.


What you get back

One row per role that moved. _change tells you what happened:

_changeMeans
newthe role was posted since your last run
changedtitle, location, team, salary or another detail was edited
closedthe role is gone from the board — filled or pulled
reopeneda role that had disappeared is listed again
unchangedonly on full-dump runs, where every open role is returned

An edited role names what moved, so you do not have to diff it yourself:

{
"_change": "changed",
"_changed_fields": "location, title",
"_changed": {
"title": { "from": "Senior Backend Engineer", "to": "Staff Backend Engineer" },
"location": { "from": "Berlin, Germany", "to": "Munich, Germany" }
},
"title": "Staff Backend Engineer",
"company": "acme",
"url": "https://boards.greenhouse.io/acme/jobs/4001"
}

A closed role names itself and says how long it took:

{
"_change": "closed",
"title": "Director, Compounding Operations",
"company": "ro",
"ats": "lever",
"location": "Romeoville, IL",
"department": "Pharmacy",
"url": "https://jobs.lever.co/ro/c7f3d544-0899-4f8f-a319-c645e3928d08",
"posted_at": "2026-08-13T22:35:07.762000+00:00",
"_days_open": 17.3,
"id": "lever:ro:c7f3d544-0899-4f8f-a319-c645e3928d08"
}

Every row carries title, company, ats, location, department, employment_type, remote, url, apply_url and posted_at. Where the provider publishes them you also get salary_min / salary_max / salary_currency, hybrid, seniority and reference. Turn on Include full posting text to add description.

posted_at is a full UTC timestamp wherever the provider gives one. On Workday roles older than 30 days it is empty: the board only says "30+ Days Ago", which names no date, and a guess would be worse than nothing.


What people use it for

Competitive hiring intelligence. A rival's role closes after 9 days — they hired fast, in that function. Six engineering roles open in a week — they raised, or they are scaling. Watch 20 competitors for the price of a coffee a month and read the pattern.

Recruiting and sourcing. Know within a day when a target company opens a role you can fill, instead of finding it in week three.

Your own funnel. Your posting has been open 60 days while comparable roles elsewhere close in 20. That is your process, not the market.

Market research. Track remote policies, salary bands and location mix across a sector over months, without re-downloading the whole sector every night.


Cost

You pay per event, not per month:

$0.01per run
$0.005per company watched — an unreachable board is free
$0.0005per role on that company's first run, when there is nothing to compare against yet
$0.01per change delivered — only roles that actually moved

Worked examples, using real board sizes:

RolesCost
First run, the three prefilled companies130$0.09 once
A quiet day on those three0 moved$0.025
A normal day, a few roles move~4 moved$0.065
First run, 20 companies~4,000$2.11 once
A normal day across 20 companies~30 moved$0.41
Adding a 21st company later~200$0.105 once

Two things worth knowing about that table:

The first run is cheap on purpose. It is the run you judge this Actor on before you trust it, so it is priced below every plain job scraper in the store rather than above them. Stripe alone is 575 roles — about $0.30 to take a complete snapshot of.

Growing your watchlist costs one company. Adding a company pays that company's baseline and nothing else; the twenty you were already watching keep their history and are not re-reported. The same is true of editing a filter, or reordering the list.

Want a hard ceiling? Set a maximum cost per run when you start it. The run stops delivering at your cap instead of quietly working for free, records in RUN_SUMMARY how many rows it withheld, and reports those rows on the next run rather than losing them — still at the baseline price if that is what they were. Setting a limit delays data; it never makes it cost more.


Getting the data out

Every run writes to a dataset you can export as JSON, CSV or Excel from the Console, or pull through the API:

https://api.apify.com/v2/datasets/<DATASET_ID>/items?format=csv

On a schedule you usually want the last run endpoint, which always points at the newest results:

https://api.apify.com/v2/acts/<USERNAME>~ats-job-scraper-change-tracker/runs/last/dataset/items?token=<TOKEN>

For alerting, add a webhook under Actor → Integrations on Run succeeded and point it at Slack, Make, Zapier or your own endpoint. Because quiet runs deliver nothing, a webhook that arrives with rows in it means something actually happened — no filtering on your side.

The dataset ships two table views: What moved for the daily read, and Time to fill for the closures, sorted by how long each role was open.


Use it from an AI agent

The same Actor runs as an MCP server, so Claude, Cursor or Codex can query it directly. Enable Standby in the Actor's settings and point your client at its Standby URL. Three tools:

Tool
watch_feedwhat moved since your last call
read_feedevery open role right now
feed_statushow many roles are tracked and still open

Then ask things like "has anyone on my watchlist opened a staff engineer role this week?" and the agent polls it itself.


Settings worth knowing

Only report movement — on by default, and the reason to use this Actor. Turn it off to get every open role on every run, billed at the cheaper baseline rate.

Include full posting text — off by default. Adds the description field and makes results much larger. On Workday it costs one extra request per role, so runs there get noticeably slower, and it stops after the first 300 roles of a board. Changing it changes what a role's record contains, so it starts a fresh comparison on purpose.

Location filter / Title filter — deliver only roles whose location or title contains your text, case-insensitive. The board is still read and compared in full, so closures are still reported and changing a filter does not reset your history — you are simply billed for fewer rows.


Limits, stated plainly

  • The first run on each company returns everything. Nothing is known yet.
  • A company name is found by probing, so a company whose board is empty on one provider and live on another resolves to the live one — but a company on none of the five is reported as unresolvable rather than guessed at. Paste the URL if you know it. Two consequences worth knowing: a name resolves only when all five providers actually answered, so a rate limit fails that name for the run rather than settling it wrongly and remembering it; and a SmartRecruiters board with nothing open at all cannot be found by name, because their API answers the same way for a company with no roles as for a name that was never a company. Watch that one as smartrecruiters:token or by URL.
  • Workday needs the full URL, and caps a board at 2,000 roles. Bigger boards are read up to that ceiling and reported, but closures are not reported for them, because the full set was never visible. The run says so in its notes.
  • An unreachable board is skipped, noted, and not charged for. Transient failures (429, 503, timeouts) are retried with backoff first. The rest of your watchlist runs normally, and no closure is invented for the roles on the board that failed — a board that times out is not a board where everyone got hired.
  • A very large watchlist has a ceiling. Tracking state is checkpointed into a single platform record with an 8 MB limit; compressed, that holds roughly 150,000 tracked roles. Past it, closed roles are dropped first and the run tells you in the dataset if it still does not fit. Roles that are still open are never dropped to make room.

Where the data comes from

Only the public job-board endpoints these six providers publish for third-party use. No authentication, no login-walled data, no personal data. Recruiter names are never requested. Descriptions are employer-authored posting text and are truncated rather than republished in full. Unlisted Ashby postings are excluded.


Troubleshooting

"No public job board found for X" — the company is on Workday (paste the careers URL), on an ATS this Actor does not read, or spells its board slug unusually. Open the careers page in a browser and paste the URL you land on.

"Board not found" — the token in the URL is wrong, or the company moved boards. Open the careers page in a browser: if it redirects somewhere else, use the URL you land on.

Everything came back as new on the second run — check whether you changed Include full posting text between runs; that changes what a role's record contains, so it starts a fresh comparison on purpose. Changing the companies or the filters does not. If neither applies, open RUN_SUMMARY and check state_durable — a failed checkpoint is reported there and as a warning row in the dataset.

A run returned nothing at all — that is the normal result on a quiet day, and it costs a couple of cents. Open RUN_SUMMARY in the run's key-value store: unchanged tells you how many roles were checked and found untouched.

Closures never appear — they cannot on a company's first run, and they will not appear for a Workday board over 2,000 roles. They do appear on filtered runs, unlike most scrapers.

A department or salary field is empty — not every provider publishes every field, and not every employer fills in the ones that exist. Salary bands come from Lever and Ashby where the company entered them; hybrid, seniority and reference come from SmartRecruiters; seniority also from Personio.


Working on the code? See [DEVELOPMENT.md].