Company Hiring Signals - Greenhouse, Lever, Ashby
Pricing
$10.00 / 1,000 company hiring snapshots
Company Hiring Signals - Greenhouse, Lever, Ashby
Which of your target accounts are hiring? One row per company from public Greenhouse, Lever and Ashby boards: open roles, departments, locations and new roles since last run, for $0.01/company.
Pricing
$10.00 / 1,000 company hiring snapshots
Rating
0.0
(0)
Developer
Luke Hunter
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
3 days ago
Last modified
Categories
Share
For sales teams, recruiters selling to employers, RevOps, investors and market researchers who want to know which target accounts are hiring. Paste company domains and get one row per company: how many roles are open, in which departments, in which locations, how many are remote, and what is new or removed since your last run. Built on the public job-board APIs of Greenhouse, Lever and Ashby.
$0.01 per company whose board was found and read. 100 companies = $1. Companies with no board, invalid input and failed requests are free.
Most hiring scrapers return one row per job, so you have to aggregate thousands of rows yourself to learn that "Acme opened six engineering roles this week". This Actor does that aggregation and the week-over-week comparison for you. A company that suddenly opens a run of sales or engineering roles is a buying signal for recruiting agencies, dev tools, HR software, security, relocation and payroll vendors.
No login, no proxy, no API key, no scraping of rendered pages: only the documented public JSON endpoints that companies' own career pages use. It never outputs recruiter names, emails or any other personal data: titles, departments, locations and counts only.
Quick start
{"companies": ["stripe.com", "ashby:openai", "https://jobs.lever.co/palantir"],"maxCompanies": 3}
What goes in companies
| You give | Example | What happens |
|---|---|---|
| A company domain | stripe.com, https://www.stripe.com/jobs | The Actor guesses the board slug from the domain (stripe, and the name without hyphens, e.g. my-co then myco) and tries Greenhouse, then Lever, then Ashby for each guess. The first board that exists is used and reported in ats and boardUrl. If none exists the row is not_found (free). |
| A board URL | https://boards.greenhouse.io/stripe, https://jobs.lever.co/palantir, https://jobs.ashbyhq.com/openai | Used directly, no guessing. |
ats:slug | greenhouse:stripe, lever:palantir, ashby:openai | Used directly, no guessing. |
Boards on the EU hosts (boards.eu.greenhouse.io, jobs.eu.lever.co) are read from the matching EU API when you pass their URL. Domain discovery is a heuristic: a company whose board slug is not derived from its domain (or that uses another ATS such as Workday) comes back as not_found. Pass the board URL or ats:slug for those. Check boardUrl on a discovered row if the slug could belong to a different company. Duplicates, and different inputs that resolve to the same board, are reported once per run.
Recipes
Which of my target accounts are hiring engineers?
{"companies": ["stripe.com", "airbnb.com", "databricks.com"],"departmentsFilter": ["engineering"],"trackChanges": false}
openRoles, departments, locations and remoteRoles then describe engineering roles only.
Weekly "what's new" for an account list
{"companies": ["stripe.com", "airbnb.com", "databricks.com", "ashby:openai"],"trackChanges": true,"stateStoreName": "my-target-accounts"}
Save it as a Task and add a weekly Schedule. The first run is the baseline: baseline: true and newRolesSinceLastRun: 0 for every company. Later runs report newRolesSinceLastRun, removedRolesSinceLastRun and up to five sampleNewTitles. Sort by newRolesSinceLastRun to see who is ramping up. Each company (and each filter set) keeps its own history in the named key-value store, so different account lists can use different stateStoreName values.
Only roles in a region
{"companies": ["greenhouse:stripe"],"locationsFilter": ["london", "dublin", "remote"]}
Input
| Field | Default | What it does |
|---|---|---|
companies | required | Domains, board URLs or ats:slug values. |
departmentsFilter | [] | Count only roles whose department contains one of these words (case-insensitive). |
locationsFilter | [] | Count only roles whose location contains one of these words (case-insensitive). |
trackChanges | true | Remember role IDs between runs and report new and removed roles. |
stateStoreName | company-hiring-signals-state | Named store holding that memory. Use a different name per account list. |
maxCompanies | 100 | Hard cap on companies checked and charged, 1 to 5000. |
Example output
One row per company:
{"company": "acmeexample.com","inputValue": "acmeexample.com","ats": "greenhouse","boardSlug": "acmeexample","boardUrl": "https://boards.greenhouse.io/acmeexample","boardCompanyName": "Acme Example, Inc.","matchConfidence": "high","openRoles": 42,"departments": { "Engineering": 18, "Sales": 9, "Marketing": 5, "Customer Success": 4, "Finance": 2 },"locations": { "San Francisco, CA": 15, "Remote - US": 11, "New York, NY": 8, "London, UK": 6 },"remoteRoles": 12,"newRolesSinceLastRun": 6,"removedRolesSinceLastRun": 2,"baseline": false,"newestPostedAt": "2026-10-12T14:03:00.000Z","oldestPostedAt": "2026-06-02T09:10:00.000Z","sampleNewTitles": ["Senior Backend Engineer", "Account Executive, Enterprise", "Data Scientist"],"warnings": [],"status": "ok","error": null,"checkedAt": "2026-10-13T08:00:00.000Z"}
A company with no board:
{"company": "no-board-example.com","inputValue": "no-board-example.com","ats": null,"openRoles": null,"status": "not_found","error": "No Greenhouse, Lever or Ashby board found for slug guesses: greenhouse:no-board-example, lever:no-board-example, ashby:no-board-example, greenhouse:noboardexample, lever:noboardexample, ashby:noboardexample. Pass the board URL or ats:slug instead.","checkedAt": "2026-10-13T08:00:00.000Z"}
Fields
| Field | Meaning |
|---|---|
company, inputValue | The domain (or board slug) and the entry you gave. |
ats, boardSlug, boardUrl | Which platform matched, its slug and the public board URL so you can verify it. |
boardCompanyName, matchConfidence | Greenhouse boards show their company name. matchConfidence is high when your domain's name (or the slug) appears in it and low when it does not, which is your cue to open boardUrl and check that the guessed slug is the right company. Both are null for Lever and Ashby, which do not expose a name. |
openRoles | Open roles after your filters. |
departments, locations | Top 10 name to count, biggest first. Locations are the primary location as the board lists it. |
remoteRoles | Roles the ATS marks remote, or whose location says "remote". Titles are not used, so "Remote Sensing Engineer" is not counted. Hybrid is not counted. |
newRolesSinceLastRun, removedRolesSinceLastRun, baseline, sampleNewTitles | Change tracking. null when trackChanges is off. |
newestPostedAt, oldestPostedAt | Range of posting dates when the board exposes them. Long-open roles can mean hard-to-fill positions. |
warnings | Notes on an ok row, for example that the Greenhouse departments list was unavailable, or that change-tracking state could not be saved. Empty when clean. |
status, error | ok, not_found, invalid_input, fetch_failed, parse_error, duplicate, budget_reached or time_limit. Only ok is charged. |
checkedAt | When the board was read. |
Pricing
$0.01 per company (company event), charged only when a board was found and read. not_found, invalid_input, fetch_failed, parse_error, duplicate, budget_reached and time_limit rows are free. A board that was found but has zero matching open roles (or none after your filters) is still a charged ok row with openRoles: 0: a company that is not hiring is a signal too. A company is delivered and charged at most once per run. The Actor honours your run's maximum cost: when the limit is reached it stops, marks the unchecked companies budget_reached, and never bills past it.
Limitations
- Greenhouse, Lever and Ashby only. Workday, SmartRecruiters, Workable, BambooHR and others are not covered here; Greenhouse Jobs Scraper & Job Alerts covers more platforms, with one row per job.
- Domain discovery guesses board slugs; it cannot find a board whose slug differs from the domain name.
- Greenhouse department names come from the board's public departments list. If that call fails after retries, the row is a free
fetch_failedwhenever you usedepartmentsFilteror change tracking (so you are never billed for incomplete data); otherwise it is delivered with emptydepartmentsand awarningsnote. - Domain discovery never skips over a guess it could not check: if an earlier candidate (say Lever) failed with a server error while a later one (Ashby) exists, the row is a free
fetch_failedrather than the lower-priority board. - Counts are what the public board shows right now. Unlisted or internal roles are not visible and are not counted.
- At most 5,000 companies per run, 5 companies in parallel, 15 s per request, 3 attempts on 429 and 5xx errors with backoff, 30 MB per response, 40 minutes per run, enforced before every request (companies not reached are free
time_limitrows you can re-run). - Change tracking compares to the previous run that used the same
stateStoreName, board and filters.
Pair it with
- Greenhouse Jobs Scraper & Job Alerts: the job-level view. When a company shows a spike here, pull the individual postings (titles, locations, salary where listed) with a new-jobs-only alert mode.
- Website Contact Scraper: once you know which accounts are hiring, collect public business contact details for the same domains.
Use it from your code, Make, Zapier or n8n
Built for automated pipelines: small runs, stable field names, one row per item, and you pay only for rows actually delivered.
One API call that runs the Actor and returns the data (replace $APIFY_TOKEN with your token from Apify Console → Settings → Integrations):
curl -s -X POST "https://api.apify.com/v2/acts/lukehunter~company-hiring-signals/run-sync-get-dataset-items?token=$APIFY_TOKEN" \-H 'Content-Type: application/json' \-d '{"companies": ["stripe.com", "ashby:openai", "https://jobs.lever.co/palantir"], "maxCompanies": 3}'
JavaScript (npm i apify-client):
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: process.env.APIFY_TOKEN });const run = await client.actor('lukehunter~company-hiring-signals').call({"companies": ["stripe.com","ashby:openai","https://jobs.lever.co/palantir"],"maxCompanies": 3});const { items } = await client.dataset(run.defaultDatasetId).listItems();
Python (pip install apify-client):
import osfrom apify_client import ApifyClientclient = ApifyClient(os.environ['APIFY_TOKEN'])run = client.actor('lukehunter~company-hiring-signals').call(run_input={"companies": ["stripe.com", "ashby:openai", "https://jobs.lever.co/palantir"], "maxCompanies": 3})items = client.dataset(run['defaultDatasetId']).list_items().items
Schedules, webhooks and no-code tools: save your input as a Task, add a Schedule (e.g. hourly or daily), and attach a webhook or one of the Integrations tab's connectors (Make, Zapier, n8n, Google Sheets, Slack) to push each run's results into your own system.
Feedback: an honest rating on the Store page, good or bad, helps others decide if this Actor fits their needs. Missing a field or found a problem? Open an issue on the Actor page.
FAQ
Is this allowed? It reads the same public, unauthenticated job-board JSON endpoints that companies publish for their own career pages, with a descriptive User-Agent, no login and no evasion.
Does it collect personal data? No. Rows contain company-level counts and job titles only. Recruiter and hiring-manager fields, where an ATS exposes them, are never read.
Why is my company not_found? It is probably on another ATS, or its board slug differs from its domain name. Find its careers page, then pass the board URL or ats:slug.
Why is newRolesSinceLastRun 0 on every row? That is the first run: it records the baseline. Run it again on a schedule.
Can I use a company's domain list from another Actor? Yes: pass the domains as companies, for example from a Google Maps or contact-scraper run.
Related Actors
Other data tools from the same developer, built to the same standard: official or public sources, hard cost caps, and honest documentation of limits.
- Website Contact Scraper: emails, phone numbers and social profile links from any list of company domains, with the page each came from and a changed-contacts-only mode for scheduled runs.
- Domain Email Provider Lookup: the email provider (Google Workspace, Microsoft 365, Zoho and more), MX, SPF and DMARC setup of any domain list, with provider-change alerts.
- Bulk WHOIS & Domain Age Lookup (RDAP): domain age, expiry date, registrar, nameservers and availability for any domain list over the official RDAP protocol, without personal contact data.
- French Company Leads: French companies (entreprises) from the official Sirene-based API, filtered by NAF code, department, headcount and creation date, with SIREN/SIRET, officers and new-company alerts.
- Poland KRS Company Lookup: Polish companies from the official KRS API by KRS number: NIP, REGON, address, share capital, PKD and the board as published, with optional VAT status.
- EU VAT Number Validator: bulk EU VAT number checks against the official VIES service: valid or invalid, registered name and address, with optional consultation numbers.
- Phone Number Validator: bulk phone number validation and E.164 formatting, offline: valid or invalid, country, and mobile or landline type for every number in a list or scraped contact dataset.
- Impressum Scraper: legal entity data from the imprint of any company website in Germany, Austria and Switzerland: legal name and form, managing directors, HRB/HRA or FN register, VAT id and address, with a changed-data-only mode for scheduled runs.