Hiring Signal Check & ATS Detector — Workday, Greenhouse, Lever avatar

Hiring Signal Check & ATS Detector — Workday, Greenhouse, Lever

Pricing

from $5.60 / 1,000 hiring check (board found and corroborated)s

Go to Apify Store
Hiring Signal Check & ATS Detector — Workday, Greenhouse, Lever

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

Brenton Keller

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

16 days ago

Last modified

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_confidenceWhat it means
verified_urlThe 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_nameThe board publishes a company name that matches the domain (GitLab / gitlab.com). Used when job links point at the ATS vendor instead.
verified_contentThe 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_onlyThe 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 extraSlugs and it is tried first.

Output

FieldExample
is_hiringtrue
ats_platform / ats_sluggreenhouse / stripe
match_confidenceverified_url
open_roles_count721
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_urlthe board's own page (https://jobs.lever.co/spotify), not one posting
remote_split / worker_types / time_typesWorkday only, whole-board counts: {"Onsite Job": 592, "Hybrid Job": 59} / {"Regular": 694, "Intern - Paid (Seasonal)": 21} / {"Full time": 749, "Part time": 1}
latest_role_posted2026-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_boardwhat the board says about whose it is (Linear / https://linear.app/)
query / query_domainthe string you submitted / the domain it resolved to
hintset whenever an answer is provisional — read it
checked_at2026-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:

  1. A board URL you passed in extraSlugs, read directly.
  2. 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.
  3. 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 is CareerDepot), and a board linked from the company's own site is verified_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:

ResultCompanies
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

EventPriceWhen
hiring-check$0.008a board was found and corroborated
hiring-unverified$0.004a board matched on the slug alone, nothing corroborating
hiring-no-match$0.003no board on any selected platform
job-listing$0.001one 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):

  1. In your table, Add enrichment, search "Apify" and choose Run Apify Actor.
  2. Connect your Apify account (Clay runs the actor on your Apify account).
  3. Choose the actor brenton8907/hiring-signal-check.
  4. 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.
  5. 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:

fieldvalue
MethodPOST
URLhttps://api.apify.com/v2/acts/brenton8907~hiring-signal-check/run-sync-get-dataset-items
Query paramstoken = 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
HeaderContent-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 to kernel on Ashby, which belongs to an unrelated London startup at kernel.ai.
  • atlas.co (an AI GIS company) resolves to atlas on Ashby, which belongs to a credit card company at atlascard.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 from kernel.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.org and atlas.co now come back this way.
  • Website differs and the domain cannot be fetched to check (a timeout, for example) → slug_only at 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_content is 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:

Platformverified_urlverified_nameverified_content
Greenhousereachable (job links are often on the company's own domain)reachable (publishes company_name)reachable
Levernever — all job links are on jobs.lever.conever — publishes no company nameonly option
Ashbyreachable via the published websitenot used — the website is strongeronly if no website is published
Workdaywhen the board is linked from the company's own sitereachable (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.