Hiring Signals Monitor avatar

Hiring Signals Monitor

Pricing

from $2.00 / 1,000 job records

Go to Apify Store
Hiring Signals Monitor

Hiring Signals Monitor

Monitor a list of companies' career pages through their ATS APIs (Greenhouse, Lever, Ashby, Workday, Workable, SmartRecruiters and more) and get every open role normalized, plus change events: new roles, removed roles, hiring surges and new functions.

Pricing

from $2.00 / 1,000 job records

Rating

0.0

(0)

Developer

Al Moutasem Nabil

Al Moutasem Nabil

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

14 hours ago

Last modified

Categories

Share

Watch a list of companies and know the moment their hiring changes. Give the Actor company career pages; it reads each one through the applicant tracking system's public API (Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Workday and more), normalizes every open role, and on repeat runs reports exactly what changed: new roles, removed roles, hiring surges and new functions a company started hiring for.

Hiring is a buying signal for sales teams, a business-development signal for recruiting agencies and a strategy signal for investors and competitors. Job-board scrapers give you a pile of postings. This Actor gives you a diff over the accounts you care about, ready for Slack, email or your CRM.

Who is it for

  • B2B sales teams watching target accounts: a company that just opened five sales roles in Riyadh is expanding there; a company that started hiring data engineers is a new buyer for data tooling.
  • Recruiting agencies spotting clients who are hiring for a function they never hired for before, or whose open roles keep growing.
  • Investors and analysts tracking portfolio companies and competitors: headcount plans, geographic expansion, senior leadership hires.
  • Competitive intelligence teams: which teams a rival is building, which they are closing.
  • Job seekers watching a shortlist of dream companies for the exact role to appear.

What makes it different

  • Company-list monitoring with change detection. A compact snapshot per watch list is kept between runs; every run emits job_added, job_removed, job_updated, hiring_surge, new_function and company_error events.
  • ATS APIs, not HTML scraping. Twelve ATS integrations read the same public JSON/XML endpoints the career pages themselves use, so data is fresh, complete and stable. HTML parsing is only a fallback for unknown career sites.
  • Auto-detection. Paste a careers URL or even just the company website; the Actor finds the ATS by URL pattern, by scanning the page for embedded ATS references, or by verifying a guessed board id. No configuration per company.
  • Normalized records. Function (13 classes) and seniority (10 levels) from rule-based classification, ISO country codes, remote/hybrid/onsite, salary when the ATS exposes it. No LLM, so classification adds zero cost per record.
  • Per-company isolation. One unreachable ATS becomes a company_error event that carries the previous job count, and never fails the run.
  • No personal data. Recruiter names, emails and phone numbers are dropped even when the ATS returns them.

Supported ATS

ATSData sourceDetection
GreenhouseJob Board APIboards.greenhouse.io/{token}, job-boards.greenhouse.io/{token}, embed scripts
LeverPostings APIjobs.lever.co/{company}
AshbyPosting API (with compensation)jobs.ashbyhq.com/{board}
WorkableWidget APIapply.workable.com/{subdomain}
SmartRecruitersPosting API (paginated)careers.smartrecruiters.com/{Company}
RecruiteeOffers API{company}.recruitee.com
PersonioPublic XML feed{company}.jobs.personio.de
BambooHRCareers list JSON{company}.bamboohr.com/careers
Breezy HRPublic JSON{company}.breezy.hr
WorkdayCXS jobs endpoint (paginated){tenant}.wd{N}.myworkdayjobs.com/{site}
TeamtailorRSS feed{company}.teamtailor.com or custom career domains
Rippling, Jobvite, iCIMS, JazzHRHTML / JSON-LDURL patterns
Anything elseJSON-LD JobPosting or job links on the page (confidence: "low")fallback

Input

FieldTypeDefaultDescription
companiesarrayrequiredCareers URLs as strings, or objects { name, careersUrl, website, ats, atsIdentifier }.
trackingIdstringName of the watch list; snapshots are stored under it. Required in change mode.
emitChangesOnlybooleanfalseOutput change events instead of every open role.
emitBaselineAsAddedbooleanfalseOn the first run for a trackingId, emit every role as job_added instead of a silent baseline.
surgeThresholdinteger5New roles at one company in one run that trigger hiring_surge.
includeDescriptionbooleanfalseAdd the plain-text description (extra requests on SmartRecruiters and Workday).
filtersobjectfunctions[], countries[] (ISO-2), seniority[], titleKeywords[], excludeTitleKeywords[].
maxJobsPerCompanyinteger0Cap per company (0 = unlimited).
maxConcurrencyinteger10Companies checked in parallel.
proxyConfigurationobjectApify datacenterProxies for the requests.

Snapshot mode (every open role, once):

{
"companies": [
{ "name": "Tamara", "careersUrl": "https://boards.greenhouse.io/tamara", "website": "https://tamara.co" },
{ "name": "Ziina", "careersUrl": "https://jobs.ashbyhq.com/ziina", "website": "https://ziina.com" },
"https://jobs.lever.co/nium",
"https://apply.workable.com/salla",
"https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite"
],
"filters": { "countries": ["AE", "SA"], "functions": ["engineering", "sales"] }
}

Change mode (schedule this daily):

{
"companies": [
"https://boards.greenhouse.io/tamara",
"https://jobs.ashbyhq.com/ziina",
"https://jobs.lever.co/nium"
],
"trackingId": "gulf-fintech-accounts",
"emitChangesOnly": true,
"surgeThreshold": 5
}

The first run under a new trackingId records the baseline silently (set emitBaselineAsAdded to get every role as job_added instead). Every later run emits only what changed.

Output

Snapshot mode: one item per open role. A real record from a Gulf fintech on Greenhouse:

{
"company": "Tamara",
"companyWebsite": "https://tamara.co",
"ats": "greenhouse",
"atsIdentifier": "tamara",
"jobId": "4683204101",
"title": "Application Support Engineer",
"url": "https://job-boards.eu.greenhouse.io/tamara/jobs/4683204101",
"department": null,
"team": null,
"location": {
"raw": "Saudi Arabia",
"city": null,
"region": null,
"country": "SA",
"remote": "unknown"
},
"function": "engineering",
"seniority": "mid",
"employmentType": null,
"postedAt": "2025-11-13T15:49:52.000Z",
"salary": null,
"firstSeenAt": "2026-09-05T17:05:54.732Z",
"lastSeenAt": "2026-09-05T17:05:54.732Z",
"confidence": "high"
}

Change mode: one item per change event. These are real items from the proof run (the snapshot was edited by hand to remove roles, so the next run reports them as added):

hiring_surge, when a company added at least surgeThreshold roles in one run:

{
"type": "hiring_surge",
"company": "Tamara",
"companyWebsite": "https://tamara.co",
"ats": "greenhouse",
"trackingId": "proof-run",
"observedAt": "2026-09-05T17:06:07.168Z",
"count": 6,
"threshold": 5,
"titles": [
"AML Transaction Monitoring Ops - Builders Program - (Emirati National)",
"Application Support Engineer",
"Associate Accountant - Builders Program - (Emirati National)",
"Associate Data Analyst - Builders Program - (Emirati National)",
"Associate Data Scientist - Builders Program - (Emirati National)",
"Collections Advisor"
]
}

new_function, when a company now has roles in a function it had none in before:

{
"type": "new_function",
"company": "Tamara",
"companyWebsite": "https://tamara.co",
"ats": "greenhouse",
"trackingId": "proof-run",
"observedAt": "2026-09-05T17:06:07.168Z",
"function": "data",
"count": 2,
"titles": [
"Associate Data Analyst - Builders Program - (Emirati National)",
"Associate Data Scientist - Builders Program - (Emirati National)"
]
}

job_added (the full normalized job is attached; job_removed attaches the compact snapshot entry instead):

{
"type": "job_added",
"company": "Ziina",
"companyWebsite": "https://ziina.com",
"ats": "ashby",
"trackingId": "proof-run",
"observedAt": "2026-09-05T17:06:07.168Z",
"job": {
"jobId": "b52d240f-2ccb-4aea-800b-623e9ca8ae09",
"title": "Backend Engineer, Payments Platform",
"url": "https://jobs.ashbyhq.com/ziina/b52d240f-2ccb-4aea-800b-623e9ca8ae09",
"department": "Engineering",
"team": "Payments Platform",
"location": {
"raw": "Dubai",
"city": "Dubai",
"region": null,
"country": "AE",
"remote": "onsite"
},
"function": "engineering",
"seniority": "mid",
"employmentType": "FullTime",
"postedAt": "2026-08-12T05:44:50.125Z",
"firstSeenAt": "2026-09-05T17:06:07.168Z",
"confidence": "high"
}
}

job_updated, with the fields that changed:

{
"type": "job_updated",
"company": "Salla",
"companyWebsite": "https://salla.sa",
"ats": "workable",
"trackingId": "proof-run",
"observedAt": "2026-09-05T17:06:07.168Z",
"job": {
"jobId": "B656D5F0F3",
"title": "Accountant (Shipping or Logistic)",
"url": "https://apply.workable.com/j/B656D5F0F3",
"location": {
"raw": "Makkah, Makkah Province, Saudi Arabia",
"city": "Makkah",
"region": "Makkah Province",
"country": "SA",
"remote": "unknown"
},
"function": "finance",
"seniority": "mid"
},
"before": {
"title": "OLD TITLE Accountant (Shipping or Logistic)",
"location": "Makkah, Makkah Province, Saudi Arabia",
"seniority": "mid",
"function": "finance"
},
"after": {
"title": "Accountant (Shipping or Logistic)",
"location": "Makkah, Makkah Province, Saudi Arabia",
"seniority": "mid",
"function": "finance"
}
}

job_removed:

{
"type": "job_removed",
"company": "Lucidya",
"companyWebsite": "https://lucidya.com",
"ats": "workable",
"trackingId": "proof-run",
"observedAt": "2026-09-05T17:06:07.168Z",
"job": {
"title": "Ghost role",
"location": "Riyadh",
"function": "other",
"seniority": "mid",
"hash": "ffff",
"firstSeenAt": "2026-01-01T00:00:00.000Z",
"jobId": "FAKE-REMOVED-1"
}
}

company_error, never charged, with the previous count so silence is not mistaken for "no jobs":

{
"type": "company_error",
"company": "Broken Co",
"companyWebsite": null,
"ats": "greenhouse",
"trackingId": "proof-run",
"observedAt": "2026-09-05T17:06:07.168Z",
"error": "HTTP 404 for https://boards-api.greenhouse.io/v1/boards/this-board-does-not-exist-123/jobs",
"count": 0,
"previousObservedAt": null
}

The key-value store record SUMMARY lists every company with detected ATS, detection method, job count, request count, duration, fallback flag and error, plus the charged events, so you can see the cost of each run.

How to schedule and get notified

  1. Open the Actor, fill in the change-mode input above with your trackingId, and click Save as task.
  2. On the task, open Schedules and create one (daily at 07:00 is typical).
  3. Open Integrations on the task and add Slack, Email, Webhook, Make or Zapier with the event Run succeeded. Point the integration at the run's dataset; in change mode the dataset contains only the change events, so an empty dataset means "nothing changed".
  4. For your own pipeline, call the API directly:
curl -X POST "https://api.apify.com/v2/acts/<username>~hiring-signals-monitor/run-sync-get-dataset-items?token=<token>" \
-H "Content-Type: application/json" \
-d '{ "companies": ["https://jobs.lever.co/nium"], "trackingId": "demo", "emitChangesOnly": true }'

State lives in the named key-value store hiring-signals-state in your account, one record per trackingId. Delete the record to reset a watch list.

Pricing

Pay-per-event: you pay for signals, not for pages. Detection requests, pagination, retries and failed companies are never charged.

EventPriceWhen
Company checked$0.005Per company whose roles were fetched successfully.
Job record$0.002Snapshot mode: per open role stored.
Change detected$0.01Change mode: per change event stored (company_error is free).

Worked example, 50 companies monitored daily with about 8 changes per day: 50 × $0.005 + 8 × $0.01 = $0.33 per run, about $10 per month. A weekly snapshot of 20 companies with 1,500 open roles costs $3.10 per run. Apify platform usage (compute, a few seconds per run) is billed separately by your plan.

Limitations

  • Change detection is relative to the filtered set: changing filters between runs produces added/removed events.
  • Workday exposes posting dates as "Posted 3 Days Ago"; dates older than 30 days are null.
  • Generic (non-ATS) career pages yield title and URL only, with confidence: "low"; location and posting date depend on JSON-LD being present.
  • Rippling, Jobvite, iCIMS and JazzHR are parsed from HTML and may miss roles rendered only by JavaScript.
  • hiring_surge and new_function are computed per run, so a company that posts 20 roles across four days never triggers a surge with surgeThreshold: 5 per day; lower the threshold or run less often.
  • Salary is reported only when the ATS exposes it (Ashby, Lever, Recruitee, Breezy, JSON-LD).

FAQ

Does it need my ATS credentials? No. Every integration uses the public endpoints the career pages call themselves.

What happens when a career page is down? You get one company_error event with the error, the previous job count and the previous observation time. The snapshot for that company is left untouched, so nothing is reported as removed.

Can a job "move" between ids? Some ATSs re-issue ids. A role with the same title, location, function and seniority under a new id is treated as the same role and keeps its firstSeenAt.

How many requests does a run make? One or two per company for most ATSs (Greenhouse, Ashby, Workable, Recruitee, Breezy, Personio, Teamtailor return everything in one call; Lever, SmartRecruiters and Workday paginate in 100/100/20). Detection from a website costs two to three extra requests the first time.

Can I run it for one company from my own code? Yes, pass a single-element companies array; trackingId can be per customer or per account list.

The Actor reads publicly available job postings through public endpoints, honours robots.txt for the HTML fallback, uses polite concurrency and stores no personal data: recruiter and hiring-manager names, emails and phone numbers are removed even when the ATS returns them. Use the data in line with the ATS and career-site terms and the laws that apply to you.