ATS Jobs Tracker: Greenhouse, Lever, Ashby & Workable Jobs
Pricing
from $1.25 / 1,000 job exporteds
ATS Jobs Tracker: Greenhouse, Lever, Ashby & Workable Jobs
Track open jobs at any company on Greenhouse, Lever, Ashby or Workable. Paste board slugs, careers URLs or names. Only-new mode for daily monitoring, repost detection, salary, remote and department fields, plus a free per-company hiring summary. No login, no proxies. $1.75 per 1,000 jobs.
Pricing
from $1.25 / 1,000 job exporteds
Rating
0.0
(0)
Developer
Destiny Lazcano
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
a day ago
Last modified
Categories
Security
Limited permissionsShare
Export every open job at the companies you care about, straight from their public Greenhouse, Lever, Ashby and Workable job boards, and track hiring over time. Paste board slugs, careers URLs or company names. You get one clean row per job with title, department, location, remote flag, employment type, posting date, salary and links. You also get a free per-company hiring summary that shows how many roles are open, in which teams and where.
Built for recruiters and sourcing teams, sales teams who treat hiring as a buying signal, investors and analysts watching headcount growth, and job seekers and job boards that need fresh postings from specific employers.
Why this tracker
- Four ATSs, one schema. Greenhouse, Lever (global and EU), Ashby and Workable are normalized into the same columns, so a mixed list of companies becomes one table.
- Auto-detection. Give a slug or a name (
stripe,Hugging Face,stripe.com) and the Actor tries each ATS in turn until it finds a public board. - Only-new monitoring mode. Schedule it daily: the first run exports everything up to your Maximum jobs to export, later runs export only postings you have not received before. Jobs you already have are never charged again.
- Repost detection. When a role disappears and comes back under a new job ID with the same title and location, it is flagged
isRepost: truewithrepostOfpointing at the old ID. - Salary everywhere it exists. Structured pay from Lever and Ashby, plus pay ranges parsed from the description text (for example
$150,000—$180,000 USDon Greenhouse pay-transparency postings).compensationSourcetells you which. - Free hiring summary per company in the key-value store: open jobs by department, location, workplace type and employment type, jobs posted in the last 7 and 30 days, median days open, median USD salary, and in monitoring mode new, closed and reposted counts.
Supported job boards
| ATS | Paste any of these | Notes |
|---|---|---|
| Greenhouse | https://job-boards.greenhouse.io/stripe, https://boards.greenhouse.io/airbnb/jobs/123, embed links with ?for=figma, or greenhouse:stripe | Salary comes from the description text when the posting shows a range. |
| Lever | https://jobs.lever.co/palantir, https://jobs.eu.lever.co/<site>, or lever:spotify | Global and EU instances are both checked. |
| Ashby | https://jobs.ashbyhq.com/openai, or ashby:ramp | Structured compensation included. Unlisted (direct-link only) jobs are skipped. |
| Workable | https://apply.workable.com/huggingface/, https://acme.workable.com, or workable:huggingface | A /j/<code> job link does not name the company, so use the company page. |
Custom careers domains (for example careers.airbnb.com) are reduced to a name guess (airbnb) and auto-detected. Careers pages hosted on a shared platform or another ATS (Notion, GitHub Pages, Workday, SmartRecruiters, BambooHR, Gusto, Wellfound, Y Combinator and similar) are rejected with a clear error instead, because their domain names the vendor, not the employer. Paste the company's board link or name for those.
Input examples
Three companies on three different ATSs (this is the prefilled example input in Console). companies is required and has no default. Without maxJobs an API or scheduled run uses 1,000.
{ "companies": ["discord", "https://jobs.ashbyhq.com/linear", "https://apply.workable.com/huggingface/"], "maxJobs": 100 }
Remote engineering roles posted in the last 14 days, with descriptions:
{"companies": ["stripe", "figma", "https://jobs.lever.co/palantir", "ashby:ramp"],"keywords": ["engineer", "developer"],"excludeKeywords": ["intern"],"remoteOnly": true,"postedWithinDays": 14,"includeDescription": true}
Daily monitoring of a watch list (pair it with an Apify Schedule):
{ "companies": ["stripe", "cloudflare", "lever:spotify"], "onlyNew": true, "stateKey": "competitors" }
Filters: keywords and excludeKeywords match whole words or phrases in the job title, ignoring case. Plural, -ing, -er and -ship forms also match, so engineer matches "Engineering Manager" and intern matches "Internship" but not "Internal Audit" or "International Tax". locations match whole words in the location text. A country name or abbreviation (United States, USA, US, United Kingdom, UK, Germany, ...) also matches the job's country code, whatever form the ATS uses. Remote matches remote-flagged jobs. Greenhouse publishes no country, so there a country filter matches only when the location text names the country. departments match department or team. postedWithinDays uses Greenhouse first_published, Lever createdAt, Ashby publishedAt and Workable published_on. Jobs with no date are kept.
Output
One row per job, de-duplicated by ATS + company + job ID:
| Column | Example |
|---|---|
ats, company, companyName | greenhouse, discord, Discord |
jobId, jobKey, title | 8917177002, greenhouse:discord:8917177002, Associate Commercial Counsel |
department, team | Legal |
location, allLocations, country | San Francisco Bay Area or Remote (U.S.), ..., US (ISO code when the ATS gives a country) |
isRemote, workplaceType | true, remote / hybrid / onsite / empty. Hybrid jobs are isRemote: false unless one of their locations is remote. |
employmentType | Full-time, Part-time, Contract, Internship, ... |
postedAt, updatedAt, daysOpen | 2026-11-03T16:27:16Z, 2 |
compensation, salaryMin, salaryMax, salaryCurrency, salaryInterval, compensationSource | $200,000—$225,000 USD, 200000, 225000, USD, year, description |
jobUrl, applyUrl, boardUrl | links to the employer's own posting |
description | plain text, only with Include job description |
isNew, isRepost, repostOf, firstSeenAt | only-new mode only |
scrapedAt | when the board was read |
Dates are ISO 8601 in UTC. Workable publishes dates without a time, so its postedAt is a plain date. Lever and Ashby publish no update time, and Lever's date is when the posting was created. companyName is filled where the ATS publishes it (Greenhouse and Workable).
The key-value store also holds HIRING_SUMMARY (free, one entry per company, computed over the whole board before your filters) and RUN_REPORT (what was read, what was found, what failed and why).
Pricing
Pay per job delivered to your dataset. The summary, the report, closed-job notices and skipped already-seen jobs are free.
| Apify plan | Price per job | Per 1,000 jobs |
|---|---|---|
| Free and Bronze | $0.00175 | $1.75 |
| Silver | $0.00150 | $1.50 |
| Gold and above | $0.00125 | $1.25 |
Apify's standard run-start fee ($0.00005) applies. Your run's Maximum cost per run is respected: at $1.75 per 1,000 jobs a $1.00 cap allows 571 jobs, and the run stops cleanly with a status message saying so. Maximum jobs to export caps a run too.
Monitoring with Apify Schedules
- Turn on Only new jobs and give your list a Monitoring list name.
- Raise Maximum jobs to export for the first run (the Console prefill is 100) so it covers every open job on your list. Run once. Everything currently open is exported and remembered, up to that maximum.
- In Console, create a Schedule (for example daily at 07:00) that runs this Actor with the same input.
- Each later run exports only new postings and updates HIRING_SUMMARY with new, closed and reposted counts. Connect a webhook, Slack or Google Sheets integration to get the new rows automatically.
If a run stops at the maximum, the jobs it did not reach come in later runs as isNew: false rows, and companies after the cut are not checked (no HIRING_SUMMARY entry) in that run.
The memory lives in your own account, in the named key-value store ats-jobs-tracker-state-<name>. Delete that store to start over. A job that was filtered out earlier is exported the first time it matches your filters.
Limits and good citizenship
- Reads only the public, unauthenticated job-board endpoints each ATS documents for careers pages and widgets. It never logs in, never submits applications and never touches candidate data.
- One honest User-Agent, no proxies, no cookies, at most one request per second per host. When an ATS answers 401, 403, 429, 451, 503 or a bot check, that answer is recorded as refused and the Actor sends no further requests to that ATS for the rest of the run. Every later company on it is reported as refused too. A name being auto-detected is then also reported as refused instead of being matched on another ATS, so it never resolves to the wrong company's board. Network errors and other 5xx are retried once.
- One company failing (wrong slug, 404, timeout) never stops the run. The status message names the failures and RUN_REPORT gives the details.
- Up to 500 companies and 100,000 jobs per run. Large boards (several MB) take a few seconds each.
- Job-board content belongs to each employer. Link back with
jobUrland respect each employer's terms when you republish. Workable marks its pages as not for AI training.
FAQ
A company was "not found". It may use another ATS, a different slug, or no public board at all. Open its careers page, copy the link to its job board and paste that instead of the name.
Why is the salary empty? The employer did not publish one. Ranges are read from structured fields first and from the description text second. Single numbers are ignored on purpose to avoid false matches.
Do I get duplicate rows across runs? Not in only-new mode. Without it, each run is a full snapshot.
Why does Greenhouse not show workplaceType? Greenhouse has no standard field. It is read from a custom "Workplace Type" field when the company has one. Some boards (Cloudflare, for example) put "Hybrid", "In-Office" or "Distributed" where the location should be. That value becomes workplaceType, and the location is taken from a location custom field or the offices. Otherwise isRemote falls back to "remote" in the location or title.
Something looks wrong. Open an issue on the Issues tab with your input JSON. Issues are answered within two business days.