# Greenhouse, Lever & Ashby Job Board API – Jobs Scraper (`opentenders/ats-jobs-scraper`) Actor

Every published job from any company's Greenhouse, Lever or Ashby job board via the official job board APIs (including the Lever Postings API): title, department, location, remote, salary, description, apply link and publish date. Filter by keyword, location, remote and posted-after date.

- **URL**: https://apify.com/opentenders/ats-jobs-scraper.md
- **Developed by:** [Giordano Horacio](https://apify.com/opentenders) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.50 / 1,000 job founds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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 Job Board API – Jobs Scraper with Date Filter

Pull **every published job** from any company that runs its careers page on **Greenhouse, Lever or Ashby**, straight from the official job board APIs: the Greenhouse Job Board API, the Lever Postings API and the Ashby job board API. One request per company, no browser, no proxies, no login. Filter by keyword, location, remote and **posted-after date** and get clean JSON/CSV with title, department, location, workplace type, salary (when the company publishes it), full description and apply link.

Built for **recruiters, sourcers, job aggregators, sales teams tracking hiring signals and labor-market researchers** who need the same boards checked every day without failed runs.

### Why this one

- **Reliability first.** The three ATS vendors publish these job-board APIs for exactly this purpose (Lever's docs state that published postings "may be scraped by third parties"). No HTML parsing that breaks when a careers page is redesigned. Each board is one JSON request with retries; an unknown slug produces an explanatory error row and the run continues with the other companies.
- **Date filter that works.** `postedAfter` uses the ATS's own publish timestamp (Greenhouse `first_published`, Lever `createdAt`, Ashby `publishedAt`), so a daily schedule with "yesterday" returns only new openings.
- **One schema for three ATS.** Same field names whether the job comes from Greenhouse, Lever or Ashby. `ats` tells you the source.
- **Salary when it exists.** Ashby compensation ranges and Lever salary ranges are normalized into `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryInterval`.
- **Health-checked daily** against three live boards, so breaks are caught before you notice.

### Pricing

Pay per job returned, no subscription: **$0.50 per 1,000 jobs** (event `job`). Error rows, empty runs and run starts are never charged. Cap your spend with `maxItems` and the run's *Maximum total charge*. Apify's free plan ($5 monthly credit) covers about 10,000 jobs per month.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `companies` | array | required | Board URLs (`https://boards.greenhouse.io/stripe`, `https://jobs.lever.co/palantir`, `https://jobs.ashbyhq.com/linear`) or `ats:slug` (`greenhouse:stripe`, `lever:palantir`, `ashby:linear`). The slug is the last part of the company's careers page URL. |
| `keyword` | string | | Case-insensitive substring of the title, e.g. `engineer`. |
| `keywordInDescription` | boolean | `false` | Also match the keyword in the description. |
| `location` | string | | Substring of any job location or country, e.g. `London`, `United States`, `Remote`. |
| `remoteOnly` | boolean | `false` | Keep only remote jobs. |
| `postedAfter` | string | | `YYYY-MM-DD`. Only jobs first published on or after this date. |
| `includeHtml` | boolean | `false` | Add `descriptionHtml` next to `descriptionText`. |
| `maxItems` | integer | `1000` | Stop after this many jobs. |
| `minResults` | integer | | Monitoring only: fail the run if fewer jobs are returned. |

Daily hiring-signal monitor for a watchlist:

```json
{
    "companies": ["greenhouse:stripe", "greenhouse:cloudflare", "lever:palantir", "ashby:ramp", "ashby:linear"],
    "postedAfter": "2026-10-25",
    "maxItems": 5000
}
```

Remote engineering roles only:

```json
{
    "companies": ["https://boards.greenhouse.io/cloudflare", "https://jobs.lever.co/veeva"],
    "keyword": "engineer",
    "remoteOnly": true
}
```

### Output

One row per job:

```json
{
    "ats": "ashby",
    "company": "ramp",
    "companyName": null,
    "jobId": "d1183b00-6590-4fe4-a585-28d84e578fe3",
    "title": "Software Engineer, Security",
    "department": "Engineering",
    "team": "Backend",
    "location": "New York, NY (HQ)",
    "locations": ["New York, NY (HQ)", "San Francisco, CA", "Remote (US)"],
    "country": "USA",
    "workplaceType": "remote",
    "employmentType": "FullTime",
    "publishedAt": "2026-05-13T20:28:09.574+00:00",
    "updatedAt": null,
    "url": "https://jobs.ashbyhq.com/ramp/d1183b00-6590-4fe4-a585-28d84e578fe3",
    "applyUrl": "https://jobs.ashbyhq.com/ramp/d1183b00-6590-4fe4-a585-28d84e578fe3/application",
    "salaryMin": 189000,
    "salaryMax": 330000,
    "salaryCurrency": "USD",
    "salaryInterval": "1 YEAR",
    "salarySummary": "$189K - $330K",
    "descriptionText": "Ramp is a financial operations platform...",
    "scrapedAt": "2026-10-26T13:42:19.902Z"
}
```

`workplaceType` is `remote`, `hybrid`, `onsite` or `null` when the ATS does not say. `updatedAt` exists for Greenhouse only. `companyName` is filled when the ATS provides it (Greenhouse). Fields that an ATS does not expose are `null`, never invented.

A board that cannot be found produces one row like `{ "error": true, "errorType": "BOARD_NOT_FOUND", "message": "...", "company": "greenhouse:typo" }` and is not charged. A run with no matching jobs produces one `NO_RESULTS` row.

### Use it from code

```bash
curl -X POST "https://api.apify.com/v2/acts/opentenders~ats-jobs-scraper/run-sync-get-dataset-items?format=json" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"companies":["greenhouse:stripe","ashby:linear"],"keyword":"engineer","postedAfter":"2026-10-01"}'
```

Works with the Apify API, clients, MCP server, Make, Zapier, n8n and Google Sheets integrations.

### Limitations

- Only companies on Greenhouse, Lever or Ashby. Workday, iCIMS, SmartRecruiters and company-built career sites are not covered.
- Lever and Ashby do not expose an "updated at" timestamp; `postedAfter` uses first publication.
- Greenhouse descriptions arrive as HTML; the plain-text version is derived from it. Ashby and Lever provide plain text natively.
- Ashby boards can be large (10+ MB); the default 512 MB memory handles them.

### FAQ

**How do I find a company's slug?** Open its careers page. `boards.greenhouse.io/<slug>`, `jobs.lever.co/<slug>` or `jobs.ashbyhq.com/<slug>`. Paste the URL or `ats:slug`.

**Can I monitor hundreds of companies?** Yes, one request per board, four in parallel. 300 boards take a couple of minutes.

**Does it include recruiter names or e-mails?** No. Only the job posting data the company publishes.

**Something broke?** Open an issue with the run ID. The three APIs rarely change, and the actor fails loudly with the ATS name and the unexpected response instead of returning wrong data.

# Actor input Schema

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

One per line. A board URL (<code>https://boards.greenhouse.io/stripe</code>, <code>https://jobs.lever.co/palantir</code>, <code>https://jobs.ashbyhq.com/linear</code>) or <code>ats:slug</code> (<code>greenhouse:stripe</code>, <code>lever:palantir</code>, <code>ashby:linear</code>). The slug is the last part of the company's careers page URL.

## `keyword` (type: `string`):

Case-insensitive substring of the job title, e.g. <code>engineer</code>. Empty = all jobs.

## `keywordInDescription` (type: `boolean`):

Match the keyword against the job description too, not only the title.

## `location` (type: `string`):

Case-insensitive substring of any of the job's locations or country, e.g. <code>London</code>, <code>United States</code>, <code>Remote</code>.

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

Keep only jobs whose workplace type is remote.

## `postedAfter` (type: `string`):

Only jobs first published on or after this date. Set it to yesterday in a daily schedule to get only new openings. Uses Greenhouse first_published, Lever createdAt and Ashby publishedAt.

## `includeHtml` (type: `boolean`):

Add the original HTML description next to the plain-text one.

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

Stop after this many jobs. Caps your cost.

## `minResults` (type: `integer`):

Optional. Fail the run if fewer jobs than this are returned, so a scheduled task alerts you when an ATS API changes.

## Actor input object example

```json
{
  "companies": [
    "greenhouse:stripe",
    "lever:palantir",
    "ashby:linear"
  ],
  "keywordInDescription": false,
  "remoteOnly": false,
  "includeHtml": false,
  "maxItems": 1000
}
```

# Actor output Schema

## `results` (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": [
        "greenhouse:stripe",
        "lever:palantir",
        "ashby:linear"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("opentenders/ats-jobs-scraper").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": [
        "greenhouse:stripe",
        "lever:palantir",
        "ashby:linear",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("opentenders/ats-jobs-scraper").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": [
    "greenhouse:stripe",
    "lever:palantir",
    "ashby:linear"
  ]
}' |
apify call opentenders/ats-jobs-scraper --silent --output-dataset

```

## MCP server setup

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

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/yT5RAggI65Kg4ey2x/builds/EIi4hIjzxjeouJ7f0/openapi.json
