# Greenhouse, Lever & Ashby Jobs Scraper | Salaries & Changes (`felixgoldwin/greenhouse-lever-ashby-jobs-scraper`) Actor

Collect public jobs from company Greenhouse, Lever and Ashby boards. Get titles, locations, descriptions and published pay, or track new, changed and no-longer-listed jobs. Supply board identifiers; unknown pay and remote status stay null.

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

## Pricing

from $1.00 / 1,000 job or change rows

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, salaries and changes

**Add a company's board identifier. Get its public jobs in one consistent JSON table.** Collect job titles, locations, descriptions, source links and any structured pay the employer publishes through the official [Greenhouse](https://docs.greenhouse.io/job-board.html), [Lever](https://github.com/lever/postings-api) or [Ashby](https://developers.ashbyhq.com/docs/public-job-posting-api) API.

Use it for hiring research, company watchlists and published-salary comparisons. Choose the mode that matches your question:

| Your question | Choose | What you get |
| --- | --- | --- |
| What jobs are listed now? | `snapshot` | The current jobs retrieved, up to your limits. |
| What changed since my last check? | `incremental` | A first-run baseline, then new, changed, reappeared or no-longer-listed events. Requires complete fetches and a persistent namespace. |

**Start with the [small tested example](examples/small-snapshot.json)** and the [Console steps](#run-on-apify). Then use the [incremental example](examples/incremental.json) to track one board over time.

Coverage is limited to the company boards you supply. Missing structured salary or remote evidence stays `null`; the scraper does not estimate it from descriptions. This is an **MVP with tested examples and documented limits**. It does not discover every company or provide full-market coverage. This independent tool is not affiliated with Greenhouse, Lever, or Ashby.

### What data is extracted?

| Field | Meaning |
| --- | --- |
| `id`, `sourceJobId` | Stable board-scoped ID and source posting ID. Greenhouse posting IDs are distinct from internal job IDs. |
| `provider`, `board`, `region`, `company` | Source identity. Company is a supplied label or the board identifier; provenance identifies that fallback. |
| `title`, `location`, `sourceUrl` | Published title, primary location, and posting URL. Additional source locations remain in provenance. |
| `remote` | `true`, `false`, or `null`, based only on supported structured source fields. |
| `description` | Published source description. HTML or encoded HTML is preserved as data. |
| `publishedPay` | Structured published ranges/components, source currency and period, plus raw provenance; `null` when unavailable. |
| `publishedAt`, `sourceUpdatedAt` | Source timestamps when exposed by the adapter. These are distinct from observation times. |
| `contentHash`, `provenance` | Deterministic normalized-content hash and the source fields/endpoints used. |
| `eventType`, `eventId` | Snapshot or incremental event type and deterministic delivery key. |
| `firstSeen`, `lastSeen`, `observedAt` | Local observation history; not evidence of the original publication date. |

No source URLs embedded in job records are fetched. Descriptions and raw source fields are untrusted text; downstream consumers must escape them before displaying HTML.

### Run on Apify

1. Open the Actor's **Input** tab and supply a company's board identifier with its provider. Use an identifier such as `lever`, not a full URL; set `region: "eu"` for the small Lever example.
2. Start with `mode: "snapshot"` and a small `maxResults`, such as 10. Leave Greenhouse pay enrichment off until you have checked the base output.
3. Review the Console price and set the run's maximum charge. At the base rates below, 10 delivered rows plus a start cost $0.015. Allow enough for the whole output bundle; an insufficient row budget produces an incomplete report and no new rows.
4. Run the Actor, open its default dataset, and inspect **REPORT** in the default key-value store. Check both source completeness and delivery status before using the results.
5. For change tracking, use `mode: "incremental"`, a fresh persistent `stateNamespace`, and `singleWriter: true`. Externally serialize every run using that namespace. Recover an unfinished observation with the same input and observation ID before starting a new one.

### Run the scraper locally

Requirements: Node.js 22 or later, npm, and the official Apify CLI available as `apify`. The lockfile pins dependencies. Public job API requests do not need credentials or proxies.

1. In this project directory, run `npm ci` and `apify --help`.
2. Create `storage/key_value_stores/default/INPUT.json` with an example below. Create its parent directory if needed.
3. Run the Actor with the official local command:

```sh
apify run --user-agent apify-codex-plugin/apify-actor-development
```

4. Inspect `storage/datasets/default/` for rows and `storage/key_value_stores/default/REPORT.json` for completeness and state diagnostics. Local storage is not uploaded to Apify Console.

Do not purge or delete the named state store when continuing an incremental watch. This project does not require `apify push`, cloud builds, paid runs, purchased proxies, or newly created credentials for local operation.

#### Snapshot example

```json
{
  "boards": [
    { "provider": "lever", "board": "lever", "region": "eu", "company": "Lever" }
  ],
  "mode": "snapshot",
  "maxResults": 10,
  "maxResultsPerBoard": 10,
  "pageSize": 3,
  "maxConcurrency": 1
}
```

Snapshot mode emits the valid jobs retrieved, including partial results, with `eventType: "snapshot"`. It does not update incremental state. Its first/last-seen values describe this snapshot only. Always inspect the report when a result or page limit is used.

#### Incremental example

```json
{
  "boards": [
    { "provider": "lever", "board": "lever", "region": "eu", "company": "Lever" }
  ],
  "mode": "incremental",
  "stateNamespace": "lever-eu-watch-v1",
  "singleWriter": true,
  "observationId": "watch-001",
  "maxResults": 1000,
  "maxResultsPerBoard": 1000
}
```

Use a new observation ID, such as `watch-002`, for the next fresh observation. Reuse `watch-001` with exactly the same input to replay its saved result. An ID is generated if omitted, but explicit IDs make interruption recovery easier.

### Input options and limits

The [input schema](.actor/input_schema.json) defines the Console form; runtime validation also rejects unknown fields and invalid combinations.

| Option | Default | Accepted values / behavior |
| --- | --- | --- |
| `boards` | Required | 1–50 objects. `provider`: `greenhouse`, `lever`, or `ashby`; `board`: 1–100 letters/digits/underscores/hyphens, starting with a letter/digit. URLs are rejected. |
| Board `region`, `company` | `global`; identifier fallback | `eu` is supported only for Lever. Company label is optional nonempty text up to 200 characters. Duplicate boards are rejected. Keep identifier spelling/case consistent between runs. |
| `mode` | `snapshot` | `snapshot` or `incremental`. |
| `stateNamespace` | None | Required for incremental: 3–50 lowercase letters/digits/hyphens, starting with a letter/digit. Store name is `jobwatch-{stateNamespace}`. |
| `singleWriter` | `false` | Must be `true` for incremental; acknowledges the concurrency restriction below. |
| `observationId` | Generated | 1–100 letters/digits/underscores/dots/hyphens, starting with a letter/digit. Immutable replay key. |
| `maxResults` | 1,000 | 1–10,000 output rows globally. A board's events are not committed if they exceed the remaining budget. |
| `maxResultsPerBoard` | 1,000 | 1–10,000 jobs per board, also bounded by `maxResults`. Truncation prevents state advancement. |
| `maxConcurrency` | 2 | 1–5 board workers and a shared HTTP concurrency budget, including pay enrichment. |
| `pageSize`, `maxPages` | 100; 100 | Lever page size 1–100; page count 1–1,000. Hitting the page cap without proving completion is incomplete. |
| `timeoutMs` | 15,000 | 100–60,000 milliseconds per request, including DNS and response body. |
| `maxRetries` | 2 | 0–5 retries after the initial attempt for selected transient failures. |
| `minIntervalMs` | 300 | 100–10,000 milliseconds between starts to the same API host. |
| `maxResponseBytes` | 20,971,520 | 1,024–52,428,800 bytes per response. Oversized responses fail closed. |
| `maxRetryWaitMs` | 30,000 | 0–60,000 milliseconds for retry waits. A longer `Retry-After` fails the request instead of retrying early. |
| `greenhousePayDetails` | `false` | Optional individual public job requests for structured Greenhouse pay. |
| `maxPayDetailRequests` | 20 | 0–1,000 detail requests per Greenhouse board. Incomplete requested enrichment prevents incremental advancement. |
| `titleContains` | None | Nonempty case-insensitive literal title filter, up to 200 characters. Any filter suppresses all incremental advancement. |

There are no user-configurable API hosts, request URLs, proxy settings, or application-submission methods. The HTTP transport allowlists official HTTPS API paths, refuses redirects, checks every DNS answer against public-address rules, and pins a checked address to the connection.

The timeout applies separately to each HTTP attempt. Queueing, host spacing and retry backoff can make the total run take longer than `timeoutMs`.

### Incremental state and event semantics

State is independent for each provider/region/board within the namespace. A board omitted from a run is untouched. The first accepted complete unfiltered observation emits `baseline`, even when jobs were published long ago. An empty complete baseline is valid; later observed IDs are `new`.

| Event | Meaning |
| --- | --- |
| `baseline` | Present in the first accepted observation. |
| `new` | First observed after a baseline, not necessarily newly published by the employer. |
| `changed` | The normalized content hash changed. |
| `reappeared` | A previously no-longer-listed source ID returned. |
| `no_longer_listed` | Missing in two complete successful unfiltered observations of the same board. It does not mean filled, hired, or permanently closed. |

Unchanged jobs update `lastSeen` without output rows. `firstSeen` survives changes and reappearances. Content hashes sort object keys and exclude observation metadata; source-published timestamps and selected raw provenance remain part of the content. Array ordering is preserved, so the hash measures normalized content, not a semantic text comparison.

Failures, malformed records, duplicate IDs, truncation, filters, and incomplete requested pay enrichment leave the entire board state unchanged, including its missing counters. Partial observations between two complete missing observations add no confirmation. A return resets the missing count. A suspicious disappearance of at least five active jobs and more than 50% of that board holds the entire observation indefinitely for operator investigation. The MVP does not provide a mass-hold override; after verification, a new namespace can establish a fresh baseline.

Company label, Greenhouse pay-enrichment choice, and normalization version form a persistent profile. Changing that profile requires a new namespace to avoid reporting configuration changes as source changes.

#### Replay and interruption recovery

Before making provider requests or creating a new observation attempt, the Actor checks for remaining row budget. If none remains, it stops without fetching or changing incremental state; any existing pending recovery remains intact. Otherwise, an immutable `OBS-{hash}` record preserves the exact fetched observation. A canonical `EVENTS-{hash}` bundle then stores all planned rows, the report and pending state updates before any state commit. The Actor checks again that the remaining row budget can cover the entire bundle before committing state or appending output. If it cannot, the full bundle remains pending and no new state is committed in that attempt. Recover with the same observation ID and input and a sufficient run budget; the saved observation is not fetched again.

A `PENDING` marker identifies the current delivery attempt. State commits occur after budget preflight and before dataset publication. The Actor verifies the SDK's accepted row count; only a full confirmed append writes a matching `DELIVERED-{hash}` receipt and clears pending state. Unexpected truncation or an uncertain append retains the full bundle for recovery, even when state already advanced. An older successful delivery receipt cannot acknowledge a later incomplete replay. If interruption happened before `OBS` was saved, recovery can fetch again because no board transitions were committed. Do not manually clear `PENDING` to bypass an unresolved interruption.

Completed replays return the original saved bundle without refetching or rewinding newer state. Recovery of a partially committed bundle applies only its outstanding state updates. The board engine also retains a replay ledger for its latest 100 accepted observations.

Dataset delivery is **at least once**. A crash or append retry can create duplicate rows; consumers must deduplicate by `eventId`. Recovery and explicit replay deliver the entire bundle into the current run's dataset, including any rows already delivered in an earlier attempt; those newly delivered rows are charged again. The canonical `EVENTS` bundle is the recovery record, not a transaction across key-value storage, datasets and billing. Replaying a failed or partial fetch repeats its saved result; use a new observation ID to request fresh data after that completed observation.

#### Concurrency and storage restrictions

Only one writer may use a namespace at a time. Local runs use a cooperative exclusive lock under `storage/jobwatch-locks/` (or the configured local storage root). A crash can leave a stale lock: verify the prior process has stopped and inspect the lock before removing it. Locks are never automatically stolen.

Cross-machine/distributed locking is not implemented. `singleWriter: true` is an acknowledgment, not a distributed lock. Cloud use must externally serialize all runs sharing a namespace; overlapping runs are unsupported.

`OBS` and `EVENTS` records are retained permanently, and state retains historical job IDs. They contain descriptions and provenance and can grow substantially. There is no automatic retention, compaction, or storage-cost control in this MVP. Plan retention before sustained use; deleting records weakens historical replay guarantees.

### Provider scope and published-pay caveats

**Greenhouse:** the [public Job Board API](https://docs.greenhouse.io/job-board.html) list endpoint uses `content=true`. Structured pay needs per-job `pay_transparency=true`; the optional enrichment costs additional HTTP requests. Cent values are converted to currency units while preserving each raw range. The documented pay object has no period, so period remains `null`. Descriptions retain source HTML entities. No remote classification is inferred from a location or description.

**Lever:** the [official Postings API](https://github.com/lever/postings-api) supports global and EU hosts with offset pagination. Descriptions combine opening/body, lists, and closing content without duplicating combined sections. Optional `salaryRange` supplies currency and interval. Remote/on-site values are explicit; hybrid or unspecified workplace values remain `null` in the boolean remote field, with source values retained. A short page proves traversal ended; reaching a cap exactly is conservatively incomplete. Offset pagination is not an atomic snapshot and can race with provider edits.

**Ashby:** the [public posting endpoint](https://developers.ashbyhq.com/docs/public-job-posting-api) requests compensation and excludes `isListed: false` direct-link postings. `publishedAt` means last published. All compensation tiers and summary components are preserved; `sourcePath` distinguishes overlapping summaries from tier details. **Never sum those components as independent pay ranges.** Equity remains its published compensation type and is never relabeled cash or salary. Missing currency, period, amounts, and remote evidence remain unknown.

The adapters request public job-board data only, not private applicant endpoints or internal job data. Published descriptions can themselves contain names or contact text; they are retained as part of the public posting. No applicant records or private credentials are collected.

### Output and diagnostics

The default dataset contains snapshot rows or incremental event rows. The default key-value store contains `REPORT`, including each board's `complete`, `unfiltered`, `advanced`, `replayed`, counts, and diagnostics. Completeness describes the fetch; an otherwise complete board can still be held or not committed. Always inspect `advanced` and diagnostics in incremental mode. A run that finishes with zero rows is not evidence that every board succeeded or has no jobs.

`plannedOutputCount` is the bundle size; `outputCount` is the SDK-confirmed number accepted in this run. `delivery.status` is `complete`, `budget_insufficient`, `truncated`, or `uncertain`. A budget hold reports zero accepted rows. When no row budget exists before an observation, `delivery.reason` is `no_row_budget`, the unfetched bundle size and `observedAt` are `null`, and `pendingObservationId` identifies any unchanged pending recovery. Unexpected truncation reports the confirmed total, while per-board `returnedRows` is `null` because a chunked append does not prove which rows were accepted. An uncertain append reports `outputCount: null`, saves the diagnostic report when possible, and fails the run. Incomplete delivery sets the report's overall status to `incomplete` and retains pending incremental recovery.

These are abbreviated synthetic examples, not live results:

```json
[
  {
    "id": "lever:global:example:posting-1",
    "sourceJobId": "posting-1",
    "eventType": "baseline",
    "title": "Engineer",
    "remote": null,
    "publishedPay": null
  },
  {
    "id": "ashby:global:example:posting-2",
    "sourceJobId": "posting-2",
    "eventType": "changed",
    "title": "Designer",
    "remote": true,
    "publishedPay": {
      "sourceField": "compensation",
      "ranges": [
        {
          "kind": "Salary",
          "min": 90000,
          "max": 120000,
          "currency": "USD",
          "period": "1 YEAR",
          "sourcePath": "compensation.compensationTiers[0].components[0]"
        }
      ]
    }
  }
]
```

Use JSON to retain nested compensation/provenance. Apify dataset exports can provide formats such as JSON, HTML, CSV, or Excel when platform export functionality is used; this repository's local runs write local JSON records and do not upload an export.

### Public board references and verification

The following primary-source references establish candidate identifiers. They are examples to inspect, not a guarantee that a company keeps using the same provider or that every live acceptance test passed:

| Provider | Board / region | Primary-source evidence |
| --- | --- | --- |
| Greenhouse | `anthropic` / global | [Anthropic open roles](https://www.anthropic.com/careers/jobs) link to its [hosted board](https://job-boards.greenhouse.io/anthropic). |
| Greenhouse | `cloudflare` / global | [Official Cloudflare hosted posting](https://job-boards.greenhouse.io/cloudflare/jobs/7541032); its [board](https://job-boards.greenhouse.io/cloudflare) redirects to Cloudflare careers. |
| Lever | `ro` / global | [Ro careers](https://ro.co/careers/) links to [Ro's hosted listings](https://jobs.lever.co/ro). |
| Lever | `lever` / eu | [Lever's official API documentation](https://github.com/lever/postings-api) links to the [EU example board](https://jobs.eu.lever.co/lever). |
| Ashby | `ashby` / global | [Ashby's API documentation](https://developers.ashbyhq.com/docs/public-job-posting-api) gives its own board as the name example; [hosted board](https://jobs.ashbyhq.com/ashby). |
| Ashby | `ramp` / global | [Ramp's hosted board](https://jobs.ashbyhq.com/ramp); [Ramp applicant privacy notice](https://ramp.com/legal/privacy-terms/privacy-terms/applicant-privacy-notice) identifies Ashby. |

Run `npm test` for synthetic tests and `npm run test:coverage` for measured coverage. Tests cover adapters, pagination, malformed/partial failures, pay nulls, input/network validation, closure safety, replay, and concurrency behavior. Consult the delivery's test report for actual results, live checks, runtime versions, and remaining gaps. Synthetic tests do not prove future provider behavior, atomic remote snapshots, or cloud operational readiness.

### Pricing and operating limits

The Actor uses Apify's automatic pay-per-event billing at these base rates:

| Event | Base price | What is charged |
| --- | --- | --- |
| `apify-actor-start` | $0.005 per start at up to 1 GB RAM | Each run start, including runs that return no rows or encounter an error. |
| `apify-default-dataset-item` | $0.001 per delivered row ($1 per 1,000) | Each snapshot job or incremental event saved to the run's default dataset. |

Repeat snapshots, partial snapshot results, and explicit replay/recovery rows are charged when delivered. This is per-row pricing, not a unique-job subscription. Unchanged incremental observations emit no row events, but the start charge still applies. The Console pricing table is authoritative for the active price. The code never manually charges these synthetic events.

Runs default to 256 MB memory, a 60-second timeout and a $0.05 maximum charge. The allowed memory range is 256 MB to 1 GB. The tested small example fits these limits. Larger boards, many boards, retries or Greenhouse pay enrichment can take longer; review the timeout, input limits and maximum charge in the Console or API before running them. The input row cap and maximum run charge are separate controls.

Public API requests use no purchased proxies. Sustained-load costs and long-term storage have not been benchmarked. Greenhouse enrichment increases request volume; immutable observations, pending state plans and raw provenance increase storage volume. Externally serialize incremental runs and plan retention before sustained use.

Hosted validation on October 15, 2026 (build 0.1.3) verified that six delivered rows matched six native row-billing events. A $0.006 run cap held the six-row bundle without state advancement; recovery delivered and billed all six rows with zero provider requests and cleared pending recovery. These checks cover the small test board, not sustained-load or broad-board performance.

# Actor input Schema

## `boards` (type: `array`):

1–50 explicit objects: provider greenhouse/lever/ashby, board identifier, optional company label, region global/eu (EU only Lever). URLs are rejected.

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

Snapshot emits fetched public jobs. Incremental requires complete unfiltered observations and explicit state namespace.

## `stateNamespace` (type: `string`):

Required for incremental: 3–50 lowercase letters/digits/hyphens. One writer only. Named KVS jobwatch-{namespace}.

## `singleWriter` (type: `boolean`):

Required true for incremental. Cross-machine distributed locking is not implemented.

## `observationId` (type: `string`):

Reuse exactly to replay the saved observation. New observations need new IDs. Auto-generated if omitted.

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

Global cap. A board whose events exceed the remaining budget is not committed.

## `maxResultsPerBoard` (type: `integer`):

Reaching an extraction cap suppresses incremental advancement.

## `maxConcurrency` (type: `integer`):

Shared HTTP request limit and board worker limit.

## `pageSize` (type: `integer`):

Lever skip/limit page size.

## `maxPages` (type: `integer`):

Reaching this safety cap makes the board incomplete.

## `timeoutMs` (type: `integer`):

Bounds each request including body.

## `maxRetries` (type: `integer`):

Retry only transient errors with bounded backoff.

## `minIntervalMs` (type: `integer`):

Minimum spacing per host.

## `maxResponseBytes` (type: `integer`):

Oversized responses fail closed.

## `maxRetryWaitMs` (type: `integer`):

Longer Retry-After values abort instead of retrying too early.

## `greenhousePayDetails` (type: `boolean`):

Adds individual public job detail requests. A cap/failure makes incremental observation incomplete.

## `maxPayDetailRequests` (type: `integer`):

Bounded optional enrichment budget.

## `titleContains` (type: `string`):

Case-insensitive literal title filter. Any filter suppresses all incremental state advancement.

## Actor input object example

```json
{
  "boards": [
    {
      "provider": "lever",
      "board": "lever",
      "region": "eu"
    }
  ],
  "mode": "snapshot",
  "singleWriter": false,
  "maxResults": 10,
  "maxResultsPerBoard": 10,
  "maxConcurrency": 1,
  "pageSize": 3,
  "maxPages": 4,
  "timeoutMs": 8000,
  "maxRetries": 2,
  "minIntervalMs": 300,
  "maxResponseBytes": 1048576,
  "maxRetryWaitMs": 30000,
  "greenhousePayDetails": false,
  "maxPayDetailRequests": 20
}
```

# Actor output Schema

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

No description

## `report` (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 = {
    "boards": [
        {
            "provider": "lever",
            "board": "lever",
            "region": "eu"
        }
    ],
    "mode": "snapshot",
    "maxResults": 10,
    "maxResultsPerBoard": 10,
    "maxConcurrency": 1,
    "pageSize": 3,
    "maxPages": 4,
    "timeoutMs": 8000,
    "maxRetries": 0,
    "minIntervalMs": 300,
    "maxResponseBytes": 1048576,
    "maxRetryWaitMs": 0,
    "greenhousePayDetails": false,
    "maxPayDetailRequests": 0
};

// Run the Actor and wait for it to finish
const run = await client.actor("felixgoldwin/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 = {
    "boards": [{
            "provider": "lever",
            "board": "lever",
            "region": "eu",
        }],
    "mode": "snapshot",
    "maxResults": 10,
    "maxResultsPerBoard": 10,
    "maxConcurrency": 1,
    "pageSize": 3,
    "maxPages": 4,
    "timeoutMs": 8000,
    "maxRetries": 0,
    "minIntervalMs": 300,
    "maxResponseBytes": 1048576,
    "maxRetryWaitMs": 0,
    "greenhousePayDetails": False,
    "maxPayDetailRequests": 0,
}

# Run the Actor and wait for it to finish
run = client.actor("felixgoldwin/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 '{
  "boards": [
    {
      "provider": "lever",
      "board": "lever",
      "region": "eu"
    }
  ],
  "mode": "snapshot",
  "maxResults": 10,
  "maxResultsPerBoard": 10,
  "maxConcurrency": 1,
  "pageSize": 3,
  "maxPages": 4,
  "timeoutMs": 8000,
  "maxRetries": 0,
  "minIntervalMs": 300,
  "maxResponseBytes": 1048576,
  "maxRetryWaitMs": 0,
  "greenhousePayDetails": false,
  "maxPayDetailRequests": 0
}' |
apify call felixgoldwin/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,felixgoldwin/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/E6etStu0FAoWHHndH/builds/0t6P2twshGWhlvy06/openapi.json
