If you've ever tried to collect job postings straight from company career sites, you know the problem: every applicant-tracking system (ATS) looks different. Workday paginates and stops at 2,000 results. Oracle Recruiting Cloud has its own Candidate Experience pages. Greenhouse, Lever and Ashby each return a different JSON shape. And once you've finally got the data, you still have to work out what changed since yesterday.

This guide shows how to get all open jobs from those five platforms in one normalized format, and how to flag new and closed jobs between runs, using the HireWatch Workday, Greenhouse, Lever, Ashby & Oracle Jobs Scraper (ATS) on Apify.

Who this is for

  • Recruiters and talent-intel teams watching competitors' headcount by department and location.
  • Job boards and aggregators that want complete, de-duplicated listings from the source, including salary ranges where the company publishes them.
  • Sales teams using hiring as a buying signal. A company that opens 40 new engineering roles this week is probably buying tools and services.
  • Developers and analysts who want hiring velocity (new minus closed jobs) as a data feed.

What you can paste in

Platform Input Example
Workday career-site URL https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite
Oracle Recruiting Cloud Candidate Experience URL https://jpmc.fa.oraclecloud.com/hcmUI/CandidateExperience/en/sites/CX_1001/jobs
Greenhouse board URL or company name Stripe
Lever board URL or company name Palantir
Ashby board URL or company name https://jobs.ashbyhq.com/ramp or Ramp

Name lookup works for Greenhouse, Lever and Ashby. Workday and Oracle need a URL: open the company's "Search jobs" page and copy the address from your browser. You can also paste a company's own careers page (e.g. https://acme.com/careers), and the Actor looks for an embedded or linked board from the platforms above. To set a display name, use Name | URL.

Step 1: Run it in the Apify Console

  1. Open the Actor page and click Try for free (or Start).
  2. In targets, add one company per line, URLs or names.
  3. Set maxJobsPerCompany to 0 to get every job. The default is 1,000, and change tracking is only fully accurate when the whole board is fetched.
  4. Leave includeCompanySummary on if you want one hiring-signal row per company.
  5. Optionally set a Maximum cost per run in the run options. The Actor stops cleanly when it hits the cap.
  6. Click Start. The results appear in three dataset views: Jobs, Company hiring signals and Jobs with descriptions, and you can export them to CSV, Excel or JSON.

Here's a first-run input mixing all five platforms:

{ "targets": [ "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite", "JPMorgan Chase | https://jpmc.fa.oraclecloud.com/hcmUI/CandidateExperience/en/sites/CX_1001/jobs", "Stripe", "Palantir", "https://jobs.ashbyhq.com/ramp" ], "maxJobsPerCompany": 0, "includeCompanySummary": true } 

Step 2: Call it from code

curl

curl -X POST \ "https://api.apify.com/v2/acts/hirewatch~career-sites-jobs-tracker/run-sync-get-dataset-items" \ -H "Authorization: Bearer <YOUR_APIFY_TOKEN>" \ -H "Content-Type: application/json" \ -d '{"targets": ["Stripe", "Palantir", "Ramp"], "maxJobsPerCompany": 0}' 

This waits for the run and returns the dataset items as JSON in one call. The sync endpoint waits at most 5 minutes, so for big Workday or Oracle boards start the run with /runs instead and read the dataset when it finishes. To cap spend, add ?maxTotalChargeUsd=1 to the URL.

Python (apify-client)

Install the client with pip install apify-client. The snippet below is for version 3.x, where call() returns a Run object. On older 1.x/2.x clients, use run["defaultDatasetId"] instead of run.default_dataset_id.

from apify_client import ApifyClient client = ApifyClient("<YOUR_APIFY_TOKEN>") run = client.actor("hirewatch/career-sites-jobs-tracker").call(run_input={ "targets": [ "https://intel.wd1.myworkdayjobs.com/External", "Stripe", "OpenAI", ], "maxJobsPerCompany": 0, "includeCompanySummary": True, }) for item in client.dataset(run.default_dataset_id).iterate_items(): if item["recordType"] == "company_summary": print(item["companyName"], item["totalOpenJobs"], item["trend"]) else: print(item["changeStatus"], item["companyName"], item["title"], item["location"]) 

What the output looks like

Every job has the same fields across all five platforms (missing values are null). A real Ashby record, trimmed:

{ "recordType": "job", "platform": "ashby", "companyName": "Ramp", "globalId": "ashby:ramp:34413f8d-26bf-4bbc-8ade-eb309a0e2245", "title": "Security Engineer, Cloud", "department": "Engineering", "employmentType": "full_time", "remoteType": "hybrid", "location": "New York, NY (HQ)", "country": "USA", "postedAt": "2026-04-07T17:12:35Z", "salaryMin": 211400, "salaryMax": 290600, "salaryCurrency": "USD", "url": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245", "changeStatus": "new", "firstSeenAt": "2026-10-08T03:07:59Z", "closedAt": null } 

Use globalId as your primary key, since it stays the same across runs. companyName is the name the platform publishes, the name you searched for, or your Name | URL label; otherwise it falls back to the board slug (e.g. intel). The optional company_summary record adds totalOpenJobs, newSinceLastRun, closedSinceLastRun, netChange, trend (growing / stable / shrinking, or null on the first run when there is nothing to compare yet), postedLast7Days, postedLast30Days, topDepartments, topLocations and remoteTypeCounts.

Step 3: Track new and closed jobs

The Actor keeps state in a named key-value store, so each run labels jobs new, unchanged or closed. To build a cheap daily change feed, switch to Only changes mode and schedule it:

{ "targets": ["Stripe", "OpenAI", "https://intel.wd1.myworkdayjobs.com/External"], "maxJobsPerCompany": 0, "outputMode": "changesOnly", "titleKeywords": ["engineer", "developer"], "trackerId": "eng-daily" } 

A few things worth knowing:

  • In changes-only mode, the first run records a baseline and outputs nothing. Turn on includeBaselineOnFirstRun if you also want the full list on day one.
  • trackerId keeps separate histories for different schedules that watch the same companies.
  • To avoid false alarms when a site's own count is slightly off (common on Oracle), a job is marked closed only after it has been missing for two runs in a row.
  • If you only need the signal and not the job rows, set "includeJobs": false and keep includeCompanySummary on.

The Workday 2,000-job cap

Workday's own listing stops at 2,000 results, which cuts off the biggest employers. The Actor gets around this by splitting the listing by job family (or another filter) and collecting each slice. In a test run on Oct 7, 2026 it returned 2,673 jobs for NVIDIA.

That same test covered 11 companies and 18,902 jobs in about 2.5 minutes:

  • Workday: NVIDIA 2,673 · Capital One 1,927 · Salesforce 1,536 · Intel 639 · Adobe 408
  • Oracle: JPMorgan Chase 7,415 · Oracle 2,291
  • Greenhouse: Stripe 722
  • Lever: Palantir 315
  • Ashby: OpenAI 815 · Ramp 161

Listings are fast. Full descriptions (includeDescriptions) on Workday and Oracle need one extra request per job at about one per second per site, so use them on filtered or smaller sets.

Polite by design

The Actor uses only the public job-listing data that each career page loads in your browser: no logins and no browser automation. It respects robots.txt, waits at least one second between requests to a site, and backs off on 429/5xx responses. Contact details are never collected: recruiter fields are ignored, and email addresses and phone numbers in descriptions are masked. Bad inputs (stale URLs, unknown names, robots-disallowed pages) are reported per company in RUN_REPORT. One bad input doesn't fail the whole run.

Pricing, with a real example

Pay per result: $1.50 per 1,000 job records and $0.01 per company summary.

  • The 11-company test above: 18,902 jobs × $0.0015 + 11 summaries ≈ $28.46.
  • Daily changes-only monitoring of 50 companies (about 300 changed jobs plus 50 summaries): ≈ $0.95 per day.
  • Summaries only for 100 companies: $1.00 per run.

Limits to know

  • Remote type on Workday is only known when descriptions are fetched. Oracle often leaves workplace type blank.
  • Workday posted dates are approximate without descriptions ("Posted 3 Days Ago" is kept in postedAtText).
  • EU-hosted Greenhouse boards aren't supported yet. If you need SmartRecruiters, iCIMS or SuccessFactors, open an issue on the Actor's Issues tab.

HireWatch is an independent publisher. This tool is not affiliated with or endorsed by Workday, Oracle, Greenhouse, Lever or Ashby. Product names are used only to describe compatibility. You are responsible for how you use the data, so check the terms of the sites involved and the laws that apply to you.