Hiring is one of the clearest public signals of what a company is about to do. Five new account executive roles mean a sales push and a need for sales tooling. A first data engineer means a data stack is being chosen. A senior role that keeps disappearing and coming back means a recruiter's pitch. The signal is in the change, not the job list: a company with 600 open roles tells you little until you know which 40 appeared this week.

This post sets up a daily watch on a list of target accounts and turns the changes into a short list for the account owners.

Disclosure: I'm Aurelix, and I built the Career Page Monitor. It's a paid tool (prices below).

What it remembers between runs

You list companies as domains (stripe.com), careers pages or job-board links. It finds the company's board on Greenhouse, Lever, Ashby, SmartRecruiters, Workable, BambooHR or Workday and remembers every role under a watchlistId. The first run is the baseline; every later run returns:

  • One company_summary row per company: ats, open_roles now and at the previous run, new_roles, removed_roles, reposted_roles, net_change, new_roles_7d and new_roles_30d, a hiring_acceleration flag (after 14 days of tracking), and the top growing departments, locations and seniority levels.
  • One role_change row per new, removed or reposted role (free): event, title, department, seniority, city, country, remote, url.

A role that comes back under a new ID with the same title and location within repostWindowDays (30 by default) is counted as reposted, not as new hiring.

A morning list for the account owners

pip install apify-client 
import os from collections import defaultdict from apify_client import ApifyClient TEAMS_WE_SELL_TO = ("sales", "revenue", "customer success", "go to market") client = ApifyClient(os.environ["APIFY_TOKEN"]) run = client.actor("aurelix/hiring-signal-monitor").call( run_input={ "companies": ["stripe.com", "ramp.com", "https://jobs.lever.co/spotify", "https://jobs.ashbyhq.com/notion"], "watchlistId": "sales-targets", "includeRoleEvents": True, } ) rows = list(client.dataset(run["defaultDatasetId"]).iterate_items()) new_in_team = defaultdict(list) for r in rows: if r["record_type"] == "role_change" and r["event"] == "new": if any(t in (r["department"] or r["title"]).lower() for t in TEAMS_WE_SELL_TO): new_in_team[r["company"]].append(r) for s in sorted((r for r in rows if r["record_type"] == "company_summary"), key=lambda s: s["new_roles_7d"] or 0, reverse=True): if s["status"] != "ok" or s["baseline"]: print(f"{s['company']}: {s['status']}{' (baseline saved)' if s['baseline'] else ''}") continue flag = " ACCELERATING" if s["hiring_acceleration"] else "" teams = ", ".join(d["name"] for d in s["top_departments_growing"]) print(f"{s['company']} ({s['ats']}): {s['open_roles']} open, {s['net_change']:+d} since last run, " f"{s['new_roles_7d']} new this week{flag}. Growing: {teams}") for role in new_in_team[s["company"]][:3]: print(f" new: {role['title']} ({role['city'] or 'no city'}) {role['url']}") 

Run it once to save the baseline, then schedule it daily (save the input as a task in the Apify Console and click Schedule). Put a Slack incoming-webhook URL in webhookUrl and each run also posts a ready-made digest of new, reposted and removed roles per company.

How other teams read the same rows:

  • Recruiting agencies: filter role_change rows on event = reposted. A role that keeps coming back is often hard to fill, and title_normalized, city and url give you the exact role to pitch.
  • Investors: run the portfolio weekly with "includeRoleEvents": false for one summary row per company, and copy open_roles, net_change and new_roles_30d into a sheet.
  • Job seekers: the companies you want to work for and a Slack webhook, daily, so a new role reaches you the morning after it opens.

If you want every open role with its full description rather than the changes, the Greenhouse Jobs Scraper returns whole boards, and its board URLs go into companies here unchanged. To watch competitors' jobs alongside their new ads, page edits and prices in one report, there is Competitor Watch.

Why this tool

  • Remembers every role between runs and reports what opened and what closed.
  • Reposted roles are told apart from new hiring.
  • Workday career sites are included.
  • Charged per company checked, whatever the size of its board.
  • What it doesn't cover: job descriptions, salary and the technologies a posting names.

Limits

  • Seven job-board systems only. iCIMS, Taleo, SuccessFactors, Oracle and home-made careers sites are not covered; those companies come back as not_found and are not charged.
  • Detection needs a careers page that links or embeds the board, or a board named like the company. Paste the board link when it fails.
  • Workday shows at most 2,000 jobs per search; larger sites are split by job family or location when Workday allows it, otherwise marked partial. Workday list pages have no department, and Workday removals are reported one run later.
  • Velocity uses the posting date the board publishes, which some employers reset when they refresh postings; BambooHR publishes none.
  • Seniority is inferred from the title and can be wrong for unusual titles.
  • Up to 50 companies per run; use several watchlists for longer lists. Changes are only as fresh as your schedule.

Cost

You pay once per company actually checked in a run, whatever its size and whatever changed; role-change rows are free, and companies whose board could not be found or read are not charged. The current price is on the Store page; Apify's free plan includes $5 of credit a month, about 500 company checks, enough for a daily watch on a dozen accounts.

Links