Performance marketing for AI-era growth – backed by models, not gut feeling.

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:

  1. ATS with a documented public API → direct API call, returns clean JSON
  2. ATS with an undocumented but predictable internal endpoint → replicate the API call the browser makes
  3. 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}).

Docs: developers.ashbyhq.com

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:

ATSDetection signal
GreenhouseURL contains greenhouse.io or boards.greenhouse.io
LeverURL contains lever.co
AshbyURL contains ashbyhq.com
BambooHRURL contains bamboohr.com
SmartRecruitersURL contains smartrecruiters.com
WorkdayURL contains myworkdayjobs.com
Custom / unknownFallback 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:

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

ATSTypical 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.