# Lever Jobs Scraper (`tallowlanelabs/lever-jobs-scraper`) Actor

Lever jobs scraper for any company board (US and EU) via Lever's public Postings API: salary ranges, team, location, remote/hybrid, commitment, apply links. Schedule for new, changed and closed jobs, pay changes included. $1.50 per 1,000 jobs. Unofficial; not affiliated with Lever.

- **URL**: https://apify.com/tallowlanelabs/lever-jobs-scraper.md
- **Developed by:** [Tallow Lane Labs](https://apify.com/tallowlanelabs) (community)
- **Categories:** Jobs, Lead generation, Automation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 job items

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

## Lever Jobs Scraper: salaries and change alerts from any Lever job board

**Lever jobs scraper for any company board (Zoox, Shield AI, Palantir, Spotify, Veeva and thousands more). Scrape Lever job postings from Lever's public Postings API: salary ranges, team, department, location, remote/hybrid, commitment, posted date and apply links. Schedule it and each run can return only new, changed and closed jobs, pay changes included.**

**Monitoring cost:** a board with no changes costs $0.0015 per check, so watching 10 Lever boards daily is about $0.32 a month, plus $0.002 per new, changed or closed job.

*By Tallow Lane Labs. Unofficial tool; not affiliated with, endorsed by or sponsored by Lever, Inc. or Employ Inc.*

### Why this one

- **Salary from Lever's structured `salaryRange`** (min, max, currency, pay period), with a fallback that reads amounts like "$120,000 - $150,000" from the posting's pay text. `salarySource` says which one you got.
- **Change alerts, including pay.** Every record says `new`, `updated` (with `changedFields` such as `salary`, `title` or `location`, and the `previous` values, old pay included), `unchanged` or `removed`. Editing pay boilerplate text alone does not count as a pay change.
- **Full list by default.** `snapshot` mode returns every live job each run; switch to `changes` for alerts only.
- **US and EU boards:** `jobs.lever.co/zoox`, `jobs.eu.lever.co/acme`, any job or apply link, `zoox`, `lever:zoox`, or a careers page that links to Lever.
- **Descriptions as text, Markdown and/or HTML**, with every section (intro, requirement lists, closing text).
- **Official public API, no proxies, no browser, no login.** One request per board; runs in seconds.

### Use cases

- **Salary benchmarking** across companies, teams and locations.
- **Pay-change and new-role alerts** for recruiters, sourcers and job seekers.
- **Sales and investor signals:** new teams and roles at target accounts, closed roles.
- **Job boards and aggregators:** a clean, normalized Lever feed.
- **Labor-market research** on remote vs. on-site hiring.

### Quick start

1. Click **Try for free** and run the example board (Zoox), or leave `boards` empty for a 5-job demo.
2. Add boards, e.g. `https://jobs.lever.co/shieldai`, `palantir`, `lever:zoox`.
3. For alerts, set `mode: changes` and schedule the Actor daily.

### Input example

```json
{
  "boards": ["https://jobs.lever.co/zoox", "shieldai", "lever:palantir"],
  "mode": "changes",
  "keywords": ["engineer"],
  "salaryOnly": true
}
```

Main options: `boards` (leave empty for a 5-job demo), `mode` (`snapshot` default, or `changes`), `changeTypes`, `emitBaseline`, filters (`keywords`, `excludeKeywords`, `locations`, `departments`, `workplaceTypes`, `postedWithinDays`, `salaryOnly`, `minSalary`), `maxJobsPerBoard`, `includeDescriptions`, `descriptionFormats` (`text`, `markdown`, `html`), `redactContactInfo`, `stateStoreName` / `stateKeyPrefix`. See the **Input** tab for all fields.

### Output example

Real record from a test run on Zoox's board, Nov 3, 2026 (texts shortened):

```json
{
  "recordType": "job",
  "id": "lever:zoox:7980e9a3-99f3-4988-ae6d-355c9199d8fb",
  "ats": "lever",
  "board": "zoox",
  "jobId": "7980e9a3-99f3-4988-ae6d-355c9199d8fb",
  "title": "Body Structures Engineer",
  "department": "Vehicle Development",
  "team": "Body, Closures and Chassis",
  "commitment": "Full-time",
  "employmentType": "full_time",
  "workplaceType": "onsite",
  "isRemote": false,
  "location": "Foster City, CA",
  "locations": ["Foster City, CA"],
  "country": "US",
  "salary": { "min": 170000, "max": 204000, "currency": "USD", "interval": "year",
    "text": "Base Salary Range There are three major components to compensation for this position: sala ..." },
  "salarySource": "salaryRange",
  "postedAt": "2026-09-10T20:41:07.958Z",
  "url": "https://jobs.lever.co/zoox/7980e9a3-99f3-4988-ae6d-355c9199d8fb",
  "applyUrl": "https://jobs.lever.co/zoox/7980e9a3-99f3-4988-ae6d-355c9199d8fb/apply",
  "descriptionText": "At Zoox, you'll collaborate with a team of world-class engineers with diverse backgrounds ...",
  "changeType": "baseline",
  "changedFields": [],
  "firstSeenAt": "2026-11-03T15:59:45.890Z",
  "detectedAt": "2026-11-03T15:59:45.890Z"
}
```

| Field | Example | Notes |
|---|---|---|
| `title` | Body Structures Engineer | Lever `text` |
| `team` / `department` | Body, Closures and Chassis / Vehicle Development | Lever categories |
| `location` | Foster City, CA | `locations` lists all |
| `workplaceType` | onsite | `remote`, `hybrid`, `onsite` or null; raw value in `workplaceTypeRaw` |
| `commitment` | Full-time | also normalized as `employmentType` |
| `salary` | 170000-204000 USD / year | null when the company publishes no pay |
| `changeType` | baseline | `new`, `updated`, `unchanged`, `removed` on later runs |
| `changedFields` | `["salary"]` | on `updated` records; `previous.salary` has the old pay |

- `removed` records carry the last known values plus `lastSeenAt`.
- Board errors are `recordType: "error"` rows, never charged. `RUN_SUMMARY` has per-board counts.
- Dataset views: **Jobs & changes**, **Changes only**, **Pay**, **Board errors**.

### Pricing (pay per event)

| Event | Price | When |
|---|---|---|
| Job item | **$0.0015** | Each job in `snapshot` mode, and each `baseline` job on a board's first `changes` run |
| Job change | $0.002 | Each `new`, `updated` or `removed` record in `changes` mode |
| Board check | $0.001 | Each board checked per run (per started 1,000 jobs) |
| Actor start | $0.0005 | Once per run (same at 256, 512 or 1,024 MB) |

Not charged: error rows, failed boards, filtered-out jobs.

- 6 companies, no changes: $0.0065 per run (about $0.20 a month daily).
- Zoox's full board (about 223 jobs) as a snapshot: about $0.34.
- 50 companies (~7,500 jobs): baseline about $11.30 once (or $0.051 with `emitBaseline: false`), then ~$0.35 on a day with 150 changes.
- Set Apify's **maximum cost per run** for a hard cap; the Actor stops cleanly and saves its state.

### Integrations

- **Make, Zapier, n8n:** Apify's official app/node → *Run Actor* `tallowlanelabs/lever-jobs-scraper` → send each new job or pay change to Slack, Google Sheets, Airtable or your CRM.
- **Apify API and clients:** `POST https://api.apify.com/v2/acts/tallowlanelabs~lever-jobs-scraper/run-sync-get-dataset-items`, or

```python
from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("tallowlanelabs/lever-jobs-scraper").call(
    run_input={"boards": ["zoox"], "salaryOnly": True})
jobs = list(client.dataset(run["defaultDatasetId"]).iterate_items())
```

- **AI agents (MCP):** `https://mcp.apify.com?tools=tallowlanelabs/lever-jobs-scraper` lets an AI assistant answer "what does Zoox pay engineers?" from live data.
- **Webhooks, Slack, Google Sheets:** on the Actor's **Integrations** tab.

### FAQ

**How do I find a company's board name?** It's the part after `jobs.lever.co/` (or `jobs.eu.lever.co/`). You can also paste any job link or the careers page.

**Do all jobs have pay?** Only if the company publishes it. On Nov 3, 2026, 207 of Zoox's 223 jobs and 531 of Shield AI's 657 had a structured salary range; Palantir's 305 had none.

**How do I get alerts when pay changes?** Use `mode: changes` on a schedule. A pay change is an `updated` record with `"salary"` in `changedFields`, the new pay in `salary` and the old pay in `previous.salary`.

**Why is `companyName` the board name?** Lever's public API doesn't return a company display name, so the board (site) name is used.

**Does it work with EU boards?** Yes. Paste the `jobs.eu.lever.co` URL or use `lever:eu:<site>`.

**Is it legal?** It reads Lever's public, unauthenticated Postings API (github.com/lever/postings-api), which exists so companies' published jobs can be shown anywhere. Only published postings are read; no logins. Use the data in line with Lever's and employers' terms and laws such as GDPR and CCPA.

### Limits

Public, published postings only; pay only when published; Lever has no "updated at" date. Memory: 512 MB covers most watch lists; use 1,024 MB if any board has over 2,000 jobs (`RUN_SUMMARY.memory` advises).

### Support

Open an issue on the **Issues** tab or email Tallow Lane Labs at wdeornellas+tallowlanelabs@gmail.com.

# Actor input Schema

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

One entry per company. Accepts a Lever board URL (jobs.lever.co/zoox, or any job or apply link on it), an EU board URL (jobs.eu.lever.co/acme), a Postings API URL (api.lever.co/v0/postings/zoox), a site name (zoox) or lever:zoox (EU: lever:eu:acme), or a company careers-page URL (the Actor loads it once and looks for a Lever link). Optional: leave empty to run a quick 5-job demo from a public example board.

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

"snapshot" (default) returns every current job on every run; each record still says whether it is new, updated (with changedFields) or unchanged since the last run. "changes" (monitor) returns only jobs that are new, updated or removed since the last run for each board; the first run for a board returns the full baseline. Schedule "changes" daily for alerts.

## `changeTypes` (type: `array`):

Which changes to output in "changes" mode. A pay change shows up as an "updated" job with "salary" in changedFields.

## `emitBaseline` (type: `boolean`):

In "changes" mode, the first run for a board has nothing to compare against. If on, all current jobs are output (charged as job items) with changeType "baseline". If off, the first run only stores the baseline silently (nothing output, nothing charged) and alerts start on the next run.

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

Only output jobs whose title contains at least one of these (case-insensitive). Leave empty for all.

## `excludeKeywords` (type: `array`):

Skip jobs whose title contains any of these.

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

Only output jobs whose location, city, region or country contains one of these strings, e.g. "New York", "Germany", "remote".

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

Only output jobs whose department or team contains one of these strings, e.g. "Engineering".

## `workplaceTypes` (type: `array`):

Only output these workplace types. Empty = all. "unknown" covers postings that do not say.

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

Only output jobs first published within this many days (where the ATS gives a date).

## `salaryOnly` (type: `boolean`):

Only output jobs that publish pay (Lever's structured salary range, or a pay amount found in the posting's salary text).

## `minSalary` (type: `integer`):

Only output jobs whose posted yearly pay reaches at least this amount (top of the range, in the posting's own currency). Jobs without posted pay, or with hourly, weekly or monthly pay, are skipped when this is set.

## `maxJobsPerBoard` (type: `integer`):

Cap the number of records output per board per run (after filters). Change detection still sees the whole board.

## `includeDescriptions` (type: `boolean`):

Include the full description. Turning this off makes runs faster and the dataset smaller.

## `descriptionFormats` (type: `array`):

Formats to include when descriptions are on: plain text, Markdown, and/or the original HTML (all sections: intro, lists such as requirements, and closing text).

## `redactContactInfo` (type: `boolean`):

Some employers put a recruiter email or phone in the public job text. Turn on to replace them with \[email removed] or \[phone removed].

## `stateStoreName` (type: `string`):

Named key-value store that keeps each board's last-seen jobs between runs. Use a different name (or a key prefix) to run independent monitors.

## `stateKeyPrefix` (type: `string`):

Optional prefix so several saved tasks can monitor the same board independently.

## `resetState` (type: `boolean`):

Ignore stored state for the boards in this run and treat it as a first run.

## `detectFromCareerPages` (type: `boolean`):

For URLs that are not a Lever domain, load the page once and look for a Lever job-board link.

## `boardConcurrency` (type: `integer`):

How many boards to process at once. Memory depends on the jobs on these boards: 512 MB (default) fits up to 3,000 live jobs across the boards processed at once (no single board over 2,000); use 1024 MB above that, or set this to 1 for very large boards.

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

Global cap on in-flight requests to the Lever API.

## `removalSafetyMinJobs` (type: `integer`):

If a board that had at least this many jobs suddenly returns zero, removals are not reported that run (it is usually an API hiccup).

## Actor input object example

```json
{
  "boards": [
    "lever:zoox"
  ],
  "mode": "snapshot",
  "changeTypes": [
    "new",
    "updated",
    "removed"
  ],
  "emitBaseline": true,
  "keywords": [],
  "excludeKeywords": [],
  "locations": [],
  "departments": [],
  "workplaceTypes": [],
  "salaryOnly": false,
  "maxJobsPerBoard": 5,
  "includeDescriptions": true,
  "descriptionFormats": [
    "text",
    "markdown"
  ],
  "redactContactInfo": false,
  "stateStoreName": "lever-jobs-scraper-state",
  "stateKeyPrefix": "",
  "resetState": false,
  "detectFromCareerPages": true,
  "boardConcurrency": 3,
  "maxConcurrency": 4,
  "removalSafetyMinJobs": 5
}
```

# Actor output Schema

## `results` (type: `string`):

All job and change records from this run in the default dataset.

## `runSummary` (type: `string`):

JSON summary of the run: boards checked, job counts, change counts and errors.

# 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": [
        "lever:zoox"
    ],
    "mode": "snapshot",
    "maxJobsPerBoard": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("tallowlanelabs/lever-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": ["lever:zoox"],
    "mode": "snapshot",
    "maxJobsPerBoard": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("tallowlanelabs/lever-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": [
    "lever:zoox"
  ],
  "mode": "snapshot",
  "maxJobsPerBoard": 5
}' |
apify call tallowlanelabs/lever-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tallowlanelabs/lever-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/3ug0Yq1dK44QuiMbi/builds/5nMWowHVWkCxNL7IC/openapi.json
