# Career Site Jobs Aggregator - ATS Salary Data (`datagrit/career-site-jobs-aggregator`) Actor

Open jobs from Greenhouse, Lever, Ashby, Workday, Workable, SmartRecruiters, Recruitee and Personio in one schema with normalized salaries.

- **URL**: https://apify.com/datagrit/career-site-jobs-aggregator.md
- **Developed by:** [datagrit](https://apify.com/datagrit) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

### What does Career Site Jobs Aggregator do?

Give it a list of companies and it returns their open jobs straight from each company's own applicant tracking system (ATS): Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Recruitee, Personio and Workday, all in one schema. Every posting comes with a structured salary (min, max, currency, period and yearly equivalents) whenever the employer publishes one, either in the ATS pay fields or as a pay range in the job description. It is built for recruiters, job boards, sales teams tracking hiring signals and anyone who monitors a fixed list of employers.

### Who is it for?

- **Job boards and newsletters** that aggregate roles from a curated list of companies and want only the new ones each day.
- **Recruiters and sourcers** who follow target companies and filter by title, location, remote and minimum yearly pay.
- **Sales and market research teams** that use hiring activity and salary bands as signals.
- **Compensation analysts** who need published pay ranges across many employers as numbers in one currency and period.

### Example output

| company | ats | title | workplaceType | salaryMin | salaryMax | salaryCurrency | salaryPeriod | salarySource |
|---|---|---|---|---|---|---|---|---|
| Robinhood | greenhouse | Engineering Manager, Credit Cards & Banking | null | 180000 | 270000 | USD | year | ats |
| ramp | ashby | Security Engineer, Cloud | hybrid | 211400 | 290600 | USD | year | ats |
| nvidia | workday | Growth Engineer, Developer Platform | remote | 200000 | 322000 | USD | year | description |
| Great Minds | recruitee | Project Manager | remote | 70000 | 80000 | USD | year | description |

```json
{
  "company": "nvidia",
  "companySlug": "nvidia/NVIDIAExternalCareerSite",
  "ats": "workday",
  "jobId": "JR2026245-1",
  "requisitionId": "JR2026245",
  "title": "Growth Engineer, Developer Platform",
  "locations": ["US, CA, Santa Clara", "US, Remote"],
  "workplaceType": "remote",
  "employmentType": "full_time",
  "postedAt": "2026-09-30T00:00:00.000Z",
  "url": "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite/job/US-CA-Santa-Clara/Growth-Engineer--Developer-Platform_JR2026245-1",
  "hasSalary": true,
  "salaryMin": 200000,
  "salaryMax": 322000,
  "salaryCurrency": "USD",
  "salaryPeriod": "year",
  "salaryAnnualMin": 200000,
  "salaryAnnualMax": 322000,
  "salaryCurrencies": ["USD"],
  "salarySource": "description",
  "salaryText": "200,000 USD - 322,000 USD",
  "equityMentioned": true,
  "bonusMentioned": false,
  "found": true
}
```

#### What data do you get?

Company, ATS, board URL, job id, requisition id, title, department, team, main location and all locations, workplace type (remote, hybrid, onsite), normalized employment type, posted and updated dates, posting and apply links, the salary fields above, equity and bonus flags, and optionally the plain-text description.

#### Salary normalization

Salaries come from the ATS pay fields when they exist (Greenhouse pay ranges by zone, Lever salary range, Ashby compensation tiers, Recruitee salary). Otherwise the Actor looks for an explicit pay range in the job description, such as `184,000 USD - 287,500 USD` or `$28.50 - $34.00 per hour`. A range is used only when it has a currency and either a period word or pay wording next to it (salary, pay, base, compensation, OTE); deal sizes, grants, stipends, funding and single amounts are ignored. Hourly, daily, weekly and monthly pay is converted to yearly amounts (2080 hours, 260 days, 52 weeks, 12 months). `salarySource` says where each salary came from, and **Read salary from description** turns the text reading off. SmartRecruiters postings have no salary: the list the Actor reads has no pay field and no description.

#### Several ranges and currencies

When a posting lists several ranges (pay zones, levels), the band covers all of them in one currency. With **Salary currencies** set, a US posting that also has a Canadian zone matches CAD and its salary fields are given in CAD, so **Minimum annual salary** is compared in that currency.

### How much does it cost?

You pay per posting returned. Pricing depends on your Apify plan: a small fee when a run starts, then a price per result that is lower on paid plans. The Apify free plan includes monthly credit you can use to try it. Status rows (for example when nothing matched) are never charged, and with only-new mode a scheduled run pays only for new postings. A run with no company in the input reads a small example (Ramp on Ashby, up to 10 postings) that is not charged per result. You can set a maximum spend on the run and the Actor stops when it is reached. It reads public JSON and XML feeds over plain HTTP without a browser.

### Input

1. Paste companies into **Companies**, one per line, in any form: a careers page URL (`https://ramp.com/careers`), a job board URL (`https://jobs.lever.co/epoch-ai`), a bare slug (`robinhood`), or `ats:slug` (`lever:palantir`). Workday needs the career site URL, for example `https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite`.
2. Optionally set filters: **Job title keywords** and **Exclude title keywords** (words of the title that start with the term, so engineer matches Engineering), **Departments or teams**, **Locations**, **Remote roles only**, **Employment types**, **Only postings with a salary**, **Minimum annual salary**, **Salary currencies**, **Posted within days**.
3. Turn on **Only postings new since my last run** and schedule the Actor for a daily feed of new roles.
4. Use **Maximum results per company** and **Maximum results** to bound the run, and **Include description text** when you need the full text.
5. Export the dataset as JSON, CSV or Excel, or call the Actor through the API, n8n, Make or MCP.

### Is it legal to scrape this data?

The Actor reads only the public job board feeds that these ATS vendors offer to employers for publishing open roles. It does not log in, bypass access controls or collect candidate data. Job descriptions can contain personal data such as a recruiter name, so if you request them you are responsible for handling them in line with applicable data protection law. This description is not legal advice.

### FAQ

**How does it find the ATS?** A board URL is read directly. A careers page is fetched once and the job board it links to most often is read. A bare slug is tried on Greenhouse, Lever, Ashby, Workable, Recruitee, Personio (the .de host) and SmartRecruiters in that order, and the first board with open postings wins. The run status lists which board each slug and careers page was matched to, and says when an earlier ATS had a board with that slug but no open postings. Use `ats:slug` or the board URL to pick the board yourself.

**What if a company is not found?** The run status lists companies not found, pages without a supported ATS link, boards with no open postings and boards that could not be read. If none of the companies is found, the run fails so a typo never looks like an empty result. SmartRecruiters returns an empty list both for a company without open roles and for an id that does not exist, so such entries are listed separately; if no other company is found the run fails.

**Are there limits?** Workday lists at most 2,000 postings per career site; very large Lever and SmartRecruiters boards are read up to 5,000 and 10,000 postings. Any such cut is reported in the run status. Workday needs one extra request per posting for its details, so Workday boards are slower than the others.

**How often should I run it?** Daily with only-new mode is typical; each filter combination keeps its own memory of delivered postings.

**Something looks wrong.** Open an issue with the input you used; changes at the source are fixed quickly.

### Related Actors

Greenhouse Salary Scraper and Ashby Salary Scraper from the same publisher go deeper into one ATS each. Other public-data Actors are listed on the Store profile.

# Changelog

This Actor's version history is a separate document: https://apify.com/datagrit/career-site-jobs-aggregator/changelog.md

# Actor input Schema

## `companies` (type: `array`):

One entry per company, in any of these forms: a job board URL (boards.greenhouse.io/robinhood, jobs.lever.co/palantir, jobs.ashbyhq.com/ramp, apply.workable.com/huggingface, careers.smartrecruiters.com/BoschGroup, greatminds.recruitee.com, gokarla-gmbh.jobs.personio.de, nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite), a company careers page URL (the Actor reads the page once and uses the job board it links to most often), a bare slug such as robinhood (tried on Greenhouse, Lever, Ashby, Workable, Recruitee, Personio and SmartRecruiters in that order; the first board with open postings wins), or ats:slug such as lever:palantir to skip the guessing. Workday needs the full career site URL. Companies that are not found are listed in the run status; if none is found the run fails. With no company at all the Actor runs a small free example (Ramp on Ashby, up to 10 postings, not charged per result) and says so in the run status.

## `keywords` (type: `array`):

Optional. Keep a posting when a word of its title starts with any of these terms (case-insensitive), so engineer also matches Engineering. Leave empty for all roles.

## `excludeKeywords` (type: `array`):

Optional. Drop a posting when a word of its title starts with any of these terms, for example intern or senior.

## `departments` (type: `array`):

Optional. Keep only postings whose department or team contains a word starting with one of these terms, for example engineering or sales. Workday career sites do not publish a department, so their postings never match this filter.

## `locations` (type: `array`):

Optional. Keep only postings where one of the listed locations contains a word starting with one of these terms, for example london, germany or new york. The check runs on the locations field of each result.

## `remoteOnly` (type: `boolean`):

Keep only postings with workplaceType remote. Hybrid roles are excluded. Lever, Ashby, SmartRecruiters and Recruitee publish the workplace type; for Greenhouse, Workable (postings not flagged remote), Personio and Workday it is remote when a location of the posting says Remote (Greenhouse: its main location).

## `employmentTypes` (type: `array`):

Optional. Keep only these normalized employment types: full\_time, part\_time, contract, temporary, internship, other. Greenhouse does not publish an employment type, so its postings never match this filter.

## `onlyWithSalary` (type: `boolean`):

Skip postings without a salary range, either from the ATS pay fields or (when Read salary from description is on) from a pay range written in the job description.

## `salaryFromDescription` (type: `boolean`):

When the ATS has no pay field for a posting, look for an explicit pay range in the job description (for example 184,000 USD - 287,500 USD or $28.50 - $34.00 per hour). Only ranges with a currency and a clear period are used; single amounts, bonuses, stipends and funding figures are ignored. salarySource tells you where each salary came from.

## `minAnnualSalary` (type: `integer`):

Keep only postings whose top salary, converted to a yearly amount (hourly x 2080, daily x 260, weekly x 52, monthly x 12), reaches this value. Amounts are never converted between currencies, so set Salary currencies together with this filter. 0 disables it. Postings without a yearly amount are skipped by this filter.

## `salaryCurrencies` (type: `array`):

Optional. Keep only postings with a salary range in one of these currencies (three-letter codes such as USD, CAD, GBP or EUR). A posting with ranges in several currencies matches when any of them is listed, and its salary fields are then given in that currency.

## `postedWithinDays` (type: `integer`):

Keep only postings published in the last N days, by the postedAt field. 0 disables the filter.

## `onlyNewSinceLastRun` (type: `boolean`):

Return only postings that earlier runs with the same companies and the same filters have not delivered to you. The Actor remembers the postings it actually returned, per job board and per filter combination, in a storage on your account; postings dropped by your filters or cut off by a result limit are not remembered and can still come later. Changing a filter starts a separate feed. The first run returns everything that matches.

## `includeDescription` (type: `boolean`):

Adds the plain-text job description, cut to 8000 characters. Off by default to keep the dataset small. SmartRecruiters postings have no description in the list the Actor reads, so it stays empty for them.

## `maxItemsPerCompany` (type: `integer`):

Stop reading a company after this many results, so one large employer does not use up Maximum results. 0 means no per-company limit.

## `maxItems` (type: `integer`):

Stop after this many postings in total across all companies.

## `proxyConfiguration` (type: `object`):

Optional proxy. Leave disabled: the job board APIs the Actor reads are public and need none.

## Actor input object example

```json
{
  "companies": [
    "robinhood",
    "https://jobs.lever.co/epoch-ai",
    "https://jobs.ashbyhq.com/ramp",
    "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
    "https://greatminds.recruitee.com"
  ],
  "keywords": [],
  "excludeKeywords": [],
  "departments": [],
  "locations": [],
  "remoteOnly": false,
  "employmentTypes": [],
  "onlyWithSalary": false,
  "salaryFromDescription": true,
  "minAnnualSalary": 0,
  "salaryCurrencies": [],
  "postedWithinDays": 0,
  "onlyNewSinceLastRun": false,
  "includeDescription": false,
  "maxItemsPerCompany": 10,
  "maxItems": 50,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (type: `string`):

All extracted records as a dataset.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "companies": [
        "robinhood",
        "https://jobs.lever.co/epoch-ai",
        "https://jobs.ashbyhq.com/ramp",
        "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
        "https://greatminds.recruitee.com"
    ],
    "maxItemsPerCompany": 10,
    "maxItems": 50,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("datagrit/career-site-jobs-aggregator").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "companies": [
        "robinhood",
        "https://jobs.lever.co/epoch-ai",
        "https://jobs.ashbyhq.com/ramp",
        "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
        "https://greatminds.recruitee.com",
    ],
    "maxItemsPerCompany": 10,
    "maxItems": 50,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("datagrit/career-site-jobs-aggregator").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "companies": [
    "robinhood",
    "https://jobs.lever.co/epoch-ai",
    "https://jobs.ashbyhq.com/ramp",
    "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
    "https://greatminds.recruitee.com"
  ],
  "maxItemsPerCompany": 10,
  "maxItems": 50,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call datagrit/career-site-jobs-aggregator --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,datagrit/career-site-jobs-aggregator"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/yHukcb8naNe0V6FHh/builds/UpRLcDgjd4C2C1mab/openapi.json
