If you sell to companies or place candidates with them, one of the most useful public signals is the company's own job board. A team that opens five Account Executive roles this month is building a sales org. A board that suddenly closes forty roles is telling you something too.
Most of that data is public. Greenhouse, Lever, Ashby, Workable, SmartRecruiters and Recruitee all publish job-board endpoints so that companies can show their openings on their own careers pages. The hard parts are:
- normalizing six different formats,
- remembering what you saw last time, so you only look at what changed, and
- turning a pile of rows into "which account should I call first".
This post walks through doing that with an Apify Actor I maintain, ATS Jobs Monitor (disclosure: I built it). Everything below is copied from two real runs on 2026-10-31 (JST). Nothing is edited except trimming fields.
The input
Three target accounts on three different ATS, only sales roles, and both optional features switched on:
{
"companies": ["greenhouse:anthropic", "lever:palantir", "ashby:ramp"],
"keywords": ["account executive", "sales"],
"changeTracking": true,
"stateKey": "devto-demo",
"signalScoring": true,
"maxJobsPerCompany": 3,
"outputCompanySummary": true
}
-
greenhouse:anthropicstyle inputs are exact. You can also paste a board URL (jobs.lever.co/palantir) or a careers page, or a bare slug (ramp), in which case the Actor tries all six ATS and marks the rowmatch_confidence: "guessed". -
changeTracking+stateKeystores what was seen under that key, so the next run with the same key only outputs new, changed and closed jobs. -
signalScoringaddssignal_score,signal_tierand plain-Englishscore_reasonsto each company summary. -
maxJobsPerCompany: 3only limits how many job rows this run outputs. I used it to keep the demo short. With change tracking on, every scanned job is still saved as seen, so if you want the full list as your baseline, set it to0on the first run.
Run 1: the baseline
The run took a few seconds and pushed 7 job rows plus 3 company summaries (one per company). Here are two job rows (trimmed):
{"type": "job", "change_type": "new", "company_name": "ramp",
"title": "Senior Manager, Account Executive | Mid-Market", "seniority": "manager",
"location": "New York, NY (HQ)", "work_model": "hybrid",
"posted_at": "2026-09-10T00:03:38.552000Z", "days_open": 51, "is_long_open": true,
"ats": "ashby", "url": "https://jobs.ashbyhq.com/ramp/60ed4fac-3833-4495-9b0f-75e6dfe4b36c"}
{"type": "job", "change_type": "new", "company_name": "palantir",
"title": "Sales", "seniority": "mid", "location": "New York, NY", "work_model": "hybrid",
"posted_at": "2026-01-09T15:50:00.613000Z", "days_open": 294, "is_long_open": true,
"ats": "lever", "url": "https://jobs.lever.co/palantir/24060586-7ba8-47c0-bfc1-f1dd5b82d8bd"}
seniority is a rule on the job title (not AI), and is_long_open flags roles open for at least longOpenDays days (30 by default). For recruiter BD, long-open roles are often the easiest conversation: the company is already struggling to fill them.
The more interesting part is the company summary. This is Ramp's, trimmed:
{"type": "company_summary", "company_slug": "ramp", "total_open_jobs": 171,
"posted_last_7_days": 13, "posted_last_30_days": 48, "remote_share": 0.89,
"top_departments": ["Sales", "Engineering", "People & Talent", "Marketing", "Compliance"],
"is_baseline_run": true,
"signal_score": 87, "signal_tier": "hot",
"score_reasons": [
"+25 new demand: 48 roles posted in the last 30 days (13 in the last 7)",
"+15 seniority: 50 manager/director/executive roles (29%), 47% senior-or-above",
"+15 department: biggest build-out is sales (77 of 171 roles)",
"+10 hard-to-fill: 125 roles open 30+ days (73%)",
"+9 velocity: 13 postings this week vs 11.2/week 30-day average (1.2x pace)",
"+8 volume: 171 open roles",
"+5 remote: 89% of roles are remote"]}
The same run scored Anthropic 81 (hot) and Palantir 57 (warm). Palantir's reasons show why it ranked lower:
+19 new demand: 17 roles posted in the last 30 days (2 in the last 7)
+0 velocity: 2 postings this week vs 4.0/week 30-day average (0.5x pace)
The score is the sum of seven capped parts: recent new roles (25), velocity (20), seniority (15), function (15), long-open roles (10), volume (10) and remote share (5). Every point comes with a reason, so you can disagree with it. It is a way to sort a list, not a prediction. If you sell to engineering teams rather than sales teams, pass scoreFunctions so the function points go to the departments you care about.
Run 2: only what changed
I ran the exact same input again right after the first run. The dataset came back with 3 rows, one company summary per company, and no job rows. Ramp's, trimmed:
{"type": "company_summary", "company_slug": "ramp", "total_open_jobs": 171,
"new_jobs_since_last_run": 0, "changed_jobs_since_last_run": 0,
"closed_jobs_since_last_run": 0, "net_change_since_last_run": 0,
"is_baseline_run": false, "signal_score": 87, "signal_tier": "hot"}
That's the point: nothing changed, so there were no job rows to read and none to pay for. On a daily schedule, a later run outputs:
-
change_type: "new"for roles that weren't there before, -
change_type: "changed"when a tracked field such as title or location changed, -
job_closedrows for roles that disappeared (filled or pulled). Closed rows are free.
When a scan is incomplete (for example, a board was truncated), the Actor merges into the old state and does not report closures, so you don't get fake "closed" alerts.
Using it for sales or recruiter BD
A setup that works:
- Put 50 to 500 target accounts in
companies(useats:slugor the board URL for accounts that matter, so a bare slug can't match the wrong company). - Run once with
maxJobsPerCompany: 0as the baseline. - Schedule it daily or weekly with the same
stateKey. - Send the dataset to Slack, Google Sheets, n8n, Make or Zapier through Apify integrations.
- Each morning, sort company summaries by
signal_scoreand readscore_reasons. Use the new job rows (titles and departments) as the opening line for outreach.
Things it does not do: it has no contact data or email addresses, it only covers companies on those six ATS (for Workday sites there is a separate Workday Jobs Monitor), and it reads only public job postings.
Pricing
Pay per event on Apify:
- $0.001 per job row ($1 per 1,000 jobs)
- $0.005 per company summary
- a $0.00005 Actor start event
- closed-job rows and error rows are free
The two demo runs above would have been 7 job rows + 6 summaries ≈ $0.04 in total. A daily run over 100 accounts where nothing changed costs mostly the summaries (100 × $0.005 = $0.50); switch outputCompanySummary off if you only want changed jobs.
Try it on the Apify Store: ATS Jobs Monitor. The source is on GitHub: paopao-studio-ai/ats-hiring-signals. Questions and bug reports are welcome there.
Top comments (0)