Hiring Signal Check & ATS Detector — Workday, Greenhouse, Lever
Pricing
from $5.60 / 1,000 hiring check (board found and corroborated)s
Hiring Signal Check & ATS Detector — Workday, Greenhouse, Lever
Hiring signals and ATS detection from company domains: is the company hiring, on Greenhouse, Lever, Ashby or Workday, and for what. Open role count, departments, seniority split and the technologies its job ads name. Each match is checked against the board's own links, website or hiring entity.
Pricing
from $5.60 / 1,000 hiring check (board found and corroborated)s
Rating
0.0
(0)
Developer
Brenton Keller
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
16 days ago
Last modified
Categories
Share
Hiring Signal Check
Is this company hiring, on which applicant tracking system, and for what?
Input company domains. Output one row each: whether they have a live job board on Greenhouse, Lever, Ashby or Workday, how many roles are open, which departments, the seniority split, and the technologies their job ads name. Workday is where most large employers post (Walmart, Target, NVIDIA, Salesforce, Boeing, Accenture), so enterprise lists are covered, not just startups.
Why the match is trustworthy
Nothing in stripe.com tells you the company's board is called stripe, so resolving a
domain to a board is a guess. This actor makes the guess and then tries to corroborate
it, and every row says which evidence it had:
match_confidence | What it means |
|---|---|
verified_url | The board points at the company's own domain: a job link on it (stripe.com, careers.airbnb.com), or, on Ashby, the company website the board publishes (linear → linear.app). Strongest. |
verified_name | The board publishes a company name that matches the domain (GitLab / gitlab.com). Used when job links point at the ATS vendor instead. |
verified_content | The board's own postings name the company. The best Lever can reach, and Ashby's fallback when a board publishes no website — but it cannot tell a same-named different company apart, so it is the weakest of the three. |
slug_only | The slug exists but nothing tied it to the domain. Treat with suspicion — it may be a different company with a similar name. |
Measured on the 12-company set: 3 verified_url, 4 verified_name, 3 verified_content,
1 slug_only, 1 correct no-match. A slug_only row is returned with the caveat stated and
billed at half price, never silently upgraded or quietly dropped.
On the live 30-company sweep: 3 verified_url on Greenhouse plus 4 on Ashby, 6
verified_name, 1 verified_content, 0 wrong companies.
"No board" and "empty board" are different facts
A board that exists with zero open roles answers differently from a domain with no board at all, and the actor keeps them apart:
ats_platform: "lever",is_hiring: false,open_roles_count: 0— they use Lever and are not hiring right now. For a prospect list, knowing their ATS is still worth having.ats_platform: null,is_hiring: false— nothing found on these four platforms.
A null platform does not mean "not hiring". Plenty of large employers use Oracle (JPMorgan, Dell), SuccessFactors (McDonald's), Eightfold (Starbucks), iCIMS, Taleo, SmartRecruiters or a hand-built careers site (Amazon, Google, Apple, Microsoft), none of which this actor reads. It means "no board found on Greenhouse, Lever, Ashby or Workday". If you know a company's board, pass its name (or a full Workday board URL) in
extraSlugsand it is tried first.
Output
| Field | Example |
|---|---|
is_hiring | true |
ats_platform / ats_slug | greenhouse / stripe |
match_confidence | verified_url |
open_roles_count | 721 |
departments | ["Global Operations", "Engineering", …], busiest first |
department_counts | {"Sales": 35, "Engineering": 32, "Marketing": 9} — open roles per department |
tech_stack_mentioned | ["Python", "Spark", "Databricks", "LLM"] |
seniority_distribution | {"exec": 1, "vp": 14, "director": 1, "manager": 227, "senior_ic": 63, "junior_ic": 50, "ic": 364} |
locations | ["Dublin", "New York", …]: the 25 busiest, by open roles (on Workday, countries) |
board_url | the board's own page (https://jobs.lever.co/spotify), not one posting |
remote_split / worker_types / time_types | Workday only, whole-board counts: {"Onsite Job": 592, "Hybrid Job": 59} / {"Regular": 694, "Intern - Paid (Seasonal)": 21} / {"Full time": 749, "Part time": 1} |
latest_role_posted | 2026-10-06T17:01:49-04:00: when the newest open role was first published, ISO 8601 on every platform |
company_name_on_board / company_website_on_board | what the board says about whose it is (Linear / https://linear.app/) |
query / query_domain | the string you submitted / the domain it resolved to |
hint | set whenever an answer is provisional — read it |
checked_at | 2026-10-12T19:30:05Z: when this row was checked (UTC), on every row including errors |
job-dump mode returns one row per open role instead: title, department, location,
seniority, apply URL, posted_at (when the role was first published) and the board's
match_confidence. Every role is a charge, so a board tied to the domain by nothing but its
slug is not listed: you get one free row naming the board and why it was held back
(google.com finds Google Operations Center's Workday board, whose postings are hired by
GOC Services India). Set includeUnverifiedBoards to list such a board anyway, or name it
in extraSlugs; its rows then say slug_only and carry the caveat in hint.
A Workday row, from a live run (trimmed):
{"query": "boeing.com", "is_hiring": true, "ats_platform": "workday","ats_slug": "boeing/EXTERNAL_CAREERS", "match_confidence": "verified_name","company_name_on_board": "The Boeing Company", "open_roles_count": 750,"departments": ["Electronic & Electrical Engineering (6B)", "Software Engineering (6J)", "…"],"remote_split": {"Onsite Job": 592, "Hybrid Job": 59},"worker_types": {"Regular": 694, "Intern - Paid (Seasonal)": 21, "…": 0},"time_types": {"Full time": 749, "Part time": 1},"locations": ["United States of America", "United Kingdom", "Germany", "…"],"tech_stack_mentioned": ["C++", "Azure", "C#", "Python", "AWS", "…"],"board_url": "https://boeing.wd1.myworkdayjobs.com/EXTERNAL_CAREERS"}
Workday
Workday boards are not addressed by a slug. Each one is a tenant, a data-centre shard and
a site (target.wd5.myworkdayjobs.com/targetcareers), and nothing in target.com says
which shard. The actor finds them in this order, after Greenhouse, Lever and Ashby miss:
- A board URL you passed in
extraSlugs, read directly. - The tenant's own robots.txt. On the right tenant and shard it answers 200 and lists
every career site; anywhere else it answers 422. The brand word is tried on 16 shards,
the five common ones first. This is how Salesforce (
wd12) and Walmart (wd504) are found: their careers pages build their links in JavaScript. - Workday links on the company's own homepage and careers pages. This catches tenants
that are not the brand word (Nike is
nke; Home Depot's site isCareerDepot), and a board linked from the company's own site isverified_url.
A board found by tenant name is corroborated by its hiring entities: each posting names
the legal entity that hires ("1001 Target Corporation", "Canada: Mastercard Canada ULC").
If any sampled entity names the company, the row is verified_name. If every one is some
other entity, the row is slug_only at half price and the hint names it. That is what
keeps google.com from being sold Google Operations Center's 95 roles (hired by "GOC
Services India Private Limited") as Google's hiring.
What is whole-board and what is sampled. Workday serves 20 postings per request and stops listing at 2,000. The role count, departments, countries and the remote / worker / time splits come from the board's own facet totals in the first request, so they cover every posting: Accenture's 42,187 and Walmart's 20,407 roles are real counts, not the 2,000 cap. The seniority split comes from the first 100 titles, and technologies from 15 posting descriptions (one request each). The hint states both samples.
Measured through a datacenter IP on 2026-10-11, 14 large employers expected to be on Workday:
| Result | Companies |
|---|---|
| Found and corroborated (13) | NVIDIA 2,671 roles · Salesforce 1,533 · Boeing 750 · Target 14,085 · Nike 766 · Accenture 42,187 · Home Depot 937 · Walmart 20,407 · Adobe 351 · Intel 666 · Workday 374 · Mastercard 1,097 · Capital One 1,957 |
| Correct no-match (1) | Dell — its careers site redirects to Oracle |
| Correct no-match, other enterprises (6) | Amazon, Apple, Microsoft (own sites) · JPMorgan (Oracle) · McDonald's (SuccessFactors) · Starbucks (Eightfold) |
Downgraded to slug_only (1) | Google: the google tenant is Google Operations Center's |
No regression on the 9 startup and open-source domains in the same suite (Stripe, GitLab, Notion, Palantir, Scale, Linear, kernel.org, sqlite.org, craigslist.org).
Cost and speed
These are public, unauthenticated JSON APIs with no bot protection, so this actor is dramatically cheaper and faster than a scraper: 4.4 requests and 1.0 s per company across a 12-company test set, no proxy required.
A match usually costs 2 requests, because the first slug guess is normally right. A
no-match is the expensive case: it has to rule out every slug candidate on every
platform before it can answer, so at the default 6 candidates across Greenhouse, Lever and Ashby it can
reach 18. That is a deliberate trade — a short domain often has a longer board name
(scale.com is scaleai, with 188 open roles; box.com is boxinc, with 129), and
answering "no board" for those is far worse than a slow negative. Narrow platforms or
lower maxSlugCandidates if you would rather have cheap negatives than complete ones.
maxRequestsPerDomain (64 by default) is a hard ceiling, and a row stopped by it says so
in hint rather than reporting a clean "no board".
Workday is the expensive platform, so it goes last. Ruling it out takes up to 16
robots.txt probes and 4 page fetches on top of the 18 above; a Workday board then costs
about 20 more requests (4 title pages and 15 posting details). Measured on the 30-domain
suite through a datacenter IP: 35 requests and 7.3 s per domain overall, against 15 and
2.3 s with Workday off. On Apify the measured pace was slower, 11.6 s per domain, so plan
on about 300 domains per hour-long run and split longer lists, or raise the run
timeout. Set platforms to ["greenhouse", "lever", "ashby"] for a startup list where
Workday is unlikely.
A timeout never costs you the rows already checked. Each domain's row is saved as soon
as it is checked. If the list is longer than the run timeout allows, the run says so in its
status message when it starts, stops starting new domains shortly before the timeout, and
gives every domain it did not reach a free row saying not checked, so a 1:1 join still
gets a row per input and you can see which to resubmit. Reaching your maximum charge per
run stops it the same way.
Pricing
| Event | Price | When |
|---|---|---|
hiring-check | $0.008 | a board was found and corroborated |
hiring-unverified | $0.004 | a board matched on the slug alone, nothing corroborating |
hiring-no-match | $0.003 | no board on any selected platform |
job-listing | $0.001 | one open role, in job-dump mode |
Prices follow how much the answer is worth, not how much it cost to produce. An uncorroborated match carries the same hiring data as a verified one but might be a different company, so it is half price rather than sharing an event with an answer we can stand behind. A no-match costs less to buy and more to produce, deliberately: it is the weaker answer, so charging match price for it would be selling less for the same money. Rows that failed, were rate limited, or could not be parsed are written to the dataset so the failure is auditable and carry no charge; so do public-suffix and non-domain inputs, and domains a run did not reach. A no-match is only billed when every selected platform actually answered: if one could not be read (a Workday search that timed out, say), nothing there was ruled out, so the row is a free error saying which platform to retry, not a "no".
Call it from code, Clay, or a weekly schedule
From code
The synchronous endpoint runs the actor and returns the rows in the HTTP response, so there is nothing to poll:
curl -X POST "https://api.apify.com/v2/acts/brenton8907~hiring-signal-check/run-sync-get-dataset-items?token=$APIFY_TOKEN&maxTotalChargeUsd=0.10" \-H 'Content-Type: application/json' \-d '{"domains": ["stripe.com", "nvidia.com", "sqlite.org"]}'
Measured for one domain: 3.5 s for a corroborated match (stripe.com), 25 s for a
no-match that has to rule out all four platforms (sqlite.org). That endpoint gives up at
300 s, so send at most ~20 domains per synchronous call, and start a normal run
(POST /v2/acts/brenton8907~hiring-signal-check/runs) for anything longer.
From Clay
Clay's built-in Apify integration (included in all Clay plans):
- In your table, Add enrichment, search "Apify" and choose Run Apify Actor.
- Connect your Apify account (Clay runs the actor on your Apify account).
- Choose the actor
brenton8907/hiring-signal-check. - In Input Data, enter
{"domains": [<Domain column>]}: insert your domain column as a token, with the key"domains"in quotes and no quotes around the token. - Run it on one row first, then map the fields you need into columns:
is_hiring,ats_platform,open_roles_count,match_confidence,departments,tech_stack_mentioned.
Each row is one actor run for one domain, and the dataset holds one row per domain, so the enrichment stays 1:1 with your table.
Alternative: HTTP API enrichment (Clay's Growth plan or higher), if you'd rather call the synchronous endpoint directly:
| field | value |
|---|---|
| Method | POST |
| URL | https://api.apify.com/v2/acts/brenton8907~hiring-signal-check/run-sync-get-dataset-items |
| Query params | token = your Apify API token (a Clay secret) · maxTotalChargeUsd = 0.01 · fields = query,is_hiring,ats_platform,match_confidence,open_roles_count,departments,department_counts,tech_stack_mentioned,seniority_distribution,locations,latest_role_posted,board_url,hint,checked_at |
| Header | Content-Type: application/json |
| Body | {"domains": ["{{domain}}"]}, with {{domain}} replaced by your domain column |
The response is a one-element array: map 0.is_hiring, 0.open_roles_count,
0.ats_platform and the rest from item 0. One row per domain, so the column stays 1:1 with
your table, and maxTotalChargeUsd = 0.01 caps a row at the $0.008 check price. Raise
Clay's HTTP timeout to 60 s, because a no-match takes ~25 s. Filter on match_confidence
(verified_url / verified_name are the safest) before using open_roles_count as a
trigger. For a list of hundreds, one run with every domain in domains is faster than a
call per row: export the dataset as CSV, join it back on query (your input, echoed
verbatim), and resubmit any row whose error starts with not checked:.
Weekly schedule: hiring changes as a trigger
"Went from 4 open roles to 40" or "started hiring engineers" is a stronger outbound trigger
than "is hiring". Save your input as an Apify Task, then add a Schedule in Console
(Schedules → Create, cron 0 7 * * 1 for Mondays at 07:00 UTC), or create one by API (schedules take the actor ID, not its name):
curl -X POST "https://api.apify.com/v2/schedules?token=$APIFY_TOKEN" -H 'Content-Type: application/json' -d '{"name": "hiring-weekly", "cronExpression": "0 7 * * 1", "timezone": "UTC", "isEnabled": true,"actions": [{"type": "RUN_ACTOR", "actorId": "vzv9UvSEe2Ndnt2S4","runInput": {"body": "{\"domains\": [\"stripe.com\", \"nvidia.com\"]}","contentType": "application/json; charset=utf-8"}}]}'
Each week, fetch the newest successful run's rows and compare them with last week's on
query. checked_at tells the two snapshots apart:
$curl "https://api.apify.com/v2/acts/brenton8907~hiring-signal-check/runs/last/dataset/items?status=SUCCEEDED&token=$APIFY_TOKEN&fields=query,is_hiring,open_roles_count,department_counts,checked_at"
Compare only rows without an error. An error or not checked row means "unknown this
week", not "stopped hiring". n8n, Make and Zapier all have first-party Apify integrations:
trigger on the scheduled run finishing and read its dataset.
Known limitations, measured
Four platforms, not all of them. Greenhouse, Lever, Ashby and Workday. Oracle, SuccessFactors, iCIMS, Taleo, Eightfold and hand-built careers sites are not read, and they are the main source of "no board found" on enterprise lists.
With a proxy configured, a dead lookalike domain is half price rather than rejected.
The Ashby website check proves notion.org is not Notion because notion.org does not
resolve. Through a SOCKS proxy that DNS failure surfaces as a proxy error, which cannot be
told apart from a proxy fault, so the row falls back to slug_only with the conflict in
the hint. Without a proxy (the default; these APIs need none) it is rejected outright.
Seniority comes from job titles, so sales titles skew it. "Account Manager" and
"Engagement Manager" are individual-contributor roles that land in the manager bucket,
which is why a sales-heavy board like Stripe's shows 227 managers in 720 roles. Read the
split as a shape, not a headcount. A missing title is reported as unknown, never guessed
as mid-level.
exec means genuine executives. A real partner title (Partner, Managing Partner)
counts as exec; a compound one does not, so "HR Business Partner" and "Administrative
Business Partner" are not executives.
tech_stack_mentioned is what the ads say, not what the company runs. It is a
keyword scan over a sample of postings. A company can run Postgres and never name it, and
an agency-written listing can name Kubernetes for a company that has none. The row reports
descriptions_sampled so you can see how much it read; raise minTechMentions to drop
technologies named only once.
Technologies come from a sample. Scanning all 721 of Stripe's postings to discover
"Python" is waste, so techSampleSize (40 by default) bounds it. The row says how many
postings were read, and the hint says when the sample was smaller than the board.
Department names are reported verbatim and are often messy. Stripe's Greenhouse board
has 203 departments with names like 1185 Account Executives (EMEA) and
5112 General University. Normalising those into a tidy ["Engineering", "Sales"]
taxonomy would mean inventing a mapping and presenting a guess as a fact, so the raw names
are returned, busiest first.
Only Greenhouse exposes departments directly. For Lever and Ashby the names come from
each posting's own department field, which is usually tidier but can be team-level
(Delta, Echo) rather than functional.
Slug resolution is a guess, with limits. The actor tries the brand, then common
qualifiers both stripped (ashbyhq.com → ashby) and added (scale.com → scaleai,
box.com → boxinc). A board named nothing like its domain is still missed —
extraSlugs is the reliable fix, and it is tried before every guess.
A different company with the same name is the main false-positive risk. Two measured cases, both on generic brand words, both Ashby boards:
kernel.org(the Linux kernel) resolves tokernelon Ashby, which belongs to an unrelated London startup atkernel.ai.atlas.co(an AI GIS company) resolves toatlason Ashby, which belongs to a credit card company atatlascard.com.
Their postings name themselves, so content corroboration confirmed the wrong answer and
both rows used to read verified_content, billed at the full rate.
Ashby boards are now checked against the website they publish. Ashby's posting API names no company and hosts every job link itself, but its hosted board page publishes the company's website. On an Ashby hit the actor reads it:
- Website is this domain →
verified_url. 21 of 22 Ashby customers tested. - Website differs, but one redirects to the other →
verified_url, and the hint says so.notion.so→notion.com,temporal.com→temporal.io,zapier.org→zapier.com. A brand comparison cannot separate these fromkernel.org/kernel.ai; the redirect can. - Website is another company's → the board is rejected and the search continues
with the remaining slugs and platforms. If nothing else turns up the row is a no-match
whose hint names the rejected board and its website.
kernel.organdatlas.conow come back this way. - Website differs and the domain cannot be fetched to check (a timeout, for example) →
slug_onlyat half price, with the conflicting website in the hint. - No website published (the company turned the hosted page off, as PostHog has) → the
old ladder applies, so
verified_contentis still possible there.
Measured on 20 adversarial domains whose brand word is a real Ashby customer's slug
(linear.com, loom.io, vanta.io, plaid.org…): before, 18 were sold as corroborated
matches and 12 of those were the wrong company. Now 13 are rejected, 6 genuine aliases
verify, 1 is slug_only, and none is a wrong company at full rate. No legitimate board
was rejected across the 62 other live domains tested.
Where each tier is reachable:
| Platform | verified_url | verified_name | verified_content |
|---|---|---|---|
| Greenhouse | reachable (job links are often on the company's own domain) | reachable (publishes company_name) | reachable |
| Lever | never — all job links are on jobs.lever.co | never — publishes no company name | only option |
| Ashby | reachable via the published website | not used — the website is stronger | only if no website is published |
| Workday | when the board is linked from the company's own site | reachable (each posting's hiring entity) | reachable; capped at slug_only when every hiring entity is another company's |
Lever is now the weak spot. Its hosted page links a logo to whatever the company chose
(Spotify's goes to spotifyjobs.com, Palantir's back to its own board), so it cannot
reject a board the way Ashby's website can.
How to protect yourself: filter on match_confidence == "verified_url" when you need
certainty; treat verified_content on a short or generic brand word as a lead rather than
a fact; and pass a known board name in extraSlugs to bypass guessing entirely.
Very large runs are not yet measured. No throttling has been seen on any of the four
platforms, including 60 concurrent requests to one Workday board, but a run of thousands of
domains has not been tested. If you see rate-limit errors, raise requestDelaySeconds.