ATS Job Change Feed - Greenhouse & Lever avatar

ATS Job Change Feed - Greenhouse & Lever

Pricing

from $0.70 / 1,000 new job detecteds

Go to Apify Store
ATS Job Change Feed - Greenhouse & Lever

ATS Job Change Feed - Greenhouse & Lever

Monitor Greenhouse and Lever job boards and get only what changed since your last run: new, updated, and removed postings, each corroborated by a content hash. You pay per change, and unchanged rows are free.

Pricing

from $0.70 / 1,000 new job detecteds

Rating

0.0

(0)

Developer

Dakota Styck

Dakota Styck

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

3 days ago

Last modified

Share

What does ATS Job Change Feed do?

ATS Job Change Feed tracks job postings across many company career pages and tells you what changed since your last run: which jobs are new, which were updated, and which were removed. Snapshot APIs leave the diffing to you, and the incremental job feeds with any real adoption report only new and closed postings. This one reports the full diff on the boards you choose and corroborates every change with a content hash. It never bills you for a posting that didn't change.

It reads the public, no-authentication JSON APIs that Greenhouse and Lever publish so companies can embed job boards on their own websites. No browser is involved, which is why runs are fast and cheap.

Point it at 50 company boards, run it daily, and each morning you get the delta.

Why use ATS Job Change Feed?

  • Stop diffing dumps yourself. You get change_type per record: new, updated, removed.
  • Catch edits too. A retitled role, a location change, or a new department shows up as updated.
  • Know when a role closes. removed tells you a posting is gone, which a snapshot API can't tell you at all.
  • Cheap to run. HTTP-only, no browser. A run over dozens of boards takes seconds.
  • Honest about outages. If a board fails to fetch, its jobs are not reported as removed. See Reliability.

Recruiters use it to track competitor hiring, job boards use it to ingest fresh listings, and market researchers watch it for headcount signals. It also works as a plain alert that a specific company opened a specific kind of role.

How to use ATS Job Change Feed

  1. Type company names into Companies. Each one is looked up on Greenhouse first, then Lever, automatically, and a pasted careers-page URL works too. Names that match neither platform are reported in the run summary under unresolved_companies.
  2. If a company's board token differs from its name (the lookup normalizes "Epic Games" to epicgames, but some tokens are abbreviations), put the token directly in Companies on Greenhouse or Companies on Lever under Boards by platform.
  3. Optionally add filters: title keywords, locations, departments, remote-only.
  4. Run it once. The first run has no history, so by default every open job comes back as new. That's your baseline.
  5. Schedule it (daily is typical). From the second run on, you get only what changed.

First run behavior

There's no prior state on the first run, so "what changed" is undefined. You choose:

first_run_modeBehavior
emit_all_as_new (default)Returns every open job tagged new. You get a full snapshot plus a baseline.
baseline_onlyReturns nothing, silently records the baseline. The next run reports changes only.

Pick baseline_only if you only care about deltas and don't want to pay for an initial full dump.

Input

All fields are optional except that you must supply at least one company or board.

{
"companies": ["airbnb", "anthropic", "spotify"],
"greenhouse_boards": ["stripe", "databricks"],
"title_keywords": ["engineer", "data"],
"locations": ["remote", "new york"],
"remote_only": false,
"first_run_mode": "emit_all_as_new",
"emit_unchanged": false,
"include_description": false,
"hash_description": false,
"state_store_name": "ats-job-feed-state"
}

Filters are applied before change detection, so a job drifting out of your filter is never mislabeled as removed.

Output

Each dataset item is one changed job.

{
"change_type": "updated",
"change_reason": "content",
"job_key": "greenhouse:stripe:5416444",
"source": "greenhouse",
"board": "stripe",
"external_id": "5416444",
"title": "Data Analyst",
"company": "Stripe",
"locations": ["San Francisco, CA", "US"],
"department": "Data Science",
"team": null,
"employment_type": null,
"is_remote": null,
"url": "https://stripe.com/jobs/search?gh_jid=5416444",
"posted_at": "2026-06-02T12:58:57Z",
"updated_at": "2026-07-27T15:17:30Z",
"first_seen_at": "2026-08-01T09:00:00Z",
"last_seen_at": "2026-08-04T01:04:17Z",
"content_hash": "3f2a91c04be7d158"
}

The dataset downloads as JSON, CSV, Excel, or HTML.

A removed item has no current record - the posting is gone - so it carries source, board, external_id, job_key, and the timestamps, with title absent.

Data fields

FieldDescription
change_typenew, updated, removed, or unchanged
change_reasonFor updated: content, timestamp, or content+timestamp
job_keyStable identity, source:board:external_id
sourcegreenhouse or lever
boardThe board token the job came from
titleJob title
companyCompany name (Greenhouse only; Lever does not publish one)
locationsAll locations attached to the posting
department / teamOrg placement, where the source provides it
employment_typee.g. "Regular Full Time" (Lever only)
is_remotetrue, false, or null when the source gives no signal
urlPublic posting URL
posted_atWhen the posting went live, ISO-8601 UTC
updated_atVendor's last-modified claim. Always null for Lever - Lever publishes none
first_seen_atWhen this actor first saw the job; survives updates
last_seen_atThis run's timestamp
content_hashHash of the tracked fields, for your own downstream checks

A run summary is written to the key-value store under RUN_SUMMARY, with per-board counts, which boards failed, and whether any safety guard tripped.

How change detection works

Most of the difficulty is in deciding what counts as a change.

Every job carries a content hash. It covers the tracked fields: title, locations, department, team, employment type, remote flag, URL, posted date. A job is updated when the hash moves. Greenhouse also publishes an updated_at, and when that moves too you get change_reason: "content+timestamp", but on its own it never counts as a change unless you opt in via detect_timestamp_only.

That default comes from a measurement, not from caution. Greenhouse occasionally re-stamps nearly a whole board inside one minute with no tracked field changing at all. Under the opt-in, each of those events would bill you for hundreds of updates that never happened.

Lever publishes no updated_at at all. Verified against the live API on 2026-08-03: the payload carries createdAt and nothing else temporal. So for Lever the content hash is the only change signal there is, and any implementation leaning on updated_at would silently never report a Lever update.

Only fingerprints are stored. A job id, a 16-character hash, two timestamps. Tracking 10,000 jobs costs well under a megabyte of state.

State lives in a named key-value store (state_store_name). Apify gives every run a fresh default key-value store, so a baseline written there would vanish and every run would look like a first run.

Adding a board doesn't reset the others. Baselines are tracked per board, so a newly added board is a first run for that board alone while every existing board keeps diffing normally. You aren't re-billed for your whole corpus because you added one company.

Changing what is hashed does start a clean baseline. Flipping hash_description changes what the hash covers, so old and new hashes can't be compared. The state key accounts for that, so you don't get every job reported as updated. Pin state_key if you want one continuous feed regardless.

Reliability

Change feeds have a specific failure mode: an outage that looks like news. Two guards address it.

A failed fetch is not an empty board. If a board returns an error, times out, or serves a non-JSON body, that board is skipped entirely: no records emitted, baseline preserved untouched. Without this, one transient 503 would report every job on that board as removed. Failed boards are listed in RUN_SUMMARY.failed_boards.

Mass removals are withheld pending corroboration. If more than removal_alarm_ratio (default 50%) of a board's tracked jobs disappear in a single run, the removals are held back and the baseline is kept, because a truncated-but-successful response looks exactly like a genuine mass close-out. Boards with fewer than 10 tracked jobs are exempt, where the ratio would be meaningless. Set suppress_mass_removals: false to let them through immediately.

Retries use exponential backoff with jitter and honor Retry-After. A 404 isn't retried: it means the board token is wrong, and hammering a free public endpoint over a typo is rude.

How much does it cost?

Pay-per-event, two prices:

EventPrice
Run start$0.002 (charged once per run up to 1 GB of memory; this actor runs at 512 MB by default, so once)
Each new, updated, or removed record$0.001

unchanged records, when you enable them, are never charged.

You pay per change, so a large board that sits still costs you almost nothing. Measured on live Greenhouse boards of ~550 and ~800 jobs: 22-35 billable changes on a weekday, with weekends much quieter. That's a few cents either way. A daily poll over 10 boards with ~1,000 tracked jobs comes to roughly $1-2/month, and a quiet day costs $0.002, the run fee and nothing else. A snapshot API bills you for the same 500 rows every day whether or not anything moved.

The first run is the exception: with no baseline yet, emit_all_as_new bills every open job once as new. Choose first_run_mode: "baseline_only" to skip that and pay only for changes from run two onward.

Runs are HTTP-only with no browser, so compute is a small fraction of a browser-based scraper's.

Tips

  • Filter early. Title and location filters run before change detection, so narrow feeds mean cheaper runs.
  • Leave include_description off unless you need the body text; it makes Greenhouse responses much larger.
  • Turn on hash_description only if you genuinely need description edits flagged, and know what it costs. Measured on Databricks' board, 2026-08-13: an employer added one legal boilerplate link to every posting at once, so 769 of 779 jobs would have billed as updated for a change that says nothing about the job. Ten were genuine edits, and six of those were already caught without it. Board-wide edits like that are normal, not rare: of sixteen Greenhouse boards scanned that day, ten showed one, and eight of those had fired within the previous ten days.
  • Use baseline_only on the first run if you only want deltas.
  • Keep max_concurrency modest. These are free public endpoints offered as a courtesy.

FAQ

Does this need an API key for Greenhouse or Lever? No. Both publish these endpoints without authentication so their customers can embed job boards on their own sites. Greenhouse's developer documentation states that job board data is publicly available and authentication is not required for GET endpoints.

Is this scraping? Not in the usual sense. It reads documented public JSON APIs; there's no browser or HTML parsing involved, and nothing is bypassed to get the data.

Why is updated_at null on all my Lever jobs? Because Lever doesn't publish it. Change detection for Lever works entirely off the content hash. This is a property of Lever's API, not a limitation of this actor.

Why did a job show as updated with no visible difference? Only possible if you enabled detect_timestamp_only: change_reason: "timestamp" means the vendor's updated_at advanced but nothing in the content hash moved. It's off by default because Greenhouse re-stamps nearly a whole board at once whenever the employer edits every posting - often just to add a legal link - and on the event measured 2026-08-13 that touched 769 of 779 jobs. If you do want description edits, enable hash_description; those arrive as change_reason: "content", and you should expect board-wide bursts along with the edits you actually care about.

Why did no removals appear when a board clearly emptied out? The mass-removal guard held them. Check RUN_SUMMARY.mass_removal_flagged. Set suppress_mass_removals: false if you'd rather have them immediately.

Which ATS platforms are supported? Greenhouse and Lever today. Ashby's public endpoint has been verified working and is the next candidate; Workable, SmartRecruiters, and Workday are further out.

Support

Found a bug or want another ATS supported? Open an issue on the Issues tab.