Job Board Scraper — Greenhouse, Lever, Ashby & Workday
Pricing
from $2.00 / 1,000 job scrapeds
Job Board Scraper — Greenhouse, Lever, Ashby & Workday
Aggregate public ATS job boards across Greenhouse, Lever, Ashby and Workday: normalize, dedupe, and emit hiring signals. Public endpoints only; PII fields filtered.
Job Board Scraper — Greenhouse, Lever, Ashby & Workday (ATS Aggregator)
A single job scraper that aggregates public jobs data across four major applicant tracking systems — Greenhouse, Lever, Ashby and Workday — then normalizes every posting into one schema, de-duplicates roles that appear on more than one board, and (in monitor mode) emits hiring signals when a company opens or closes reqs.
It reads only official, public job-board endpoints (the same JSON these career sites load in your browser). It does not touch any login-gated page and it filters personal data out of the output by design.
- Greenhouse API —
boards-api.greenhouse.iopublic board API - Lever postings —
api.lever.co/v0/postingspublic postings API - Ashby —
api.ashbyhq.com/posting-api/job-boardpublic posting API - Workday scraper — public
/wday/cxs/{tenant}/{site}/jobscareer-site search endpoint
Use cases
- B2B sales & market intent — "who is hiring a Salesforce admin / a Kubernetes SRE / a
RevOps lead right now" is a strong buying signal. Monitor mode turns new reqs into
jobs_addedevents you can route to sales. - Recruiting & talent mapping — track competitor headcount plans and open roles by department and location across every ATS at once.
- Salary / compensation research — capture
salary_min/salary_maxwhere the board exposes it (Ashby and Lever frequently do; pay-transparency roles on others). - Job-board aggregation — build a clean, de-duplicated jobs feed from many companies and many ATS platforms without writing four different scrapers.
Input
{"targets": [{ "ats": "greenhouse", "token": "stripe", "company": "Stripe" },{ "ats": "lever", "token": "palantir", "company": "Palantir" },{ "ats": "ashby", "token": "ramp", "company": "Ramp" },{ "ats": "workday", "token": "salesforce", "company": "Salesforce","workday": { "host": "salesforce.wd12.myworkdayjobs.com", "tenant": "salesforce", "site": "External_Career_Site" } }],"includeDescription": false, // true = add PII-scrubbed plain-text description (max 5000 chars)"mode": "snapshot", // "snapshot" | "monitor""maxJobsPerTarget": 0 // 0 = all public postings}
Finding the token/host for each ATS
| ATS | token is… | Extra |
|---|---|---|
| greenhouse | board token in boards.greenhouse.io/<token> | — |
| lever | slug in jobs.lever.co/<token> | — |
| ashby | slug in jobs.ashbyhq.com/<token> | — |
| workday | tenant name | also set workday.host / workday.tenant / workday.site from the public career-site URL https://<host>/<lang>/<site> |
Output
Every job is normalized to the same whitelist of fields (no raw source JSON is ever passed through):
{"ats": "ashby", "company": "Ramp","job_id": "…", "canonical_key": "sha1(company|title|location)","title": "Customer Activation Manager | Enterprise","department": "Sales", "team": null,"location": "New York", "locations": ["New York", "Remote (US)"], "remote": false,"salary_min": 147000, "salary_max": 262000, "salary_currency": "USD","posted_at": "2026-05-11T12:47:52.238Z", "updated_at": null,"url": "https://jobs.ashbyhq.com/ramp/…","description_text": null,"sources": [ { "ats": "ashby", "company": "Ramp", "job_id": "…", "url": "https://…" } ]}
De-duplication & hiring signals
De-dupe. The same role can appear multiple times (posted per-shift, or listed on two ATS during
a migration). Items sharing a canonical_key — sha1(company | normalized_title | primary_location)
— collapse into the single most complete record, and every origin is kept in sources[].
Monitor mode. Set "mode": "monitor". The actor stores a per-company snapshot (the set of
canonical_keys) in a persistent key-value store and, on the next run, emits signal items:
{ "type": "signal", "event": "jobs_added", "company": "Stripe", "count": 5, "titles": ["…"], "total_active": 123, "ts": "…" }{ "type": "signal", "event": "jobs_removed", "company": "Stripe", "count": 2, "titles": ["…"], "total_active": 123, "ts": "…" }
Job items have no type field; signal items are tagged "type": "signal" and operational
records (a board that could not be fetched) are tagged "type": "status", so all three are easy to
filter apart in the dataset.
Compliance
- Public endpoints only. The actor calls the official, public job-board APIs that these career
sites serve to anonymous browsers. It never accesses
/admin, applicant, or any login-gated page, and for Workday it only calls the public CXS search endpoint — never an authentication host. - Personal data is filtered out. Output is a strict field whitelist: recruiter names, contact
emails and similar fields are never mapped. When
includeDescriptionis enabled, the description text is additionally run through an email/phone scrubber before it is stored. - robots.txt is fetched and logged for each host as a courtesy trace.
- Your responsibility. You should review each ATS provider's Terms of Service and each target company's terms before scraping at scale, and confirm your use complies with applicable law (including data-protection rules in your jurisdiction). This actor is a tool; how you use it is up to you.
FAQ
Which ATS platforms are supported? Greenhouse, Lever, Ashby and Workday public boards.
Do I need an API key or login? No. Only public endpoints are used; no authentication.
Does it use a headless browser? No — it is pure HTTP/JSON, which is fast and cheap.
Can it fetch full job descriptions? Yes, set includeDescription: true. Descriptions are
converted to plain text, scrubbed of emails/phone numbers, and truncated to 5000 characters.
How does Workday pagination work? The actor pages the public CXS endpoint 20 results at a time. Workday only returns the true total on the first page, which the actor accounts for.
What if one company or ATS fails? Each target is isolated — a failure is recorded as a status
item (e.g. endpoint_unavailable, rate_limited, workday_probe_failed) and the run continues.
Local development
npm installnpm test # unit tests (sanitize / normalize / dedupe / signals / schema)npm run typecheckapify run # or: npx tsx src/main.ts (reads storage/key_value_stores/default/INPUT.json)