# Greenhouse, Lever & Ashby Jobs Scraper — Hiring Signals (`api_creators/greenhouse-lever-ashby-jobs-scraper`) Actor

Scrape Greenhouse, Lever US/EU and Ashby jobs with full descriptions, published pay and change tracking. Workflow templates included. Job-check fees plus platform usage.

- **URL**: https://apify.com/api_creators/greenhouse-lever-ashby-jobs-scraper.md
- **Developed by:** [API Creators](https://apify.com/api_creators) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.45 / 1,000 successful job checks

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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 Scraper — Hiring Signals

Collect jobs from supported Greenhouse, Lever and Ashby employer boards in one consistent format. Get full job descriptions, published salary labels, locations and source links for recruiting research, job aggregation and account research. Compare repeat observations to track newly observed, changed and no-longer-listed jobs.

This scraper reads public webpages through Apify Proxy or your own HTTP/HTTPS proxies. No ATS API key or paid data service is required. It is an independent tool and is not affiliated with Greenhouse, Lever or Ashby.

**Pricing starts at $1.50 per 1,000 successful unique job checks, plus Apify platform/proxy usage and the small start fee below.** Checks are billable even when unchanged or excluded by filters. This is a checked-job fee, not a charge only for output rows.

| Apify plan | Per 1,000 successful checks | Per Actor start |
|---|---:|---:|
| Free | $1.50 | $0.00005 |
| Starter | $1.49 | $0.00005 |
| Scale | $1.47 | $0.000049 |
| Business | $1.45 | $0.000049 |

Consult this Actor's live Pricing tab for the applicable current rate. Successful checks can bill in a partial or subsequently failed run; failed parses are not successful job checks. The billing section below explains receipts and caps.

### Get your first results

1. In **Input**, replace the example under **Job board or job page URLs** with your supported employer URL. To try the scraper first, keep the small Lever example below.
2. Leave **Output mode** on **Current jobs (snapshot)** and keep **Apify Proxy** selected. Leave optional sections unchanged.
3. Click **Start**. Open **Output → Job results** when the run finishes.
4. Open **Completeness report** in the output selector. Check `status: "COMPLETE"`; platform success alone can also contain a partial preview.
5. Review the **Jobs** table, then use **Export** for JSON or CSV. For spreadsheets, select **Spreadsheet export** to use formula-safe derived text columns.

Copyable starter input:

```json
{
  "targets": [{ "url": "https://jobs.lever.co/basis" }],
  "mode": "snapshot",
  "maxResults": 200,
  "maxRequests": 500,
  "proxyConfiguration": { "useApifyProxy": true }
}
```

The example is a public demonstration board, not a preset customer target. Its jobs can change. Targets accept URL strings or objects with `url` and optional `companyLabel`. A company label is your own annotation; it does not replace the employer name published on the page.

### Supported Greenhouse, Lever and Ashby URLs

| Provider | Employer board | Individual job |
|---|---|---|
| Greenhouse jobs scraper | `https://job-boards.greenhouse.io/BOARD` or `https://job-boards.greenhouse.io/embed/job_board?for=BOARD` | `https://job-boards.greenhouse.io/BOARD/jobs/ID` |
| Lever US jobs scraper | `https://jobs.lever.co/BOARD` | `https://jobs.lever.co/BOARD/UUID` |
| Lever EU jobs scraper | `https://jobs.eu.lever.co/BOARD` | `https://jobs.eu.lever.co/BOARD/UUID` |
| Ashby jobs scraper | `https://jobs.ashbyhq.com/BOARD` | `https://jobs.ashbyhq.com/BOARD/UUID` |

For Greenhouse, replace `BOARD` with the employer's known public board token. Ordinary modern board roots (including a trailing slash) normalize to the same token-free public frame before fetching. For example, token `greenhouse` becomes `https://job-boards.greenhouse.io/embed/job_board?for=greenhouse`. The scraper does not discover that token from a company website. Other demonstration boards are `https://jobs.lever.co/basis` and `https://jobs.ashbyhq.com/Ashby`.

**Supported scope:** the verified public Greenhouse embed/detail layout, current single-page Lever boards, and Ashby pages containing the complete public job array. Corporate career wrappers, automatic employer discovery, legacy Greenhouse hosts, Greenhouse EU hosts and unknown pagination layouts are outside this release. Lever EU uses a distinct identity/scope namespace, so it cannot be merged with a same-name US board or close its jobs. Use the exact supported forms above. Every employer variation has not been validated; unrecognized or incomplete pages cannot advance a monitor.

### What you get

| Fields | How to use them |
|---|---|
| `jobKey`, `provider`, `board`, `sourceId` | Stable source identity. Upsert current jobs by `jobKey`. |
| `title`, `companyName`, `department`, `employmentType` | Published role and employer metadata when available. |
| `locationText`, `locations`, `workplaceType` | Published location and job-level workplace labels. |
| `descriptionText`, `descriptionHtml` | Full readable prose and sanitized formatting. |
| `compensationRaw`, `compensationRanges`, `compensationComponents` | Published pay labels and unambiguous ranges, with source context. |
| `sourceUrl`, `applyUrl`, `provenance`, `warnings` | Source links, field provenance and interpretation warnings. An application link is not an application submission. |
| `sourceDatePosted`, `firstSeenAt`, `lastSeenAt` | Source-provided date metadata versus this monitor's own observation times. |
| `changeType`, `changedFields`, `eventId`, `snapshotStatus` | Snapshot/change interpretation, downstream deduplication and completeness. |

An excerpt from a real Greenhouse job observed on **28 October 2026** (not the full record):

```json
{
  "jobKey": "greenhouse:greenhouse:8204473",
  "provider": "greenhouse",
  "title": "Senior Software Engineer",
  "companyName": "Greenhouse",
  "workplaceType": null,
  "sourceDatePosted": null,
  "compensationRanges": [{
    "min": 117500,
    "max": 164500,
    "currency": "CAD",
    "interval": null,
    "component": "base_salary"
  }],
  "sourceUrl": "https://job-boards.greenhouse.io/greenhouse/jobs/8204473",
  "snapshotStatus": "COMPLETE"
}
```

Missing source fields remain `null` or empty arrays. Published OTE (on-target earnings), base pay, commission and equity labels are preserved; they are not interchangeable. Dollar-only text does not establish USD, and a company being remote-first does not classify every job as remote. Ambiguous mixed ranges or unsupported number formats stay raw. Source posting dates are unverified metadata; `firstSeenAt` is not a posting date. JSON retains full nested pay/provenance and original source strings. The table's readable pay column is a derived text presentation.

### Filter jobs and export to your workflow

Optional **Output filters** accepts a JSON object. For example, `{"keywords":"engineer","location":"Canada"}` matches title/description keywords and published locations. The supported keys are `keywords`, `location`, `department`, `employmentType`, `workplaceType` and `salaryPresent`. Text filters use case-insensitive contains. `salaryPresent: true` requires a structured published range; `false` selects jobs without one. Leave it absent to include both.

Filters apply **after full unfiltered fetching**. They reduce output rows, not crawl work, cost or monitored membership. Apify supplies JSON/CSV/XML exports and dataset pagination. In n8n or an authenticated Apify integration, upsert snapshots by `jobKey`; deduplicate changes by `eventId`. Use **Spreadsheet export** columns when mapping untrusted source text into Sheets/CSV. The Actor itself stores no Sheets/n8n credentials and sends no messages.

#### Ready-to-configure workflow templates

After a run, select **n8n workflow (JSON)**, **Google Sheets monitor (Apps Script)** or **Workflow setup guide** from Output to download the templates. They also ship in the source package's `workflows/` folder.

- **n8n verified snapshot:** import the JSON, select your Apify Header Auth credential on the HTTP nodes, review the Configure node and run manually. It polls a single run, checks COMPLETE/COMMITTED and reads every dataset page before emitting job records for your destination. It is inactive by default and does not claim automatic incremental chaining.
- **Google Sheets changes monitor:** paste the script into a dedicated workbook's Apps Script project, put the token/targets/proxy settings in Script Properties, and run setup. It creates an hourly sync tick but stays paused until you explicitly enable it after reviewing the scope and budget. It keeps a durable pending run and predecessor, serializes ticks, upserts jobs/events idempotently and advances only after complete delivery. An unchanged zero-row result still advances the monitor. Source failures or uncertain paid starts pause for review.

Tokens and custom proxy passwords belong in private credentials or Script Properties, not exported workflow JSON or cells. The guide explains authorization, schedules, recovery, scope changes and selected Sheet fields. These templates have automated local checks and independent code review; they have not been executed in your n8n/Google account. Start with a small manual run before enabling a schedule.

### Compare runs for hiring observations

Start with a complete snapshot. To continue the same monitor, put its successful run ID into **Previous committed run ID**, keep the same target URLs/company labels, and choose **Changes since previous run**. A complete snapshot already creates the state needed for that comparison. Leave the previous run blank only when deliberately starting a fresh baseline.

| Observation | Meaning |
|---|---|
| `baseline` | First observed in a new monitor; not evidence of a newly posted job. |
| `new` | Newly observed since the supplied predecessor. |
| `changed` | Meaningful published fields changed; see `changedFields`. |
| `closed` | No longer publicly listed after two complete eligible absences at least one hour apart; never proof a role was filled. |
| `reopened` | A previously closed listing is publicly observed again. |

An unchanged complete comparison correctly returns an empty changes dataset. Direct job URLs cannot infer board removal. Changes mode rejects a board combined with its own direct job URLs. A new independent changes run emits a baseline; snapshot mode always emits current `snapshot` rows.

Board membership is checked before and after detail fetching. Matching complete listings and valid details are required before immutable `STATE`/`COMMITTED` records are written. Capped or incomplete snapshots can return clearly marked `PARTIAL` previews without committed state; incomplete changes runs fail without authoritative events. Output succeeds before state is committed. An unexplained drop below half a prior open count of at least ten is quarantined for inspection.

For automation, advance your saved predecessor only when the platform run succeeded, `REPORT.status` is `COMPLETE`, `stateCommitted` is true, and your downstream delivery succeeded. A static scheduled predecessor repeatedly compares against the same baseline; automatic chaining is not built in. Concurrent runs form separate branches. Select one successful branch to continue and deduplicate at-least-once event delivery by `eventId`.

### Proxy, runtime and usage expectations

Apify Proxy is selected by default. To use your own proxy, choose custom proxies in the proxy editor, or set `useApifyProxy: false` and `proxyUrls: ["http://USER:PASSWORD@HOST:PORT"]`. HTTP and HTTPS proxy origins are supported, up to 20. Keep credentials private. Custom and Apify choices are mutually exclusive. There is no direct fallback and no automatic upgrade to paid proxy groups. Explicit groups you select depend on your account and may have different costs.

On **28 October 2026**, the original three demonstration boards returned **89 jobs: 66 Ashby, 18 Greenhouse and 5 Lever**. Before monetization was enabled, one complete run used **98 proxied page attempts**, about **11.78 MB** of responses and **2 minutes 16 seconds**, with approximately **$0.009 displayed platform usage**. A complete repeat returned zero content changes. These are limited historical validation observations, not promised counts, coverage, speed or current total pricing. Actor event fees now add to platform usage; the old $0.009 figure is not a current all-in quote. Displayed usage excludes later timed storage; proxy selection, retries and board size affect cost. For 200 Free-plan checks, the actor fee alone is $0.30 plus start and platform usage; choose your maximum run cost accordingly. The supplied workflow cap is $0.50, not an expected spend.

One check is a distinct job successfully fetched and parsed in that run. Duplicate URLs, listing/robots requests and retry attempts are not extra job-check units. **Job check billing receipts** in Output retain the source, checked time and charged/pending status. An unconfirmed charge stops work without an automatic retry. Paid runs cannot be resurrected; inspect the original run before deliberately starting a new one. The SDK and platform cap stop unaffordable work; a capped or uncertain run cannot advance monitor state. In-flight work can continue briefly while a run stops; later storage and invoices from your custom proxy provider are not guaranteed to be included in that run cap.

Defaults: 200 checked jobs, 500 page attempts and 60 requests/minute, with at least one second between requests to an origin. Hard bounds: 10 targets, 1,000 checked jobs, 2,000 attempts, 5 MiB/page, 50 MiB/run, 250,000 description characters, 6 MiB state and 20 MiB planned dataset payload. Requested job limits are not capacity guarantees. Descriptions are never silently truncated. Listing checks, robots requests and retries also consume request capacity.

Robots rules are respected. Denials, CAPTCHA, TLS errors and unsupported redirects are terminal. Up to two transient network/429/500/502/503/504 retries retain the same proxy identity; Retry-After above 30 seconds fails safely. This is webpage scraping, not an ATS API client or browser renderer. It does not submit applications or collect candidate data.

### Troubleshooting

| Symptom | What to check |
|---|---|
| Unsupported URL or unknown layout | Compare the URL with the supported forms above. Modern Greenhouse board roots and Lever US/EU forms are supported. Corporate wrappers and legacy/EU Greenhouse hosts need separate support. |
| Successful run but PARTIAL report | Open **Completeness report**. Inspect its target `reasons`. Raise a deliberate job/request cap only within your budget, or split targets and begin a new baseline. Never advance a monitor from this preview. |
| Zero change rows | If COMPLETE with committed state, the normalized content may be unchanged. Use snapshot mode for current jobs. |
| Missing salary, date or workplace | The source may omit it or publish ambiguous metadata. Inspect raw pay/provenance; values are not invented. |
| Previous run rejected | Use a successful committed predecessor from the same Actor/account, target scope and normalization version. If unavailable, deliberately start a fresh baseline by clearing the run ID. |
| Count-drop quarantine | Inspect the live source. A deliberate fresh baseline is required if the new membership is legitimate. |
| Proxy access error, 403, CAPTCHA or robots denial | Check your Apify proxy entitlement or custom proxy credentials and source access. Do not repeatedly retry around an access denial. |

For advanced state, capacity, normalization and development details, see **REFERENCE.md** in the source package. Expanded employer/region validation is recorded in **COVERAGE.md**. Version 3 improves labelled pay extraction and literal HTML-boundary separators; version-2 predecessors require an explicit fresh baseline rather than emitting parser improvements as job changes. Current records use normalization version 3; incompatible extraction contracts require a new baseline. Closure/reopen rules have controlled automated coverage; the live validation above did not observe actual closures or reopenings.

# Actor input Schema

## `targets` (type: `array`):

Replace the example with 1–10 URLs. Accepts strings or {"url":"...","companyLabel":"optional"}. Greenhouse modern /BOARD roots or /embed/job_board?for=BOARD; Lever jobs.lever.co/BOARD or jobs.eu.lever.co/BOARD; Ashby jobs.ashbyhq.com/BOARD. Corporate wrappers, legacy/EU Greenhouse hosts and API endpoints are unsupported.

## `mode` (type: `string`):

Use snapshot for your first run. Changes compares with an explicit previous committed run; without one it emits a first-observed baseline, not newly posted jobs.

## `maxResults` (type: `integer`):

Maximum checked jobs, not a post-filter row limit. If a board exceeds the cap, snapshot returns a PARTIAL preview with no monitor commit; changes mode fails. Default 200.

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

Apify Proxy by default, or your own HTTP/HTTPS proxies. All source requests require a proxy. Platform/proxy usage is extra to successful-job-check fees, including explicit paid groups. No direct fallback or automatic upgrade. Keep custom credentials private.

## `filters` (type: `object`):

Example: {"keywords":"engineer","location":"Canada"}. Optional keys: keywords, location, department, employmentType, workplaceType (case-insensitive contains). salaryPresent:true requires a structured published pay range; false selects missing ranges. Omit salaryPresent for both. No fields are required.

## `previousRunId` (type: `string`):

Paste the successful Apify run ID whose STATE and COMMITTED records you want to continue. Keep the same Actor/account, targets and normalization version 3; version 2 requires a fresh baseline. Advance only after complete output and downstream delivery. A static scheduled predecessor does not automatically chain runs; use the included monitor template for that.

## `maxRequests` (type: `integer`):

Total proxied robots, listing, detail and retry attempts. Board listings are read twice. Reaching a cap prevents a complete monitor commit.

## `maxRequestsPerMinute` (type: `integer`):

Global rate limit, additionally respecting per-origin spacing and robots crawl-delay. Access denials are terminal.

## `concurrency` (type: `integer`):

At most two simultaneous requests globally. Per-origin spacing and robots crawl-delay still apply.

## `includeRawSource` (type: `boolean`):

Include selected published JobPosting JSON-LD and posting fields in JSON output. Full HTML, application configuration and forms are excluded. Raw metadata is not stored in monitor state.

## Actor input object example

```json
{
  "targets": [
    {
      "url": "https://jobs.lever.co/basis"
    }
  ],
  "mode": "snapshot",
  "maxResults": 200,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "filters": {},
  "previousRunId": "",
  "maxRequests": 500,
  "maxRequestsPerMinute": 60,
  "concurrency": 2,
  "includeRawSource": false
}
```

# Actor output Schema

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

No description

## `report` (type: `string`):

No description

## `state` (type: `string`):

No description

## `committed` (type: `string`):

No description

## `billing` (type: `string`):

No description

## `workflowN8n` (type: `string`):

No description

## `workflowSheets` (type: `string`):

No description

## `workflowGuide` (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 = {
    "targets": [
        {
            "url": "https://jobs.lever.co/basis"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("api_creators/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 = { "targets": [{ "url": "https://jobs.lever.co/basis" }] }

# Run the Actor and wait for it to finish
run = client.actor("api_creators/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 '{
  "targets": [
    {
      "url": "https://jobs.lever.co/basis"
    }
  ]
}' |
apify call api_creators/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,api_creators/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/fTPeBg7THM3Ubq70Z/builds/7OilasRqOlmtRs6Ql/openapi.json
