ATS Hiring Intent - Buying Signals (Greenhouse + 4 ATSes)
Pricing
from $8.00 / 1,000 enriched job fetcheds
ATS Hiring Intent - Buying Signals (Greenhouse + 4 ATSes)
Aggregates jobs across Greenhouse, Lever, Ashby, SmartRecruiters, and Workday with auto ATS detection from a domain. Gemini classifier emits per-company buying signals (expanding-sales, evaluating-CRM, platform-build-out). Monitor mode tracks 100s of companies weekly via KV deltas.
Pricing
from $8.00 / 1,000 enriched job fetcheds
Rating
5.0
(1)
Developer
Omar Eldeeb
Maintained by CommunityActor stats
2
Bookmarked
4
Total users
0
Monthly active users
8 days ago
Last modified
Categories
Security
Limited permissionsShare
What does ATS Hiring Intent do?
ATS Hiring Intent aggregates open job postings across five major applicant tracking systems — Greenhouse, Lever, Ashby, SmartRecruiters, and Workday — into one normalized feed and turns the raw job data into per-company buying signals ready for a sales / RevOps pipeline.
Give it a list of company domains. It auto-detects each company's ATS, pulls every open role, normalizes function and seniority across platforms, and (in signals mode) runs each role through Gemini 3.5 Flash Lite to extract intent tags (expanding-sales, evaluating-CRM, series-B-build, platform-build-out, …) and infer the specific vendors / tech stack the company is buying or building on. One composite CompanySignal per refresh tells your reps which prospects just lit up — Stripe is expanding-sales (8/8 AE roles posted, AI-customer focus, vendor hypothesis: OpenAI, Anthropic, NVIDIA).
Zero anti-bot. Zero proxies needed by default. No login. Every data source is an official public ATS endpoint. Replaces $99-$15K/seat tools (Apollo, ZoomInfo, Bombora, Cognism) on the hiring-signal job-to-be-done.
Why use ATS Hiring Intent?
- B2B sales / SDRs — find every prospect actively hiring for a role that signals they're buying your product. Hiring 5 DevOps + 2 Platform Engineers = evaluating IaC + observability tooling. Hiring a VP Sales + 3 AEs = expanding GTM motion. Apollo only counts headcount; this actor explains what they're buying.
- RevOps & GTM intelligence — score your ICP list weekly, build prospect lists from buying-intent tags, replace your stale $1-5K/mo intent-data line item.
- Recruiters & talent intelligence — track hiring velocity at competitors, see which functions/seniorities they're loading up on.
- VC / corporate development — detect Series A → C ramp signals, exec hires, geographic expansion. Headcount-delta tracking with KV-checkpointed monitoring.
- Founders doing competitive intel — type a competitor's domain, see every open role, every detected vendor, every team they're scaling.
- AI / data teams — a clean 30+ field unified ATS schema across 5 platforms with deterministic taxonomy normalization. Drop straight into a vector store or BI tool.
How to use
- Pick a mode:
discover— given domains, detect which ATS each one uses (cheap onboarding pass).jobs— fetch normalized jobs only, no AI.signals— jobs + Gemini intent tags + per-company composite signal (the value tier).monitor— same assignalsbut only emits deltas vs. the previous run. Best with a daily/weekly schedule.
- Provide either
companyDomains(list of domains — auto-detected) oratsTargets(direct ATS slugs likegreenhouse:stripe,lever:palantir,ashby:linear,smartrecruiters:visa,workday:nvidia.wd5/NVIDIAExternalCareerSite). - Optionally filter by
keywordFilter,functionFilter(engineering / sales / …),seniorityFilter,locationFilter,remoteOnly,postedWithinDays. - Run. Job rows (and discovery rows in
discovermode) land in the run's default dataset. Company signals and change events land in two separate named datasets in your account — see Where each row type lands. - Export as JSON, CSV, or Excel from the Apify Console — or pull via API / webhook.
- Want weekly intent updates? Set mode to
monitorand schedule the actor. Subsequent runs only emit new/changed/closed roles + the latest composite signal.
Free trial: the first 10 billable result events of every run (job rows, company signals, change events, discoveries — whichever come first) are free. The per-run start fee is always charged. Enough to check output shape and signal quality on a small run before committing budget.
Input examples
1. Discover which ATS your top 5 prospects use.
{"mode": "discover","companyDomains": ["stripe.com", "linear.app", "notion.so", "vercel.com", "anthropic.com"]}
2. Pull every open engineering role at 3 companies, no AI.
{"mode": "jobs","companyDomains": ["stripe.com", "anthropic.com", "linear.app"],"functionFilter": ["engineering"],"seniorityFilter": ["senior", "lead", "principal"],"maxJobsPerCompany": 100}
3. Generate weekly buying signals across a 50-company prospect list.
{"mode": "signals","companyDomains": ["stripe.com", "notion.so", "linear.app", "vercel.com", "anthropic.com"],"maxJobsPerCompany": 50,"intentBatchSize": 20,"postedWithinDays": 30}
4. Monitor 200 companies — emit deltas only on the second-and-subsequent run. (Schedule daily.)
{"mode": "monitor","companyDomains": ["acme.com", "..."],"maxJobsPerCompany": 100,"remoteOnly": false}
5. Skip auto-detection — pass direct ATS targets.
{"mode": "signals","atsTargets": ["greenhouse:stripe","lever:palantir","ashby:linear","smartrecruiters:visa","workday:nvidia.wd5/NVIDIAExternalCareerSite"]}
Output
Where each row type lands
Each row has a type field. Rows are split across datasets so each one has a single shape:
Row type | Modes | Dataset | Fetch via API |
|---|---|---|---|
job | jobs, signals, monitor | The run's default dataset | GET https://api.apify.com/v2/actor-runs/<RUN_ID>/dataset/items?clean=true&token=<TOKEN> |
ats_discovery | discover | The run's default dataset | Same as above |
company_signal | signals, monitor (or emitCompanySignals: true) | Named dataset signals-<RUN_SUFFIX> in your account | GET https://api.apify.com/v2/datasets/~signals-<RUN_SUFFIX>/items?clean=true&token=<TOKEN> |
change_event | monitor (or incrementalMode: true) | Named dataset changes-<RUN_SUFFIX> in your account | GET https://api.apify.com/v2/datasets/~changes-<RUN_SUFFIX>/items?clean=true&token=<TOKEN> |
<RUN_SUFFIX> is the last 12 characters of the run ID. Example: run Bef84LkoIt1oR2lnT writes its company signals to the dataset signals-LkoIt1oR2lnT. The leading ~ means "a dataset with this name in the account that owns the token"; <your-username>~signals-<RUN_SUFFIX> works too.
Good to know:
- The run log prints the exact dataset ID and items URL for each named dataset (lines starting with
[output]), and the run's status message names them. - Named datasets are not linked to the run, so they do not appear in the run's Storage tab and are not read by integrations that use "the run's dataset" (Google Sheets, Make, Zapier, the
Outputtab). Open them under Storage → Datasets in the Console, or fetch them via the API above. - Named datasets are not deleted automatically when the run's own storage expires. Delete old
signals-…/changes-…datasets from Storage → Datasets when you no longer need them.
With the JavaScript client:
const run = await client.actor('constructive_calm/ats-hiring-intent-scraper').call(input);const suffix = run.id.slice(-12);const { items: jobs } = await client.dataset(run.defaultDatasetId).listItems();const { items: signals } = await client.dataset(`~signals-${suffix}`).listItems(); // signals / monitor modesconst { items: changes } = await client.dataset(`~changes-${suffix}`).listItems(); // monitor mode
Sample enriched job record:
{"type": "job","sourceId": "d3bc1ced-3ce4-4086-a050-555055dbb1ff","company": "Linear","domain": "linear.app","atsPlatform": "ashby","atsSlug": "Linear","title": "Senior / Staff Fullstack Engineer","function": "engineering","seniority": "senior","department": "Product","team": "Engineering","location": "Europe","isRemote": true,"employmentType": "FullTime","postedDate": "2026-04-21T20:13:45.158+00:00","applyUrl": "https://jobs.ashbyhq.com/linear/d3bc1ced-3ce4-4086-a050-555055dbb1ff/application","intentTags": ["platform-build-out", "going-international"],"vendorSignals": ["TypeScript", "React", "GraphQL", "PostgreSQL", "Redis", "Kubernetes"],"intentSummary": "This role indicates a focus on expanding the core product platform and international growth.","enrichmentLevel": "enriched"}
Sample company_signal record:
{"type": "company_signal","company": "Stripe","atsPlatform": "greenhouse","atsSlug": "stripe","signal": "expanding-sales","confidence": 1.0,"evidence": "8/8 open roles match this signal. Examples: Account Executive AI Sales, Account Executive AI Startups, Account Executive Commercial.","vendorHypothesis": ["Stripe", "OpenAI", "Anthropic", "NVIDIA", "Shopify"],"openRolesCount": 8,"newRolesLast30d": 8,"topFunctions": [{ "function": "sales", "count": 8 }],"secondarySignals": [{ "signal": "series-B-build", "confidence": 0.66, "evidence": "3/8 roles tagged." }]}
Data fields (selected)
| Field | Type | Notes |
|---|---|---|
type | enum | job | company_signal | ats_discovery | change_event |
company | string | Display name pulled from board metadata when available |
atsPlatform | enum | greenhouse | lever | ashby | smartrecruiters | workday |
function | enum | Normalized: engineering, sales, marketing, product, data, design, operations, support, finance, hr, legal, other |
seniority | enum | intern | junior | mid | senior | lead | principal | director | vp | cxo | unknown |
intentTags | array | 22-tag fixed taxonomy — see input schema for the full list |
vendorSignals | array | Specific vendor / tech names mentioned in the JD |
intentSummary | string | One-line plain-English takeaway from Gemini |
salaryMin / salaryMax / salaryCurrency | numbers + ISO code | When the ATS exposes pay bands (Greenhouse + Ashby do) |
applyUrl | string | Direct link to the application page |
signal (on company_signal) | enum | The dominant intent tag, or general-hiring / shrinking / stable |
vendorHypothesis | array | Top 8 vendors aggregated across the company's open roles |
changeType (on change_event) | enum | role-opened | role-closed | role-updated | salary-changed |
How much does it cost?
| Event | Price | Fires when |
|---|---|---|
apify-actor-start | $0.01 per GB of run memory | Once per run. At the default 4 GB memory that is $0.04 per run |
basic-job-fetched | $0.0015 | A normalized job without AI fields is emitted (mode jobs; in signals / monitor only for a job the AI could not classify) |
enriched-job-fetched | $0.008 | A Gemini-enriched job is emitted (modes: signals, monitor) |
company-signal-event | $0.05 | Composite buying signal per company (modes: signals, monitor). Not charged when the AI gave no answer for any of that company's jobs |
change-event | $0.02 | Role opened / closed / updated vs. previous run (modes: monitor, or any mode with incrementalMode). An opened or updated role also emits (and charges) its job row; a closed role emits only the change event |
ats-discovered | $0.005 | Domain → ATS detection record (mode: discover). Domains whose ATS could not be detected (detectionMethod: "failed"), detected boards that could not be loaded, and direct atsTargets echoed back in discover mode are still listed but not charged |
The first 10 billable result events of every run are free (any mix of the events above except apify-actor-start, which is always charged). The run log prints how many events were free and how many were paid.
Typical run costs
| Scenario | Volume | Cost |
|---|---|---|
| Discover ATS for 50 prospects | 50 discoveries | ~$0.24 |
| Pull 50 roles each from 10 companies | 500 basic jobs | ~$0.78 |
| Weekly signals run, 50 companies × 40 roles | 2,000 enriched + 50 signals | ~$18.50 |
| Daily monitor, 200 companies × 30 roles, ~3 deltas/co/day | 600 change events (+ a job row for each opened/updated role) + 200 signals/day | ~$22–27/day |
All figures include the $0.04 start fee at the default 4 GB memory and the 10 free events per run.
Comparison: Apollo Pro $99/seat/mo (no signals), ZoomInfo + Bombora $3-5K/mo, Clay Growth $1.5-2.5K/mo, Cognism + intent ~$2K/mo. The 50-company weekly run above costs ~$80/mo — same job-to-be-done, ~5-25× cheaper than every comparable.
Tips & advanced options
monitormode is built for scheduling. Set up an Apify schedule (daily or weekly), feed in your prospect list, and only pay for deltas + the latest composite signal: an opened or changed role costs its job row + a change event, a closed role costs a change event, and each company's refreshed signal costs onecompany-signal-event. The first run charges full freight (everything is "new"); later runs are deltas-only. When nothing changed, the run's status message says so ("No new or changed jobs since the previous run …").
How monitor mode decides what is a change
- Opened = a posting that was not on the company's board at the last full read and is in scope (passes your filters and fits
maxJobsPerCompany). A role that was already on the board and only now fits your cap or filters (for example because another role closed) is recorded silently, not billed as opened. - Updated = title, location, or salary band changed (plus the posting's update date on Greenhouse, Lever, Ashby and SmartRecruiters). Workday exposes no real update date, so Workday roles are compared on title, location and salary only.
- Closed = a tracked role whose posting is gone from a board that was read to the end this run. Closures are not reported (and tracked roles are kept for the next run) when a board fails to load or is rate-limited, lists 0 postings, is larger than the 1,000-posting read limit, or the run stopped before finishing that company (
maxItems, maximum charge, error). The status message tells you when that happened. A role that is still open but no longer fits your filters or cap is dropped from tracking without a charge. - Changing filters or
maxJobsPerCompanyre-baselines. On the first run after such a change, roles that were already on the board and now fit are recorded silently (no rows, no charges); postings that are genuinely new since the last full read are still reported as opened. Monitors created before October 29, 2026 re-baseline once on their first run on this version (every company, including companies that had no matching roles before). To get the full current list at any time, run the same input insignalsmode. - Do not run two monitor runs on the same companies at the same time. Each run compares against the state the previous run saved; two overlapping runs both see the same changes and both deliver and bill them. The actor detects an overlapping run on the same targets and says so in the log and status message, but it does not block it.
- Progress is saved while rows are delivered (at most every 15 seconds) and when the run is migrated or aborted, so a timeout, abort or migration does not re-bill already delivered roles on the next run. After a hard kill, at most the roles delivered in the last ~15 seconds can be delivered (and billed) once more.
- AI cost control: monitor runs send only new or changed roles to the AI. Unchanged roles reuse the intent tags from earlier runs for the company signal, and unchanged Workday roles skip their description request (a 200-role Workday company drops from ~4 minutes to ~1 minute per monitor run).
- Size limit per monitor: tracking state for one input is stored in a single key-value record (about 350 bytes per tracked role; 200 companies × 30 roles ≈ 2 MB, 200 × 100 ≈ 7–8 MB). Keep one monitor under roughly 20,000 tracked roles and split bigger prospect lists across several monitors. If the state ever cannot be saved, the run says so in its status message (it starts with "ERROR"), because the next run would then deliver and bill the same changes again. On boards too large to read to the end, a tracked role not seen for 5 runs stops being tracked (no charge).
- Workday note — Workday tenants live at
tenant.wd{N}.myworkdayjobs.comwhere N is 1-12. Useworkday:tenant.wdN/SiteSegmentform. Find thewdNfrom any URL on their careers page. Some Workday tenants throttle aggressive scraping — switch toRESIDENTIALproxy if you see 403s. - Auto-detection covers ~25K-30K companies out of the box (all Greenhouse, Lever, Ashby, SmartRecruiters customers + most Workday F500). For non-detectable domains, pass a direct
atsTargetsentry and the actor skips resolution. - Filter aggressively to control budget.
functionFilter,seniorityFilter,locationFilter,remoteOnly,postedWithinDaysall run before Gemini enrichment, so you only pay enrichment cost on the roles that matter. maxJobsPerCompanycounts jobs that pass your filters. WithmaxJobsPerCompany: 5andfunctionFilter: ["engineering"]you get up to 5 engineering roles per company, however many other roles the board lists first. On paginated boards (Workday, SmartRecruiters) the actor reads at most 1,000 postings per company.maxItemscounts job rows and company-signal rows. Change events are not counted: you get one change event per new or changed job row, plus one per closed role.- Workday descriptions. In
signals/monitormode the actor opens each kept Workday role's detail page (one request each, ~0.8 s) so the AI andkeywordFiltersee the full description. A 200-role Workday company takes about 3 minutes. Injobsmode Workday rows carry no description andkeywordFiltermatches Workday titles only. - A run with 0 rows always says why. The status message reports how many jobs were scanned and how many each filter removed (e.g.
0 jobs matched your filters. Scanned 124 open jobs across 2 companies; removed by functionFilter [legal]: 122 …), or that a job board could not be loaded. intentBatchSize= 20 is the cost-vs-latency sweet spot. Larger batches cost less per job but increase end-to-end latency.- Vendor signals are tools/tech mentioned in JDs, not necessarily companies the prospect is buying. A role mentioning "Salesforce" usually means they use Salesforce — sometimes it means they sell to Salesforce customers. Cross-check with
intentSummaryfor context. - Cross-ATS dedup — if the same company is somehow listed twice (rare), dedup by
(atsPlatform, atsSlug)downstream.
Legal & ToS
- Every ATS endpoint used by this actor is publicly published and intended for third-party career-site / aggregator use. No login or API key required.
- Greenhouse Job Board API, Lever Postings API, Ashby Public Job Posting API, and SmartRecruiters Posting API are all explicitly documented as public — see developers.greenhouse.io/job-board, github.com/lever/postings-api, developers.ashbyhq.com/docs/public-job-posting-api, developers.smartrecruiters.com/docs/posting-api.
- Workday CXS endpoints serve the same data as the public-facing careers page; we paginate at default 20-per-page with gentle 150ms pacing.
- The actor is intended for sales intelligence, recruiting research, and market analysis. Do not use the data to discriminate, harass, or auto-apply at scale.
- Job descriptions are © the posting employer. We store and re-emit them verbatim for analysis. Do not republish full JDs as your own content.
FAQ & support
Q: Which ATSes are supported in v1? A: Greenhouse, Lever, Ashby, SmartRecruiters, Workday. BambooHR, Recruitee, Teamtailor, Personio in v1.1.
Q: What's the difference between signals and monitor?
A: signals runs the full pipeline every time. monitor does the same but auto-enables incremental mode — on subsequent runs you only see new / changed / closed roles, plus the refreshed company-signal record. Use monitor for scheduled subscriptions.
Q: Why didn't my domain auto-detect?
A: ~10-15% of companies use custom careers pages that don't link to a known ATS host. Pass an atsTargets entry directly. Also try the discover mode in isolation — it tells you what was found and what failed.
Q: Can I get LinkedIn / Indeed jobs? A: No. This actor only scrapes companies' own ATS career pages. LinkedIn aggregations are heavily anti-botted and not worth the proxy cost. The data here is fresher anyway — it's the source ATS, not a 2nd-hand index.
Q: Will the intent classifier hallucinate tags?
A: The classifier is constrained to a fixed 22-tag taxonomy and instructed to return ["unknown"] if uncertain. We've validated against ~100 real postings; false-positive rate is < 5%. If you see noise, lower the intentBatchSize to 5-8 (smaller context = sharper inference).
Q: How do I report bugs?
A: Open an issue on the actor's Apify page. Include the input JSON and the offending record's sourceId + applyUrl.
Built with TypeScript on the Apify SDK. Zero proxies, zero anti-bot, native fetch. No headless browser, no CAPTCHA.
Changelog
- October 29, 2026:
maxJobsPerCompanynow caps the jobs that pass your filters, not the first N postings on the board. Before, a strict filter plus a small cap could return 0 rows even when matching jobs existed. Runs that end with 0 rows now say why in the status message, and a board that is missing or down is reported as such instead of as empty. Workday: up to 1,000 postings per company are now read (previously only the first 40), and job descriptions are fetched again (a URL error had left them empty), so Workday runs return more rows and richer AI tags. Monitor mode billing fixes: Workday roles are no longer reported as "updated" on every run; a role is reported closed only when its board was read completely (no more closures from a failed or rate-limited board, amaxItemscut, or a lowered cap); changing filters or the cap re-baselines instead of billing old roles as opened, and existing monitors re-baseline once on their first run on this version. Indiscovermode, undetectable domains, detected boards that fail to load, and echoedatsTargetsare no longer charged.role-closedrows now carry the real job title. README corrections: company signals and change events are written to named datasets (signals-…/changes-…), not the default dataset; the start fee is $0.01 per GB of memory ($0.04 per run at the default 4 GB), not $0.0001. No output field was added, removed or renamed. - October 2026: The intent classifier now uses Google Gemini 3.5 Flash Lite, because the provider retires Gemini 2.5 Flash on 2026-10-20. AI calls are retried on temporary errors and on incomplete answers. Billing correction: a job is charged as
enriched-job-fetchedonly when the AI classified it (as before), and acompany-signal-eventis no longer charged when the AI gave no answer for any of that company's jobs; the signal row is still delivered. If the AI provider rejects the key or runs out of credit, the run stops using AI, says so in its status message, and still delivers every job as a basic row. No output field was added, removed or renamed.