# Greenhouse, Lever & Ashby Jobs API: Salary, Location, Remote (`actose/ats-career-pages-jobs`) Actor

All open jobs from Greenhouse, Lever & Ashby career pages in one format: title, department, location, remote, salary, dates, description. Auto-detects the ATS. onlyNew mode for monitoring. No API key, no login. MCP-ready for AI agents. Errors and empty results are free. $1 per 1,000 jobs.

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

## Pricing

from $0.85 / 1,000 job delivereds

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

## Greenhouse, Lever & Ashby Jobs API: Salary, Location, Remote

**$1 per 1,000 jobs. Pay only for jobs delivered: errors, unknown companies, empty boards and already-seen jobs are free.**

One Actor for the three ATS most tech companies use for their career pages. Give it a list of companies or
career page URLs. It detects whether each one runs on **Greenhouse, Lever or Ashby** and returns every open job in
**one unified JSON format**: title, department, team, locations, country, remote / hybrid / onsite, employment type,
salary range, publication date, job URL, apply URL and the full description as plain text, Markdown or HTML.

- **No API key, no login, no browser.** It reads the public JSON job-board APIs that the career pages themselves use.
  That is fast and stable, with no fragile HTML scraping.
- **Auto-detection**: `"stripe"`, `"Notion Labs"`, `https://jobs.lever.co/spotify`, `https://boards.greenhouse.io/airbnb`,
  EU boards (`job-boards.eu.greenhouse.io`, `jobs.eu.lever.co`), or a company career page that embeds one of these ATS.
- **`onlyNew` mode** for scheduled monitoring: each run returns only the jobs published since the last run, and you
  are billed only for those.
- **Unified salary fields** (`salaryMin`, `salaryMax`, `salaryCurrency`, `salaryInterval`) from the structured ATS
  data (Ashby, Lever). When the ATS has no salary field, they are parsed from the pay-transparency text in the description
  (Greenhouse).
- **Filters before billing**: title keywords, excluded keywords, location, department, remote only, employment type,
  posted within N days.
- **MCP-ready** for Claude, Cursor, ChatGPT and other AI agents. The descriptions are clean text or Markdown, ready for LLMs and RAG.

### How much does it cost?

Pay per event: **$0.001 per job delivered** (`job-result` event). No monthly rental.

| You get | You pay |
| --- | --- |
| 1 company with 100 open jobs | $0.10 |
| 1,000 jobs | **$1.00** |
| 50 companies × 200 jobs = 10,000 jobs | $10.00 |
| Daily `onlyNew` watch of 50 companies (~30 new jobs/day) | ≈ $0.90 / month |

Free (never billed): companies not found, boards with no open job, network errors, jobs removed by your filters,
duplicates (the same board given twice), and jobs already delivered in a previous run when `onlyNew` is on.
Set **Maximum cost per run** in Apify Console: the Actor stops cleanly when the limit is reached.

### Input

```json
{
  "companies": ["stripe", "https://jobs.lever.co/spotify", "https://jobs.ashbyhq.com/notion", "Société Générale"],
  "platform": "auto",
  "onlyNew": false,
  "titleKeywords": ["engineer", "data"],
  "excludeTitleKeywords": ["intern"],
  "locations": ["London", "Remote"],
  "remoteOnly": false,
  "postedWithinDays": 30,
  "descriptionFormat": "text",
  "maxJobsPerCompany": 0
}
```

| Field | What it does |
| --- | --- |
| `companies` | Slugs, company names, ATS board URLs, career page URLs, or `greenhouse:slug` / `lever:slug` / `ashby:slug`. |
| `platform` | `auto` (default), or force `greenhouse`, `lever`, `ashby`. |
| `onlyNew` | Return only jobs not delivered by a previous run with the same company list. |
| `stateKey` | Optional name of the onlyNew memory (keep it when you edit the list). |
| `titleKeywords`, `excludeTitleKeywords` | Title filters (case and accent insensitive). |
| `locations`, `departments` | "Contains any of" filters on locations/offices/country and department/team. |
| `remoteOnly`, `employmentTypes`, `postedWithinDays` | Remote, `full-time` / `part-time` / `contract` / `internship` / `temporary`, recency. |
| `descriptionFormat` | `text` (default), `markdown`, `html`, `all`, or `none` (lightest). |
| `maxJobsPerCompany`, `maxTotalJobs` | Caps (newest jobs first). 0 = no cap. |

### Output

One dataset item per job, with the same fields whatever the ATS:

```json
{
  "platform": "ashby",
  "companySlug": "notion",
  "companyName": "Notion",
  "sourceInput": "https://jobs.ashbyhq.com/notion",
  "jobId": "1fc309c8-da20-4ff2-84c7-8b863ece2b0a",
  "title": "Software Engineer, AI Connections (Developer Platform)",
  "department": "Engineering",
  "team": "Engineering",
  "location": "San Francisco, California",
  "locations": ["San Francisco, California", "New York, New York"],
  "offices": [],
  "country": "United States",
  "workplaceType": "hybrid",
  "isRemote": true,
  "employmentType": "full-time",
  "salaryMin": 200000,
  "salaryMax": 290000,
  "salaryCurrency": "USD",
  "salaryInterval": "year",
  "salaryText": "$200K – $290K",
  "salarySource": "structured",
  "publishedAt": "2026-08-24T14:44:49Z",
  "updatedAt": null,
  "requisitionId": null,
  "jobUrl": "https://jobs.ashbyhq.com/notion/1fc309c8-da20-4ff2-84c7-8b863ece2b0a",
  "applyUrl": "https://jobs.ashbyhq.com/notion/1fc309c8-da20-4ff2-84c7-8b863ece2b0a/application",
  "boardUrl": "https://jobs.ashbyhq.com/notion",
  "language": null,
  "descriptionText": "WHO WE ARE\n\nNotion is the collaborative AI workspace...",
  "descriptionMarkdown": null,
  "descriptionHtml": null,
  "descriptionLength": 4210,
  "openJobsOnBoard": 148,
  "isNew": null,
  "scrapedAt": "2026-10-19T08:00:00Z"
}
```

Export as JSON, CSV, Excel or HTML from the dataset, or read it through the API (`?view=overview` gives a compact table).

### Use cases

#### 1. Recruiting agency: new engineering jobs every morning in Google Sheets

A tech recruiter tracks 40 scale-ups that hire engineers in Europe. A daily schedule with `onlyNew` sends only the
jobs opened since yesterday to a Google Sheet, and the team calls the hiring managers the same day.

```json
{"companies": ["stripe", "airbnb", "notion", "https://jobs.lever.co/spotify", "datadog", "ramp"],
 "onlyNew": true, "titleKeywords": ["engineer", "developer", "sre"], "locations": ["London", "Paris", "Berlin", "Dublin", "Remote"]}
```

```json
[{"companyName": "Stripe", "title": "Backend Engineer, Payments", "location": "Dublin", "publishedAt": "2026-10-18T13:02:00Z", "isNew": true, "jobUrl": "https://stripe.com/jobs/search?gh_jid=..."}]
```

Typical volume is 10 to 40 new jobs a day, so **about $1 a month**.

#### 2. Sales team: hiring signals for lead generation

A B2B SaaS sales rep sells a data tool. Companies that open "Data Engineer" or "Analytics" roles are warm leads. Every
Monday the run feeds the CRM with `companyName`, `title`, `department` and `publishedAt`.

```json
{"companies": ["airbnb", "notion", "figma", "linear", "palantir"], "titleKeywords": ["data engineer", "analytics", "data platform"], "postedWithinDays": 7, "descriptionFormat": "none"}
```

```json
[{"companyName": "Airbnb", "title": "Senior Data Engineer, Payments", "department": "Engineering", "publishedAt": "2026-10-15T17:40:00Z"}]
```

With `descriptionFormat: "none"` the run is lighter. Five companies cost a few cents a week.

#### 3. Compensation analyst / job board: salary benchmarks

An HR analyst builds pay benchmarks from published salary ranges (US pay-transparency laws). They pull all jobs from 100
companies once a month and pivot `salaryMin` / `salaryMax` by `title`, `country` and `employmentType`.

```json
{"companies": ["stripe", "openai", "notion", "ramp", "spotify"], "locations": ["United States", "US", "New York", "San Francisco"], "descriptionFormat": "none"}
```

```json
[{"companyName": "Openai", "title": "Research Engineer", "salaryMin": 295000, "salaryMax": 440000, "salaryCurrency": "USD", "salaryInterval": "year", "salarySource": "structured"}]
```

About 5,000 jobs a month cost **$5**.

#### 4. AI agent: "Which remote ML roles are open at these companies?"

An AI assistant calls the Actor through MCP with `remoteOnly: true` and `descriptionFormat: "markdown"`, then summarizes the
requirements of each job for the user.

### API examples

**Python**

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("<username>/ats-career-pages-jobs").call(run_input={
    "companies": ["stripe", "https://jobs.lever.co/spotify", "notion"],
    "titleKeywords": ["engineer"],
    "onlyNew": True,
})
for job in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(job["companyName"], job["title"], job["location"], job["jobUrl"])
```

**JavaScript**

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('<username>/ats-career-pages-jobs').call({
    companies: ['stripe', 'https://jobs.ashbyhq.com/openai'],
    remoteOnly: true,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map((j) => `${j.companyName} | ${j.title} | ${j.salaryMin ?? ''}-${j.salaryMax ?? ''}`));
```

### Integrations (Make, n8n, Zapier, Google Sheets)

- **Make / Zapier**: module "Apify → Run an Actor" (daily schedule, input with `"onlyNew": true`), then "Apify → Get dataset
  items", then "Google Sheets → Add a row" or "Slack → Send message" for each new job.
- **n8n**: Schedule Trigger → Apify node (Run Actor and get dataset items) → filter on `salaryMin` → Google Sheets / Airtable / Notion.
- **Apify schedules + webhooks**: schedule the Actor in Apify Console and send each run's dataset to your own endpoint.
- **Direct links**: the dataset is available as JSON, CSV or Excel at
  `https://api.apify.com/v2/datasets/<datasetId>/items?format=csv`.

### Use with AI agents (MCP)

The Actor works with the Apify MCP server (`https://mcp.apify.com`). Add it to Claude Desktop, Claude Code, Cursor, ChatGPT or
any MCP client, then ask for example: *"List the remote data engineering jobs open at Stripe, Notion and Spotify, with
their salary ranges."* The input is plain (company names or URLs), the output is flat JSON with self-explanatory camelCase
fields, and the descriptions come as plain text or Markdown, ready for an LLM or a RAG index.

### FAQ

**How do I find a company's slug?** Open its career page and look at a job link: `boards.greenhouse.io/<slug>`,
`jobs.lever.co/<slug>`, `jobs.ashbyhq.com/<slug>`. You can also paste the career page URL: the Actor looks for the ATS
link in the page. If there is none, it tries the domain name as the slug.

**Does it need a proxy or an account?** No. These are public job-board APIs, read directly with polite delays and retries.

**How does `onlyNew` work?** The IDs of delivered jobs are kept in a named key-value store
(`ats-career-pages-jobs-state`), with one memory per company list (or per `stateKey`). The first run delivers everything.
Later runs deliver only unseen jobs. Closed jobs are dropped from the memory automatically.

**Why is a salary missing?** Many companies do not publish one. Ashby and Lever provide structured ranges when the
company fills them in. For Greenhouse the range is parsed from the description text, and `salarySource` tells you which
source was used.

**Is `companyName` always the official name?** Greenhouse provides it. For Lever and Ashby, the public API has no company
name, so it is your input text if you typed a name, otherwise the slug in title case (e.g. "Openai").

**What if a company is not found?** Nothing is billed. The log and the run status list the inputs not found.

### Limits

- Only **Greenhouse, Lever and Ashby** are supported. Workday, SmartRecruiters, Workable and others are not.
- Only what the company publishes on its public board: unlisted or internal jobs are not visible.
- Lever has no "updated" date. Greenhouse has no structured country, remote flag or salary unless the company adds them
  (these fields are then inferred from the location and description, or left `null`).
- Salary parsing from free text is best-effort: always check `salaryText`.
- Auto-detection with a bare name tries the obvious slugs. When a name is ambiguous, give the exact board URL or force the platform.
- Very large boards (1,000+ jobs with descriptions) take longer to download. Use `descriptionFormat: "none"` for lists only.

# Actor input Schema

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

One per line. Accepts a board slug ("stripe", "notion"), a company name ("Notion Labs"), a job board URL ("https://boards.greenhouse.io/airbnb", "https://jobs.lever.co/spotify", "https://jobs.ashbyhq.com/openai", EU boards too), a company career page that embeds one of these ATS ("https://www.example.com/careers"), or a forced platform with "greenhouse:stripe", "lever:spotify", "ashby:notion". The platform is detected automatically.

## `platform` (type: `string`):

Leave on Auto to detect Greenhouse, Lever or Ashby for each company. Force a platform only if a slug exists on several of them.

## `onlyNew` (type: `boolean`):

For scheduled runs: return (and bill) only jobs that were not delivered by a previous run with the same list of companies. The first run delivers all open jobs and remembers them.

## `stateKey` (type: `string`):

Name of the memory used by onlyNew (letters, digits, dashes). By default one memory per list of companies. Set a name to keep the same memory when you edit the list.

## `titleKeywords` (type: `array`):

Keep only jobs whose title contains at least one of these words (case and accent insensitive), e.g. "engineer", "data".

## `excludeTitleKeywords` (type: `array`):

Drop jobs whose title contains one of these words, e.g. "senior", "intern".

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

Keep only jobs whose location, office or country contains one of these texts, e.g. "London", "Germany", "Remote".

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

Keep only jobs whose department or team contains one of these texts, e.g. "Engineering", "Sales".

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

Keep only jobs marked remote by the ATS or with "Remote" in their location.

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

Keep only these normalized types: full-time, part-time, contract, internship, temporary. Empty = all.

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

Keep only jobs published in the last N days (0 = no date filter).

## `descriptionFormat` (type: `string`):

Plain text (default, LLM-friendly), Markdown, raw HTML, all three, or none (lighter and faster; salary is then only taken from structured ATS fields).

## `maxJobsPerCompany` (type: `integer`):

Newest jobs first. 0 = all open jobs.

## `maxTotalJobs` (type: `integer`):

Stop after this many jobs across all companies (0 = no limit). Your run's maximum cost is always respected too.

## Actor input object example

```json
{
  "companies": [
    "stripe",
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/notion"
  ],
  "platform": "auto",
  "onlyNew": false,
  "titleKeywords": [],
  "remoteOnly": false,
  "postedWithinDays": 0,
  "descriptionFormat": "text",
  "maxJobsPerCompany": 0,
  "maxTotalJobs": 0
}
```

# Actor output Schema

## `jobs` (type: `string`):

No description

## `jobsJson` (type: `string`):

No description

## `jobsCsv` (type: `string`):

No description

# 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": [
        "stripe",
        "https://jobs.lever.co/spotify",
        "https://jobs.ashbyhq.com/notion"
    ],
    "titleKeywords": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("actose/ats-career-pages-jobs").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": [
        "stripe",
        "https://jobs.lever.co/spotify",
        "https://jobs.ashbyhq.com/notion",
    ],
    "titleKeywords": [],
}

# Run the Actor and wait for it to finish
run = client.actor("actose/ats-career-pages-jobs").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": [
    "stripe",
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/notion"
  ],
  "titleKeywords": []
}' |
apify call actose/ats-career-pages-jobs --silent --output-dataset

```

## MCP server setup

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

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/Ot6XmfZmbN2HH09Rw/builds/QmfDKp2p3KSPqHR88/openapi.json
