The Core Insight
Most career pages appear JS-rendered in a browser, but the data behind them is public JSON – the browser just fetches it via API after page load. If we intercept that API call instead of rendering the page, we get clean structured data with no browser automation needed.
The practical implementation splits into three tiers:
- ATS with a documented public API → direct API call, returns clean JSON
- ATS with an undocumented but predictable internal endpoint → replicate the API call the browser makes
- Fully custom career page → headless browser (Playwright) as a last resort; since the data is public and viewable in a browser, it can always be captured this way
Tier 1: ATS Platforms with Public APIs
No authentication required for any of the following.
Greenhouse
Used by a large share of tech and growth-stage companies.
GET https://boards-api.greenhouse.io/v1/boards/{token}/jobs?content=true
Returns JSON with full job listings including title, location, and description. The {token} is the company's board token, visible in the URL of their Greenhouse careers page.
Docs: developers.greenhouse.io/job-board
Lever
Common among mid-size tech companies.
GET https://api.lever.co/v0/postings/{company}
Returns JSON by default. Add ?mode=xml for XML. The {company} slug is visible in the careers page URL.
Docs: hire.lever.co/developer/documentation
Ashby
Fast-growing among startups and scale-ups.
GET https://api.ashbyhq.com/posting-api/job-board/{clientname}?includeCompensation=true
The {clientname} is visible in their careers page URL (jobs.ashbyhq.com/{clientname}).
BambooHR
GET https://{company}.bamboohr.com/jobs/embed2/json
SmartRecruiters
GET https://api.smartrecruiters.com/v1/companies/{company}/postings
Returns JSON for public postings.
Tier 2: Undocumented but Predictable Endpoints
Workday
Used by enterprise and large companies. No official public API, but the careers page loads data via an internal CXS JSON endpoint that can be called directly:
POST https://{company}.wd{n}.myworkdayjobs.com/wday/cxs/{company}/{site}/jobs
Where {n} is typically 1, 2, 3, or 5. The full URL structure is visible in browser DevTools (Network tab → XHR requests) on any Workday careers page. Returns the same structured JSON the browser uses to render the page.
Alternatively: use Playwright to intercept the network request rather than scraping the DOM. Since the data is public and the browser can always see it, a headless browser captures it reliably without parsing HTML.
Reference: jobspipe.dev/blog/workday-api-guide
Detection Logic
ATS detection can be automated by checking the career page URL or HTTP response headers:
| ATS | Detection signal |
|---|---|
| Greenhouse | URL contains greenhouse.io or boards.greenhouse.io |
| Lever | URL contains lever.co |
| Ashby | URL contains ashbyhq.com |
| BambooHR | URL contains bamboohr.com |
| SmartRecruiters | URL contains smartrecruiters.com |
| Workday | URL contains myworkdayjobs.com |
| Custom / unknown | Fallback to Playwright |
A simple script checks the career page URL stored in the watch list, routes to the correct handler, and falls back to Playwright if no match.
Data Storage and Deduplication
Since the data is public and structured, saving it is straightforward. Each fetched position gets stored locally (SQLite or a flat JSON file) with:
company– from watch listjob_id– from ATS response (stable identifier)title,location,urlfirst_seen_datestatus–new/reviewed/shortlisted/skipped
On each weekly cycle, the script compares fetched job_id values against stored records. Only positions with no existing record are flagged as new. This also solves the duplicate detection problem as a side effect.
Coverage Estimate
| ATS | Typical share of target companies |
|---|---|
| Greenhouse | ~30% (tech, growth-stage) |
| Workday | ~25% (enterprise) |
| Lever | ~15% (mid-size tech) |
| Ashby | ~10% (startups, scale-ups) |
| BambooHR / SmartRecruiters / other | ~10% |
| Custom / unknown | ~10% |
Handling the top four ATSs with direct API calls covers approximately 80% of the target company universe without a browser. The remaining ~20% (custom pages) fall back to Playwright – still automatable, just slower and more brittle.