New Job Postings Monitor — Hiring Signals & Job Change Alerts
Pricing
from $2.00 / 1,000 change detecteds
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
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
a day ago
Last modified
Categories
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:
- Reads every board you listed, straight from the ATS's own public endpoint — 11 platforms behind one input format.
- Loads the fingerprint snapshot the previous run stored, one record per board.
- Diffs them on job ID, then on title, location and salary.
- Writes a row for each
new,closedandchangedposting, plus onesummaryrow per company. - 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;
newLast30Daysper company is a hiring-velocity leaderboard, andclosedCountspikes with a fallingopenCountare 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.
newrows plustitleFilteris the whole play. - Get alerted to new job postings for roles you care about —
titleFilterandlocationFilternarrow the watchlist, and thenewrows 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
salaryinchangedFieldsand 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 API | This Actor | |
|---|---|---|
| Answers | "What is open at these companies?" | "What changed at these companies since yesterday?" |
| State | none — every run is independent | a snapshot per board, carried between runs |
| Billed on | every posting returned | every change, plus a flat fee per company watched |
| A quiet day costs | the full board, again | $0.002 + $0.001 per company |
| Best used | ad-hoc, or when you want the whole board | on 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
closedalerts. 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
newwithreposted: 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
| Field | Type | Default | What 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 |
stateStoreName | string | job-postings-monitor-state | The named key-value store holding the previous snapshot. One per watchlist |
emitNew | boolean | true | Report roles that appeared |
emitClosed | boolean | true | Report roles that disappeared |
emitChanged | boolean | false | Report roles that were edited. Off by default — edits are noisier than openings and closures, and each one is billable |
emitUnchanged | boolean | false | Also write a free, description-free row per unchanged role, for reconciling against your own data |
changedFields | multi-select | ["title","location","salary"] | What has to move before a posting counts as changed. Options: title, location, salary, description |
titleFilter | array of strings | none | Monitor only roles whose title contains one of these (case-insensitive) |
locationFilter | array of strings | none | Monitor only roles whose location contains one of these |
firstRunBehavior | baseline-only | emit-all | baseline-only | What to do on a board's first run |
includeDescription | boolean | false | Include the posting body on new and changed rows. Forced on when description is a tracked field |
maxJobsPerBoard | integer | 2000 | Per-board ceiling. Keep it above your boards' size — see Reliability |
atsPlatforms | multi-select | all | Restricts which platforms auto-detection will consider |
outageGuardThreshold | integer | 10 | (Advanced) See Reliability |
requestTimeoutSecs | integer | 20 | (Advanced) Per-request timeout |
maxRunSecs | integer | 240 | (Advanced) Wall-clock budget for the whole run. Boards not reached in time get a diagnostic row and keep their snapshots |
maxConcurrency | integer | 6 | (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 row — ok: 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.
| Event | Free | Starter (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.06company-monitored 30 runs × 25 × $0.001 = $0.75change-event 25 × 4 × 4.3 weeks = 430 events430 × $0.004 = $1.72total = $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:
- Diagnostic rows are free. A dead slug, a timeout, a domain with no detectable ATS — never billed.
- A suspected ATS outage is free. The board is skipped, not billed (see below).
- The baseline run bills nothing per role. First contact with a board costs one
actor-startand onecompany-monitored. unchangedrows are free. They are a reconciliation manifest, not a data product — they carry no description body.actor-startis 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.
- An ATS outage must never look like a mass closure. If a board's previous snapshot held more than
outageGuardThresholdroles (default 10) and it returns 10% or less of that, the comparison is abandoned: noclosedevents, 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. - 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 flaggedtruncated: true. - 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.
- 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.
- A misbehaving state store cannot destroy your history. An unreadable snapshot makes the board fall back to a baseline — never inventing
closedevents — 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-clientfrom apify_client import ApifyClientclient = 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.
Legal and data-protection notes
- 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.txtis 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; aDisallowmeans 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.
| Actor | What it does |
|---|---|
| Career Site Jobs API | The same 11 ATS connectors without the state: every open posting as normalized JSON, $2 per 1,000 postings |
| Website Contact Extractor | Business emails, phone numbers and social profiles from a list of website domains |
| SEC EDGAR API | SEC filings, normalized XBRL financials and full-text search, by ticker or CIK |
| Wayback Machine Toolkit | Archived URLs, archived page text, and a diff of one page between two dates |