# Lever Jobs Scraper: Postings, Teams & Commitments (`arman-bd/lever-jobs-scraper`) Actor

Pull every public posting from any Lever careers site: title, team, commitment, workplace type, location and full description. No login, no proxy, no browser.

- **URL**: https://apify.com/arman-bd/lever-jobs-scraper.md
- **Developed by:** [Arman Hossain](https://apify.com/arman-bd) (community)
- **Categories:** Jobs, Lead generation, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.74 / 1,000 posting scrapeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Lever Jobs Scraper: Postings, Teams & Commitments

![Lever Jobs Scraper: Postings with typed salary ranges, workplace type and structured requirement blocks](https://api.apify.com/v2/key-value-stores/ZQOcNAOHrIgTacAmy/records/lever-jobs-scraper.jpg)

Pulls every public posting from any Lever-hosted careers site. You get the title, team, department, commitment, workplace type, location list, requirement bullets, the salary range where the company publishes one, and the direct apply URL.

Lever runs the applicant tracking system behind thousands of startups and scale-ups, and every careers site is backed by a public endpoint. This Actor reads that endpoint directly, so there's no browser, no proxies, no login and no rate-limit games. A board with 400 postings comes back in a few seconds.

**Agent skill: [SKILL.md](https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/lever-jobs-scraper.md)**

```
https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/lever-jobs-scraper.md
```

### What you get

| Field | What it holds |
|---|---|
| `company` | The Lever slug the posting came from |
| `postingId` | Lever's UUID for the posting |
| `title` | Job title, which Lever calls `text` on the wire |
| `team`, `department`, `level` | Lever's category taxonomy. `department` and `level` are only set by companies that use them |
| `commitment` | Free-text employment type, e.g. `Full-time`, `Contract`, `Internship` |
| `workplaceType` | `remote`, `onsite`, `hybrid` or `unspecified` |
| `location`, `allLocations`, `country` | Primary location, the full location list, and the ISO country code |
| `categories` | The raw Lever category object, kept verbatim |
| `salaryRange`, `salaryDescription` | `{ min, max, currency, interval }` and any free-text pay note, when the company publishes them |
| `descriptionPlain` | Opening and body as plain text, straight from Lever |
| `lists` | Requirement and responsibility blocks as `{ text, items[] }`, with bullets split into an array |
| `additionalPlain` | The closing "about the company" section as plain text |
| `hostedUrl`, `applyUrl` | Posting page and the direct application form |
| `createdAt` | Posting creation time, converted from Lever's epoch milliseconds to ISO 8601 |
| `scrapedAt` | When the run happened |

`RUN_SUMMARY` in the key-value store holds per-run counts, the filters you used, and any company that failed.

### Use cases

- **Portfolio hiring tracking.** Watch every company a fund has invested in and diff headcount signals week over week.
- **Job aggregators.** Ingest hundreds of Lever careers sites on a schedule into one normalised feed.
- **Remote-supply analysis.** Count `workplaceType: "remote"` postings by team across a sector.
- **Lead generation.** A company hiring three data engineers is a company buying data tooling.
- **Compensation research.** Collect the `salaryRange` objects that pay-transparency laws now force onto public postings.

### Quick start

Two companies, everything they have:

```json
{
 "companies": ["leverdemo", "spotify"]
}
```

Remote engineering roles only, capped:

```json
{
 "companies": ["leverdemo", "https://jobs.lever.co/ro"],
 "searchTerms": ["engineer", "developer"],
 "teamFilter": ["engineering"],
 "workplaceType": ["remote"],
 "maxJobsPerCompany": 50,
 "includeDescription": true
}
```

A light sweep for change detection:

```json
{
 "companies": ["spotify"],
 "includeDescription": false
}
```

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `companies` | array | required | Company slugs like `leverdemo`, or full careers URLs. You can mix both. URLs get normalised to slugs for you. |
| `searchTerms` | array | `[]` | Keep only titles containing one of these terms, case-insensitive. Empty means keep everything. |
| `teamFilter` | array | `[]` | Matched against team and department. Empty means all teams. |
| `commitmentFilter` | array | `[]` | Substring match, so `full` catches both `Full-time` and `Regular Full Time (Salary)`. Empty means all. |
| `workplaceType` | array | `[]` | Exact match on `remote`, `onsite`, `hybrid`, `unspecified`. `on-site` and `on site` are normalised to `onsite`. Empty means all. |
| `locationFilter` | array | `[]` | Matched against the primary location and every entry in `allLocations`. Empty means all locations. |
| `maxJobsPerCompany` | integer | `0` | Cap postings saved per company, applied after filtering. 0 means no limit. |
| `includeDescription` | boolean | `true` | Include `descriptionPlain`, `lists` and `additionalPlain`. Turn it off for a far smaller dataset. |

All filters are independent and combine with AND. A posting has to match at least one term in each filter you actually filled in.

### Output example

A real record, trimmed only in `descriptionPlain`:

```json
{
 "company": "ro",
 "postingId": "d2e897ae-3b4c-4219-ab0d-0403decc2115",
 "title": "Engineering Manager, Core Infrastructure",
 "team": "Engineering",
 "department": null,
 "level": null,
 "commitment": "Full-time",
 "workplaceType": "hybrid",
 "location": "New York, NY",
 "allLocations": ["New York, NY"],
 "country": "US",
 "categories": {
 "commitment": "Full-time",
 "location": "New York, NY",
 "team": "Engineering",
 "allLocations": ["New York, NY"]
 },
 "salaryRange": null,
 "salaryDescription": null,
 "descriptionPlain": "Join Tech @ Ro to build the future of healthcare, from the ground up .",
 "lists": [
 {
 "text": "What You'll Do",
 "items": [
 "Lead and develop a Core Infrastructure team responsible for production-critical systems and foundational engineering capabilities.",
 "Own execution across the team's portfolio: clarify priorities, sequence work, manage tradeoffs, and ensure high-quality delivery."
 ]
 }
 ],
 "additionalPlain": "The target base salary for this position ranges from $219,300 to $260,000 .",
 "hostedUrl": "https://jobs.lever.co/ro/d2e897ae-3b4c-4219-ab0d-0403decc2115",
 "applyUrl": "https://jobs.lever.co/ro/d2e897ae-3b4c-4219-ab0d-0403decc2115/apply",
 "createdAt": "2026-06-15T13:33:29.004Z",
 "scrapedAt": "2026-08-06T11:40:18.574Z"
}
```

This posting leaves `salaryRange` empty and states pay in prose instead, which is a common pattern. Where the structured field is filled in it looks like `{ "min": 108097, "max": 135121, "currency": "USD", "interval": "per-year-salary" }`.

### Finding a company slug

Open the company's careers page and look at the URL.

| URL you see | Slug |
|---|---|
| `jobs.lever.co/acme` | `acme` |
| `jobs.eu.lever.co/acme` | `acme` |
| `jobs.lever.co/acme/1234-5678` | `acme` |

You can paste the whole URL and let the Actor pull the slug out. If a company's careers page sits on its own domain, view source and search for `lever.co`. The slug is almost always in an iframe or a fetch URL.

### API example

```bash
curl -X POST "https://api.apify.com/v2/acts/arman-bd~lever-jobs-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
 "companies": ["leverdemo"],
 "searchTerms": ["engineer"],
 "maxJobsPerCompany": 25
 }'
```

### JavaScript example

```js
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('arman-bd/lever-jobs-scraper').call({
 companies: ['leverdemo', 'spotify'],
 searchTerms: ['engineer'],
 workplaceType: ['remote'],
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const job of items) console.log(`${job.company} - ${job.title} (${job.location})`);
```

### Notes

- Lever returns every posting in one response, so there is no pagination to walk. Filtering and `maxJobsPerCompany` are applied after the fetch, which means a capped run is not a cheaper request.
- Timeouts are generous. Large boards return multi-megabyte payloads and Lever has been observed taking around 100 s, so each request gets 120 s before it is abandoned, with up to three attempts and linear backoff.
- A bad slug won't kill the run. An unknown company returns HTTP 404 with `{"ok":false,"error":"Document not found"}` and lands in `RUN_SUMMARY.failures`. The Actor only errors out if every company fails.
- Bullets are split, not stripped. Lever ships requirement blocks as a bare run of `<li>` elements, and each block becomes `{ text, items[] }` so you can count or filter requirements without touching HTML.
- Public data only. No authentication, no personal data, nothing that gets around access controls.

### FAQ

**Do I need a Lever API key?** No. You supply no credentials.

**Why is `title` not called `text`?** Because Lever's wire field is `text`, which is unusable in a spreadsheet. It is renamed to `title`, and everything else keeps Lever's own naming.

**Does every posting have a salary?** No. `salaryRange` is populated only where the company filled it in, commonly for US roles covered by pay-transparency rules. Elsewhere it is `null`, and nothing is inferred.

**Why did I get an empty array back?** A valid slug with no open roles returns `[]` with HTTP 200, which is different from a wrong slug returning HTTP 404. `RUN_SUMMARY` distinguishes the two: the first shows `0 listed`, the second shows up in `failures`.

**Can I get only new postings?** Run it on a schedule and diff on `postingId`. `createdAt` gives you a genuine first-seen date, so a daily run reconstructs a hiring timeline.

**Can I plug it into something else?** Yes. Apify API, the client libraries, webhooks, scheduled runs, dataset exports to JSON, CSV or Excel, or MCP. The output is structured JSON.

# Actor input Schema

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

Company slugs or careers-site URLs. The slug is the path segment in the careers URL, so for https://jobs.lever.co/leverdemo the slug is 'leverdemo'. Full URLs work too and get normalised for you.

## `searchTerms` (type: `array`):

Keep only postings whose title contains at least one of these terms (case-insensitive). Leave empty to keep every posting.

## `teamFilter` (type: `array`):

Keep only postings whose team or department contains at least one of these terms (case-insensitive). Leave empty for all teams.

## `commitmentFilter` (type: `array`):

Keep only postings whose commitment contains at least one of these terms (case-insensitive). Companies word this freely, so 'full' matches 'Full-time' and 'Regular Full Time (Salary)'. Leave empty for all.

## `workplaceType` (type: `array`):

Keep only postings with one of these workplace types. Lever uses 'remote', 'onsite', 'hybrid' and 'unspecified', and 'on-site' is accepted and normalised. Leave empty for all.

## `locationFilter` (type: `array`):

Keep only postings whose location, primary or any of the additional locations, contains one of these terms (case-insensitive). Leave empty for all locations.

## `maxJobsPerCompany` (type: `integer`):

Cap the number of postings saved per company after filtering. Set 0 for no limit.

## `includeDescription` (type: `boolean`):

Include the plain-text description, requirement lists and closing section. Disable for a much smaller, faster dataset.

## Actor input object example

```json
{
  "companies": [
    "leverdemo",
    "https://jobs.lever.co/spotify"
  ],
  "searchTerms": [
    "engineer",
    "designer"
  ],
  "teamFilter": [
    "engineering",
    "product"
  ],
  "commitmentFilter": [
    "full",
    "intern"
  ],
  "workplaceType": [
    "remote",
    "hybrid"
  ],
  "locationFilter": [
    "london",
    "new york"
  ],
  "maxJobsPerCompany": 0,
  "includeDescription": true
}
```

# Actor output Schema

## `items` (type: `string`):

Every record the run produced.

## `runsummary` (type: `string`):

The RUN\_SUMMARY record from the run's key-value store.

# 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": [
        "leverdemo",
        "spotify"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("arman-bd/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 = { "companies": [
        "leverdemo",
        "spotify",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("arman-bd/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 '{
  "companies": [
    "leverdemo",
    "spotify"
  ]
}' |
apify call arman-bd/lever-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,arman-bd/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/H5ylv1Xsiz2TS0MQo/builds/priFf97agabxR8cpL/openapi.json
