ATS Jobs Feed (Greenhouse, Lever, Ashby & more)
Pricing
from $2.00 / 1,000 job exporteds
ATS Jobs Feed (Greenhouse, Lever, Ashby & more)
Job listings from the public JSON APIs of Greenhouse, Lever, Ashby, Workable and SmartRecruiters. One clean record per job (Markdown description, department, locations, remote flag, salary when published, apply URL) plus a diff mode that flags new, updated and closed jobs between runs.
Pricing
from $2.00 / 1,000 job exporteds
Rating
0.0
(0)
Developer
Vellum Kasane
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
3 days ago
Last modified
Categories
Share
ATS Jobs Feed (Greenhouse, Lever, Ashby, Workable, SmartRecruiters)
Get every open job of any company that runs its careers page on Greenhouse, Lever, Ashby, Workable or SmartRecruiters as clean, structured records, straight from the vendors' public job-board JSON APIs. No HTML scraping, no browser, no proxies: a run over four boards finishes in a few seconds.
Each job comes out as one record with the description converted to Markdown, department and team, all locations, an inferred remote flag, employment type, the published salary range when the company discloses one, the hosted job URL and the apply URL. Turn on diff mode and a scheduled run becomes a change feed: every record is marked new, updated or closed compared with the previous run, so you only pay for what changed.
Built for:
- Job boards and aggregators that want one normalised schema across five ATS vendors instead of five parsers.
- Recruiting and sales intelligence: watch competitors' or target accounts' hiring (
newengineering roles in Berlin, closed positions, salary bands). - AI agents and MCP clients that need job descriptions as Markdown (call it from the Apify MCP server, read the dataset).
- Alerts: schedule hourly with
diff: true, plug the dataset into Slack, email or a webhook via Apify integrations.
How it works
- Give the Actor a list of companies. Each entry is
{ "ats": "greenhouse", "slug": "gitlab" }or just the careers URL (https://jobs.lever.co/spotify,https://jobs.ashbyhq.com/linear,https://apply.workable.com/blueground/,https://careers.smartrecruiters.com/Bosch). The ATS and the board slug are detected from the URL. - The Actor calls the vendor's public JSON endpoint for that board (see Data sources), normalises every job and converts the HTML description to GitHub-flavoured Markdown.
- Filters (keywords, location, remote, department) are applied, then records are written to the dataset, one per job, and billed per record.
- In diff mode the Actor keeps a compact snapshot of every job it has seen in a named key-value store and emits only changes.
Input
| Field | Type | Default | Notes |
|---|---|---|---|
companies | array | required | { ats, slug } objects and/or board URLs. ats is one of greenhouse, lever, ashby, workable, smartrecruiters. Lever EU boards: { "ats": "lever", "slug": "acme", "region": "eu" } or the jobs.eu.lever.co URL. |
keywords | string[] | [] | Keep jobs whose title or description contains any of these (case-insensitive). |
locationContains | string[] | [] | Keep jobs whose location contains any of these. |
remoteOnly | boolean | false | Keep only jobs with remote: true. |
departments | string[] | [] | Keep jobs whose department / team contains any of these. |
maxJobs | integer | 500 | Hard cap on exported records per run (all boards). Caps your cost. |
diff | boolean | false | Diff mode: mark new / updated / closed versus the previous run. |
emitUnchanged | boolean | false | Diff mode: also export unchanged jobs (status unchanged). |
stateStoreName | string | ats-jobs-feed-state | Named key-value store holding the diff state. Use one per schedule / filter set. |
includeHtml | boolean | false | Add the original description HTML (description_html). |
Example input:
{"companies": [{ "ats": "greenhouse", "slug": "greenhouse" },"https://jobs.ashbyhq.com/posthog",{ "ats": "workable", "slug": "blueground" },{ "ats": "smartrecruiters", "slug": "smartrecruiters" }],"keywords": ["engineer"],"remoteOnly": true,"maxJobs": 200,"diff": true}
Finding the slug: it is the token in the company's careers URL, e.g. boards.greenhouse.io/**gitlab**, job-boards.greenhouse.io/**gitlab**, jobs.lever.co/**spotify**, jobs.ashbyhq.com/**linear**, apply.workable.com/**blueground**, careers.smartrecruiters.com/**Bosch**. Unknown boards are reported per board in SUMMARY.json and skipped; the run still succeeds for the other boards.
Output
One dataset item per job (you can download it as JSON, CSV, Excel or HTML):
{"company": "PostHog","company_slug": "posthog","ats": "ashby","board_url": "https://api.ashbyhq.com/posting-api/job-board/posthog?includeCompensation=true","job_id": "2f1e5d7c-3c1e-4a0f-9b1d-6f0a3d2c1b9e","title": "Product Engineer","department": "Engineering","team": "Product","location": "Remote - EMEA","locations": ["Remote - EMEA", "Remote - US"],"remote": true,"workplace_type": "remote","employment_type": "Full-time","salary": {"min": 150000,"max": 220000,"currency": "USD","interval": "year","summary": "$150K - $220K","source": "ats"},"description_markdown": "## About the role\n\nYou will ship features end to end...\n\n- Own a product area\n- Talk to users every week","job_url": "https://jobs.ashbyhq.com/posthog/2f1e5d7c-3c1e-4a0f-9b1d-6f0a3d2c1b9e","apply_url": "https://jobs.ashbyhq.com/posthog/2f1e5d7c-3c1e-4a0f-9b1d-6f0a3d2c1b9e/application","published_at": "2026-08-12T09:14:02.000Z","updated_at": null,"first_seen": "2026-08-12T09:14:02.000Z","status": "listed","closed_at": null,"content_hash": "6d0c5c0b8f7c0d5c3c1e9a3b0f2a7d4e1c8b6a9f0e3d2c1b4a5f6e7d8c9b0a1f","fetched_at": "2026-09-22T14:46:40.278Z"}
| Field | Meaning |
|---|---|
status | listed (diff off) or new / updated / unchanged / closed (diff on). Closed records carry the last known title, location, department and URLs; description_markdown is null. |
remote / workplace_type | From the ATS flag when it exists (Ashby isRemote / workplaceType, Lever workplaceType, Workable telecommuting, SmartRecruiters location.remote), otherwise inferred from the location and title ("Remote", "Hybrid", "Work from home", ...). |
salary | Structured pay range from Greenhouse (pay_input_ranges), Lever (salaryRange) or Ashby (compensation), source: "ats". When the ATS has no field but the description states a range such as $120,000 - $160,000, it is parsed with source: "description". Otherwise null. |
updated_at | Only Greenhouse exposes it. Change detection uses content_hash (title + location + department + type + description), so it works for every vendor. |
first_seen | Diff mode: the first run in which the job was seen. Otherwise the ATS publish date. |
SUMMARY.json in the key-value store lists, per board, the API URL, jobs fetched, jobs matching the filters, jobs exported, and new / updated / unchanged / closed counts, plus any per-board error.
Diff mode
With diff: true the Actor stores STATE in the named key-value store (stateStoreName): one compact snapshot per job (title, location, URLs, first_seen, content_hash). On the next run:
- job not in the state:
new - job in the state with a different
content_hash:updated - job in the state that is no longer on the board:
closed(withclosed_at) - same hash:
unchanged(only exported whenemitUnchangedis on)
Filters are applied before diffing, so the state only tracks jobs matching your filters; changing the filters therefore produces one wave of new / closed records. A board that fails to load leaves its part of the state untouched (no false closures). State is saved after every board, so a run that hits maxJobs or its budget resumes cleanly.
Pricing
This Actor uses pay-per-event pricing:
| Event | Price | When |
|---|---|---|
job-exported | $0.002 per record ($2 per 1,000) | Once per record written to the dataset, including closed records in diff mode. Filtered-out jobs, unchanged jobs (unless emitUnchanged), duplicates and failed boards are never charged. |
| Actor start | $0.005 per run | Apify's standard start event; covers the first seconds of compute. |
Examples: a one-off pull of a 300-job board costs about $0.61. An hourly diff schedule over 50 boards that together change 40 jobs a day costs about $0.20 a day in events plus 24 starts ($0.12). Set Max total charge per run to hard-cap spend; the Actor stops gracefully before exceeding it.
Data sources and limits
| ATS | Endpoint used | Documentation | Notes |
|---|---|---|---|
| Greenhouse | GET https://boards-api.greenhouse.io/v1/boards/{slug}/jobs?content=true&pay_transparency=true | https://docs.greenhouse.io/job-board.html | "Job Board data is publicly available, so authentication is not required." The description is delivered entity-encoded and is decoded before conversion. No pagination (whole board in one response, several MB for large companies). |
| Lever | GET https://api.lever.co/v0/postings/{slug}?mode=json (EU: api.eu.lever.co) | https://github.com/lever/postings-api | Public, no key for reads. Description, sections (lists) and closing text are merged into one document. Boards with no public postings return []. |
| Ashby | GET https://api.ashbyhq.com/posting-api/job-board/{slug}?includeCompensation=true | https://developers.ashbyhq.com/docs/public-job-posting-api | Unlisted jobs (isListed: false) are skipped. Compensation tiers are summarised into salary. |
| Workable | GET https://www.workable.com/api/accounts/{subdomain}?details=true (redirects to apply.workable.com/api/v1/widget/accounts/{subdomain}) | https://workable.readme.io/reference/jobs-1 | Public widget endpoint ("security": [{}]), returns published jobs with descriptions. No structured salary. Accounts without a public careers page return Not Found. |
| SmartRecruiters | GET https://api.smartrecruiters.com/v1/companies/{id}/postings?limit=100&offset=N + GET .../postings/{postingId} | https://developers.smartrecruiters.com/docs/endpoints | Public Posting API; the description lives in the detail call, so each job costs one extra request (fetched with concurrency 4, only for jobs that pass the metadata filters). No structured salary. |
Other limits:
- The Actor reads only what the vendors publish on their public job-board APIs; it does not scrape careers pages, so companies on other ATS vendors (Workday, Taleo, iCIMS, BambooHR, Personio...) are not supported.
- Job ids are stable per vendor, but a company migrating from one ATS to another changes every id (one wave of
closed+new). - Vendors do not document rate limits for these endpoints; the Actor fetches boards sequentially and retries 429/5xx with backoff. Runs with hundreds of boards may take a few minutes.
Tips
- Follow a competitor set: put 20-50 boards in
companies,diff: true, schedule hourly, and connect the dataset to Slack. Only changes are billed. - Salary research:
keywords: ["engineer"], then filter the dataset onsalary.source == "ats"for disclosed ranges (Greenhouse pay-transparency boards and Ashby publish the most). - Remote roles only:
remoteOnly: truecombines the vendor flag with text inference; checkworkplace_typeforhybrid. - Different filter sets: use a separate
stateStoreNameper schedule so the diffs do not interfere.
Verified
Local run on 2026-09-22 with apify run --purge (Apify CLI 1.10.0, Apify SDK 3.7.2, Node 24), prefill input above (four boards, maxJobs: 40):
- 4 boards fetched (
greenhouse:greenhouse21 jobs,ashby:posthog9,workable:blueground7,smartrecruiters:smartrecruiters1) - 38 records exported, 0 errors, runtime about 3 seconds. - Output spot-checked per vendor: Markdown descriptions without leftover tags or entities, locations, remote inference, employment type, salary from Ashby compensation, Greenhouse
updated_at. - Diff mode tested with a separate state store: first run marked every job
new; after editing the stored state (one job removed, one hash changed, one phantom id added), the second run produced exactly onenew, oneupdatedand oneclosedrecord and no others. - Pay-per-event path tested with
ACTOR_TEST_PAY_PER_EVENT=1 ACTOR_MAX_TOTAL_CHARGE_USD=5(local test price $1/event): 5job-exportedevents charged, then the Actor stopped with "Run budget reached (maxTotalChargeUsd)". - Unit tests:
npm test(13 tests over real API fixtures: ATS detection from URLs, all five normalisers, entity decoding, salary parsing, remote inference, filters, diff). - Endpoint checks the same day: Greenhouse boards
greenhouse,gitlab,stripe,figma,duolingo,anthropicrespond; Leverleverdemo,spotify,palantirrespond (netlify,plaidreturn 404 - those companies no longer use Lever); Ashbynotion,linear,posthog,ramp,openairespond; Workableblueground,skroutz,nuveirespond; SmartRecruiterssmartrecruitersresponds.
Support
Open an issue on the Actor's Issues tab. Issues are answered within one business day; bug fixes ship as new builds without changing the input schema.
Made by Vellum Labs.