New Job Postings Monitor — Hiring Signals & Job Change Alerts avatar

New Job Postings Monitor — Hiring Signals & Job Change Alerts

Pricing

from $2.00 / 1,000 change detecteds

Go to Apify Store
New Job Postings Monitor — Hiring Signals & Job Change Alerts

New Job Postings Monitor — Hiring Signals & Job Change Alerts

Watch company career pages on a daily schedule and get only what changed: roles opened, closed or edited, plus per-company open counts and hiring velocity. Covers Greenhouse, Lever, Ashby, Workable, Workday and 6 more ATS. Built for sales triggers and recruiting intel.

Pricing

from $2.00 / 1,000 change detecteds

Rating

0.0

(0)

Developer

Insight Solutions

Insight Solutions

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

a day ago

Last modified

Share

Track competitor hiring on a schedule and get only what changed. Point this Actor at a list of company career boards, run it daily, and every run returns the roles that opened, closed or were edited since the last run — plus a per-company summary with open-role counts and hiring velocity.

It reads each ATS's own public feed across 11 platforms, so a run finishes in seconds with no browser, no proxies, no API keys and no login. You pay per change, not per job. A quiet day costs a fraction of a cent per company.

Try it in 30 seconds

{ "boards": ["greenhouse:stripe", "ashby:ramp", "lever:leverdemo"], "emitChanged": true }

Run it once to lay down the baseline, then schedule it daily. The first useful output arrives the next morning — see First run below.

How the job posting change detection works

A career-board scraper hands you 400 postings every morning and leaves you to work out which three are new. This Actor does that comparison for you, and keeps doing it, because it remembers. Each run:

  1. Reads every board you listed, straight from the ATS's own public endpoint — 11 platforms behind one input format.
  2. Loads the fingerprint snapshot the previous run stored, one record per board.
  3. Diffs them on job ID, then on title, location and salary.
  4. Writes a row for each new, closed and changed posting, plus one summary row per company.
  5. Stores the new snapshot for tomorrow.

Supported ATS platforms: Greenhouse (greenhouse:stripe), Lever (lever:leverdemo), Ashby (ashby:ramp), Workable (workable:lyst), Recruitee (recruitee:vandebron), SmartRecruiters (smartrecruiters:Visa), Teamtailor (teamtailor:oatly or teamtailor:career.acme.com), Personio (personio:acme), Breezy HR (breezy:new-incentives), JazzHR (beta) (jazzhr:company3), Workday (beta) (workday:nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite). You can also paste a career-page URL, or pass a bare company domain (stripe.com) and let the Actor find the board.

Use cases

  • Track competitor hiring — watch 50 companies; newLast30Days per company is a hiring-velocity leaderboard, and closedCount spikes with a falling openCount are the leading edge of a freeze.
  • Sales triggers and buying signals — a company opening its first "Head of RevOps" role, or five SDR reqs in a week, is intent that shows up here weeks before a funding announcement. new rows plus titleFilter is the whole play.
  • Get alerted to new job postings for roles you care about — titleFilter and locationFilter narrow the watchlist, and the new rows are the alert.
  • Monitor career page changes without an HTML differ — the comparison happens on structured ATS fields, so a site redesign is not a change and a re-worded paragraph is not a false alert.
  • Compensation tracking — put salary in changedFields and every published pay-range revision lands with the old and new numbers side by side.
  • Feed a warehouse — the dataset is stable and flat; point the Apify → Snowflake/BigQuery/webhook integration at it and append.

How it compares

Most "hiring signal" tools re-scrape the whole board and bill you per role returned, then tell you to diff the runs yourself. This one holds state and bills for the delta.

Career Site Jobs APIThis Actor
Answers"What is open at these companies?""What changed at these companies since yesterday?"
Statenone — every run is independenta snapshot per board, carried between runs
Billed onevery posting returnedevery change, plus a flat fee per company watched
A quiet day coststhe full board, again$0.002 + $0.001 per company
Best usedad-hoc, or when you want the whole boardon a daily schedule

Two more differences worth stating plainly:

  • An ATS outage never looks like a mass closure. If a board that held 400 roles comes back with 10% or fewer, the comparison is abandoned rather than emitting hundreds of false closed alerts. Nothing else in this category does this.
  • Re-posted roles are one event, not two. A role closed and immediately re-opened under a new ATS ID is reported once, as new with reposted: true — one row, one charge.

If you need the full current state of a board rather than the deltas, use Career Site Jobs API — same connectors, priced per posting. They pair well: pull the board once to seed your database, then schedule this Actor to keep it current.

First run, and setting up a schedule

The first time this Actor sees a board there is nothing to compare against, so it will not bill you for a backfill you did not ask for. The first run stores the snapshot, writes one summary row per board with firstRun: true and the current openCount, charges actor-start and company-monitored once each, and charges no change events. Changes start on the second run. Set firstRunBehavior to emit-all for the whole board up front — every open role is then reported as new and billed once. Adding a new company re-baselines that company only.

To schedule it, run it manually once to check the input, then use Actions → Schedule Actor. Daily is right for almost everyone, and keeps the 7-day and 30-day velocity figures meaningful; early morning in the employer's timezone catches the previous day's postings. Point a Slack, webhook, email, Zapier or Make integration at "run succeeded" and filter on eventType.

One state store per watchlist. Two runs sharing one store would each diff against the same snapshot and double-report every change — so the second one stops with a clear message instead.

Where the state lives

An Actor's default key-value store is created fresh every run, so it cannot carry yesterday's snapshot. This Actor keeps state in a named store — job-postings-monitor-state by default — which works within its limited permissions: such an Actor may create storage, write to it, and re-open storages it created in previous runs. Nothing else in your account is touched, and limited permissions are a requirement for x402 agentic payments.

The one tradeoff: a limited-permissions Actor cannot read storages you created by hand. If stateStoreName points at one you made in Console, the Actor may not be able to open it — and it then logs a warning, treats the run as a first run and emits no change events, so a permissions problem never becomes a false wave of "closed" alerts. Let the Actor create its own store and this never comes up.

Each board gets one record: a small fingerprint per posting plus 90 days of daily counts (a 325-role board is ~94 KB). A record cannot exceed 9 MB and a fingerprint costs ~460 bytes, so one board tops out at about 17,000 tracked roles; beyond 8 MB the Actor refuses to write, keeps the previous snapshot, and returns a diagnostic row telling you to lower maxJobsPerBoard or narrow the filters.

Input reference

FieldTypeDefaultWhat it does
boards (required)array of strings["greenhouse:stripe"]One entry per company: ats:slug, a career-page URL, or a bare domain for auto-detection
stateStoreNamestringjob-postings-monitor-stateThe named key-value store holding the previous snapshot. One per watchlist
emitNewbooleantrueReport roles that appeared
emitClosedbooleantrueReport roles that disappeared
emitChangedbooleanfalseReport roles that were edited. Off by default — edits are noisier than openings and closures, and each one is billable
emitUnchangedbooleanfalseAlso write a free, description-free row per unchanged role, for reconciling against your own data
changedFieldsmulti-select["title","location","salary"]What has to move before a posting counts as changed. Options: title, location, salary, description
titleFilterarray of stringsnoneMonitor only roles whose title contains one of these (case-insensitive)
locationFilterarray of stringsnoneMonitor only roles whose location contains one of these
firstRunBehaviorbaseline-only | emit-allbaseline-onlyWhat to do on a board's first run
includeDescriptionbooleanfalseInclude the posting body on new and changed rows. Forced on when description is a tracked field
maxJobsPerBoardinteger2000Per-board ceiling. Keep it above your boards' size — see Reliability
atsPlatformsmulti-selectallRestricts which platforms auto-detection will consider
outageGuardThresholdinteger10(Advanced) See Reliability
requestTimeoutSecsinteger20(Advanced) Per-request timeout
maxRunSecsinteger240(Advanced) Wall-clock budget for the whole run. Boards not reached in time get a diagnostic row and keep their snapshots
maxConcurrencyinteger6(Advanced) Boards fetched in parallel; never more than one request per host

Filters decide what is watched, not just what is reported. A titleFilter of ["engineer"] means non-engineering roles are not in the snapshot at all. Changing a filter between runs will therefore look like roles opening and closing — set them once and leave them.

Output reference

Every row carries eventType and ok, so "just the changes" is ok === true && eventType !== 'summary'.

A change row (eventType: new | closed | changed | unchanged):

{
"ok": true, "eventType": "changed", "changeType": "changed",
"jobId": "8077890", "atsPlatform": "greenhouse", "boardSlug": "stripe",
"companyName": "Stripe", "companyDomain": "stripe.com",
"title": "Principal Product Designer", "department": "Design",
"employmentType": "FullTime", "seniority": "principal",
"locationRaw": "London, United Kingdom; Dublin, Ireland",
"isRemote": false, "workplaceType": null,
"salaryMin": 95000, "salaryMax": 145000, "salaryCurrency": "GBP", "salaryPeriod": "YEAR",
"descriptionText": null, // only when includeDescription is on
"applyUrl": "https://job-boards.greenhouse.io/stripe/jobs/8077890",
"jobUrl": "https://stripe.com/jobs/search?gh_jid=8077890",
"postedAt": "2026-08-11T09:04:00-04:00", "updatedAt": "2026-09-09T08:30:00-04:00",
"changedFields": ["title", "location", "salary"],
"previousValues": { // what those fields held yesterday
"title": "Staff Product Designer",
"locationRaw": "London, United Kingdom",
"salaryMin": 95000, "salaryMax": 130000, "salaryCurrency": "GBP", "salaryPeriod": "YEAR"
},
"reposted": false, "previousJobId": null,
"firstSeenAt": "2026-09-08T06:00:00.000Z", "lastSeenAt": "2026-09-09T06:00:00.000Z",
"closedAt": null, // set on closed rows only
"companyOpenRoles": 9, "companyOpenRolesDelta": -1,
"previousRunAt": "2026-09-08T06:00:00.000Z", "runComparedAt": "2026-09-09T06:00:00.000Z",
"scrapedAt": "2026-09-09T06:00:00.000Z",
"source": "boards-api.greenhouse.io",
"sourceUrl": "https://boards-api.greenhouse.io/v1/boards/stripe/jobs?content=true"
}

A closed row has the same keys, read back out of the stored snapshot — the posting is gone, so there is nothing live left to read — with closedAt holding the run that first found it missing. That is our observation time, not the employer's closing date, which no ATS publishes.

A summary row — one per company, every run — adds firstRun, openCount, previousOpenCount, openCountDelta, newCount, closedCount, changedCount (counted even when emitChanged is off), unchangedCount, repostedCount, newLast7Days, closedLast7Days, newLast30Days, closedLast30Days, newPerDay30Days, historyDays and truncated. Read the velocity fields against historyDays: newLast30Days: 4 after three days of monitoring is a three-day number, not a slow month.

A diagnostic rowok: false, eventType: "diagnostic", plus input, atsPlatform, boardSlug, error, source, sourceUrl, checkedAt — and no charge. Other boards still finish and the run still succeeds as long as at least one board was compared; a run in which no board could be compared is reported as failed.

Pricing

Pay per event. You are billed for changes, not for postings.

EventFreeStarter (Bronze)Scale (Silver)Business (Gold)
actor-start — once per run, after the run has produced at least one real row$0.002$0.002$0.002$0.002
company-monitored — once per company actually compared this run$0.001$0.001$0.0008$0.0006
change-event — once per new, closed or changed row$0.004$0.004$0.003$0.002

Worked example — 25 companies, watched daily for a month, on a Starter plan. Assume a typical 4 changes per company per week.

actor-start 30 runs × 1 × $0.002 = $0.06
company-monitored 30 runs × 25 × $0.001 = $0.75
change-event 25 × 4 × 4.3 weeks = 430 events
430 × $0.004 = $1.72
total = $2.53 / month

The same watchlist pulled as full boards, at 60 roles per company, would be 45,000 postings a month. This is the whole argument for the monitor.

Five things that are unusual, and deliberate:

  1. Diagnostic rows are free. A dead slug, a timeout, a domain with no detectable ATS — never billed.
  2. A suspected ATS outage is free. The board is skipped, not billed (see below).
  3. The baseline run bills nothing per role. First contact with a board costs one actor-start and one company-monitored.
  4. unchanged rows are free. They are a reconciliation manifest, not a data product — they carry no description body.
  5. actor-start is charged only once real output exists. A run that produces nothing but diagnostics costs nothing at all.

Reliability

Five failure modes could make a monitor actively harmful. All five are handled explicitly.

  1. An ATS outage must never look like a mass closure. If a board's previous snapshot held more than outageGuardThreshold roles (default 10) and it returns 10% or less of that, the comparison is abandoned: no closed events, previous snapshot kept, a free diagnostic row. A genuine wind-down still gets through, delayed by exactly one run — the next run seeing the same collapse is treated as confirmation and the closures are reported normally. This is the single most important correctness rule in the Actor.
  2. Unread is not closed. If a board fills maxJobsPerBoard, postings beyond the ceiling were not read — and what we did not read did not close. Closed detection is suspended for that board, unseen fingerprints are carried forward, and the summary row is flagged truncated: true.
  3. Nothing is billed before delivery, nothing delivered is lost, nothing is billed twice. Rows reach your dataset before any charge is recorded, and a snapshot is written only after its rows are pushed. If a run hits your maximum-cost limit part-way through, the snapshot advances past exactly the changes you did receive.
  4. A run that could not do its job says so. Every board failing, or the deadline arriving before any board was read, ends the run as failed with the reason in the status message.
  5. A misbehaving state store cannot destroy your history. An unreadable snapshot makes the board fall back to a baseline — never inventing closed events — and is left as it is rather than overwritten. An unwritable snapshot produces a diagnostic row, because otherwise the next run would silently re-baseline for ever.

Snapshots for boards you stop monitoring expire after 90 days. maxRunSecs (default 240) bounds the run in wall-clock time: on expiry the Actor stops fetching, keeps and bills for everything already delivered, and writes a diagnostic row for each board it never reached.

Integrations and use from code

One JSON object in, one flat array out. The Actor runs with limited permissions, uses pay-per-event pricing and never enters Standby mode, so it works over the Apify MCP server and with x402 agentic payments.

curl -X POST "https://api.apify.com/v2/acts/insight.solutions~job-postings-monitor/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"boards":["greenhouse:stripe"],"emitChanged":true}'
# pip install apify-client
from apify_client import ApifyClient
client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("insight.solutions/job-postings-monitor").call(run_input={
"boards": ["greenhouse:stripe", "ashby:ramp", "lever:leverdemo"],
"titleFilter": ["revops", "sales"],
"emitChanged": True,
})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
if row.get("ok") and row["eventType"] in ("new", "closed", "changed"):
print(row["eventType"], row["companyName"], row["title"], row["locationRaw"], sep=" | ")

FAQ

How often should I run it? Daily. Weekly is fine if you only want big moves; anything faster mostly buys duplicate changed events.

Why do I sometimes see a changed row where nothing looks different? You will not, from a timestamp alone: a bare updatedAt bump never counts as a change, because most ATS platforms touch that field for unrelated reasons. A changed row means one of your changedFields actually moved, and previousValues shows exactly what it was.

Can I track description edits? Yes — add description to changedFields. Descriptions are fingerprinted as a hash so snapshots stay small, so previousValues reports descriptionHash rather than the old body; you get the new body in descriptionText. This makes every run fetch the posting bodies, which is slower.

What is reposted? Some ATS platforms give a re-opened role a brand-new ID, which would read as one closure plus one opening. When a new role's title and location match one that just disappeared from the same board, it is reported once, as new, with reposted: true and the old previousJobId — and firstSeenAt is carried across, so "how long has this role been open" survives the ID change.

What happens if I change the board list? Companies you add get a baseline run. Companies you remove stop being compared; their snapshots expire after 90 days. Companies you keep are unaffected.

Can two schedules share one state store? No, and the Actor enforces it. The second run finds the store locked, writes a diagnostic row, and exits without touching anything. The lock is valid for that run's maxRunSecs plus five minutes, so a long run is never evicted while working, and a crashed run releases its board within minutes.

Why is a company I want missing? It is on an ATS we do not support yet (iCIMS, Taleo, SuccessFactors and BambooHR are the usual suspects); or it uses a supported ATS under a slug that differs from its brand name — check where the "Apply" button goes; or it hosts its own careers page with no ATS behind it, so there is no public feed to read.

Will I get rate limited? These are syndication endpoints — vendors publish them so postings get crawled. The Actor sends at most one request at a time to any single host and backs off exponentially on 429 and 5xx. Each attempt times out at requestTimeoutSecs (20 s by default), no single URL is retried beyond about 40 s, and maxRunSecs bounds the whole run.

  • Public endpoints only. Every source is an unauthenticated, publicly documented job-syndication feed or a public career board page. The Actor never logs in, never accepts cookies or session tokens, and never uses an API key belonging to anyone else.
  • No personal data by design. Recruiter contact details — email addresses, mailto: links, personal LinkedIn profile URLs — and recruiter or hiring-manager attribution lines are removed from every description before it is written to your dataset. Recruiter names exposed as structured fields by an ATS are never mapped into the output at all. A name written into free prose with no attribution marker is not chased: no regex catches that without mangling the description around it.
  • robots.txt is respected on every HTML page we fetch — the career pages read during domain auto-detection, and the JazzHR public board page. The file is fetched once per domain per run and evaluated per RFC 9309; a Disallow means the page is skipped. The other sources are the ATS vendors' own JSON and XML syndication feeds, which are not HTML pages.
  • You are the controller of what you do next. If you export this data, combine it with personal data, or contact people based on it, GDPR/CCPA obligations are yours, not ours.
  • Not affiliated with Greenhouse, Lever, Ashby, Workable, Recruitee, SmartRecruiters, Teamtailor, Personio, Breezy HR, JazzHR, Workday, or with any employer whose postings you monitor. All product names and trademarks belong to their respective owners and are used only to describe which public feeds this Actor reads.

Our other Actors

HTTP-only data Actors — no browsers, no proxies, charge-on-success.

ActorWhat it does
Career Site Jobs APIThe same 11 ATS connectors without the state: every open posting as normalized JSON, $2 per 1,000 postings
Website Contact ExtractorBusiness emails, phone numbers and social profiles from a list of website domains
SEC EDGAR APISEC filings, normalized XBRL financials and full-text search, by ticker or CIK
Wayback Machine ToolkitArchived URLs, archived page text, and a diff of one page between two dates