# Greenhouse + Lever + Ashby Jobs Scraper (Only New Jobs) (`keepable_injera/greenhouse-lever-ashby-jobs-scraper`) Actor

Only new jobs since last run. Greenhouse + Lever + Ashby in one. Collects public job postings, filters by keyword and location, and links to each original posting.

- **URL**: https://apify.com/keepable_injera/greenhouse-lever-ashby-jobs-scraper.md
- **Developed by:** [Chee Chiaw Lim](https://apify.com/keepable_injera) (community)
- **Categories:** Jobs
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$2.00 / 1,000 jobs

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 Jobs Scraper (Only New Jobs)

### What it does

Collect only new jobs since the last run, with Greenhouse + Lever + Ashby in one Actor. Each job uses the same output fields and links to the original posting. You can filter jobs by title or department keywords and location.

### How only new works

`onlyNew` defaults to `true`. The Actor saves seen job IDs in the named key-value store `job-aggregator-state`, under the `SEEN` key. Each entry maps a `source:slug` key to an array of job IDs.

On the first run, all jobs are new. Later runs push only IDs absent from the saved state. Changes to an existing job do not make it new. Set `onlyNew` to `false` to push matching jobs again.

Seen IDs include processed jobs that do not match your filters, so widening filters later does not make those jobs new. Jobs left unprocessed when the charge limit stops a run remain eligible for a later run. Deleting `SEEN` resets the history. State is per Apify account and shared by runs that use this named store. Avoid overlapping runs that update the same state.

### Input fields

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `greenhouse` | Array of strings | `["dropbox"]` | Greenhouse board slugs. Use `[]` to skip Greenhouse. |
| `lever` | Array of strings | `["binance"]` | Lever board slugs. Use `[]` to skip Lever. |
| `ashby` | Array of strings | `["linear"]` | Ashby board slugs. Use `[]` to skip Ashby. |
| `keywords` | Array of strings | `[]` | Match any keyword in the title or department, ignoring case. Empty means all jobs. The input form prefills `["engineer"]`. |
| `locations` | Array of strings | `[]` | Match any term within the location or exactly against the workplace type, ignoring case. Empty means all locations. The input form prefills `["remote"]`. |
| `onlyNew` | Boolean | `true` | Push only IDs absent from saved `SEEN` state. |

When both filters are set, a job must match a keyword and a location term. Supply board slugs rather than URLs. Duplicate slugs within a source are fetched once.

### Input examples

#### All new jobs across all three sources

```json
{
  "greenhouse": ["dropbox"],
  "lever": ["binance"],
  "ashby": ["linear"],
  "keywords": [],
  "locations": [],
  "onlyNew": true
}
```

#### Keyword and remote filter

```json
{
  "greenhouse": ["dropbox"],
  "lever": ["binance"],
  "ashby": ["linear"],
  "keywords": ["engineer"],
  "locations": ["remote"],
  "onlyNew": true
}
```

#### One source only

```json
{
  "greenhouse": [],
  "lever": [],
  "ashby": ["linear"],
  "keywords": [],
  "locations": [],
  "onlyNew": true
}
```

### Output schema

The default dataset contains one item per pushed job, with these fields.

- `source`: `greenhouse`, `lever`, or `ashby`.
- `company`: Company name from the board when available, otherwise derived from its slug.
- `jobId`: Source job ID as a string.
- `title`: Job title with normalized whitespace.
- `location`: Location text, or an empty string when unavailable.
- `remote`: `true` for remote, `false` for hybrid or onsite, or `null` when unknown.
- `workplaceType`: `remote`, `hybrid`, `onsite`, or `null` when unknown.
- `department`: Department or team text, or an empty string when unavailable.
- `salary`: An object with `min`, `max`, `currency`, and `interval`, or `null` when unavailable. Individual values can be `null`. Intervals are `hour`, `day`, `week`, `month`, or `year`.
- `postedAt`: Publication timestamp in ISO 8601 format, or `null`.
- `updatedAt`: Update timestamp in ISO 8601 format, or `null`. Ashby falls back to its publication timestamp.
- `url`: Link to the original job posting.

This sample is the normalized output of a Lever fixture in `test/fixtures/lever.json`.

```json
{
  "source": "lever",
  "company": "Match Group",
  "jobId": "69396299-e587-4063-aef6-0ce2fd66e9ee",
  "title": "AI Product Engineer",
  "location": "Vancouver, British Columbia",
  "remote": false,
  "workplaceType": "hybrid",
  "department": "Match Group",
  "salary": {
    "min": 35,
    "max": 38,
    "currency": "CAD",
    "interval": "hour"
  },
  "postedAt": "2026-09-18T16:14:22.697Z",
  "updatedAt": null,
  "url": "https://jobs.lever.co/matchgroup/69396299-e587-4063-aef6-0ce2fd66e9ee"
}
```

### RUN_SUMMARY record

The default key-value store contains a `RUN_SUMMARY` record for each run. It includes `startedAt`, `finishedAt`, `onlyNew`, `filters`, `totals`, `companies`, and `warnings`.

`totals` contains `fetched`, `afterFilter`, and `pushed` counts. `afterFilter` counts matching jobs before seen-ID and duplicate checks. Each company entry includes its source, slug, company name, counts, and status. Status is `ok`, `empty`, `not_found`, or `failed`. Failed entries also include an error message. Counts cover companies processed before the run stops.

`stoppedAtChargeLimit` is `true` when the maximum charge limit stops processing. Already pushed jobs and their seen IDs are saved.

### Pricing

The price is $0.002 per job pushed to the dataset ($2.00 per 1,000 jobs). There is no per-run fee. You are only charged for jobs pushed, using the `job` event. Jobs skipped by filters or seen-ID checks incur no event charge. The run stops when your max charge limit is reached.

### Limits

- Only public job boards are supported.
- Requests are limited to 1 request per second per host. Rate-limit responses and server errors are retried up to three times.
- Unlisted Ashby jobs are skipped. Jobs without an ID or original posting URL are also skipped.
- Bad slugs produce warnings. Empty boards also produce warnings and may indicate a bad slug or no openings.
- State is per Apify account, rather than per task or input configuration.
- Available fields depend on the source API. Salary and timestamps can be missing.

### Disclaimer

This Actor is unofficial and is not affiliated with or endorsed by Greenhouse, Lever, or Ashby. It uses only their documented public job-board APIs. Every item links to the original posting.

### Local development

```sh
npm install
npm test
npm start
```

Local runs use the Apify SDK's local storage. The integration tests run the Actor with fixture responses and isolated temporary storage.

# Actor input Schema

## `greenhouse` (type: `array`):

Greenhouse public job-board slugs to fetch. Use an empty array to skip this source.

## `lever` (type: `array`):

Lever public job-board slugs to fetch. Use an empty array to skip this source.

## `ashby` (type: `array`):

Ashby public job-board slugs to fetch. Use an empty array to skip this source.

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

Include jobs whose title or department contains any keyword, ignoring case. Leave empty to include all.

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

Include jobs whose location contains any term or whose workplace type matches a term, ignoring case. Use remote to include remote jobs. Leave empty to include all locations.

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

Push only job IDs not seen in previous runs. Uses saved state in the named key-value store job-aggregator-state under SEEN. The first run treats all jobs as new.

## Actor input object example

```json
{
  "greenhouse": [
    "dropbox"
  ],
  "lever": [
    "binance"
  ],
  "ashby": [
    "linear"
  ],
  "keywords": [
    "engineer"
  ],
  "locations": [
    "remote"
  ],
  "onlyNew": true
}
```

# Actor output Schema

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

New jobs pushed in this run

## `runSummary` (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 = {
    "greenhouse": [
        "dropbox"
    ],
    "lever": [
        "binance"
    ],
    "ashby": [
        "linear"
    ],
    "keywords": [
        "engineer"
    ],
    "locations": [
        "remote"
    ],
    "onlyNew": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("keepable_injera/greenhouse-lever-ashby-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 = {
    "greenhouse": ["dropbox"],
    "lever": ["binance"],
    "ashby": ["linear"],
    "keywords": ["engineer"],
    "locations": ["remote"],
    "onlyNew": True,
}

# Run the Actor and wait for it to finish
run = client.actor("keepable_injera/greenhouse-lever-ashby-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 '{
  "greenhouse": [
    "dropbox"
  ],
  "lever": [
    "binance"
  ],
  "ashby": [
    "linear"
  ],
  "keywords": [
    "engineer"
  ],
  "locations": [
    "remote"
  ],
  "onlyNew": true
}' |
apify call keepable_injera/greenhouse-lever-ashby-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,keepable_injera/greenhouse-lever-ashby-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/dduLxomhjFSRUMKIu/builds/co24C1ckHne58LuHW/openapi.json
