# Lever Jobs \[$0.99] Scraper & API — Any Company (`memo23/lever-scraper`) Actor

Scrape open jobs live from any company on Lever (jobs.lever.co) — fetched at run time, not from a stale job database. Title, full description, compensation, location, apply URL — via the public postings API.

- **URL**: https://apify.com/memo23/lever-scraper.md
- **Developed by:** [Muhamed Didovic](https://apify.com/memo23) (community)
- **Categories:** Jobs, AI, Agents
- **Stats:** 22 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.99 / 1,000 results

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?

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 🧭

Scrape **every open job from any company hosted on [Lever](https://www.lever.co/)** (jobs.lever.co) — title, full description, compensation, location, department, and apply URL — straight from Lever's public postings API. One request per company returns the whole board, fully detailed. No login, no anti-bot, no browser.

Every row is fetched **live from Lever at run time** — not served from a pre-indexed job database — so closed roles disappear instantly, new postings appear the moment they're published, and any company on Lever is covered, not just the ones a database happens to index.

![How Lever Jobs Scraper works](https://raw.githubusercontent.com/muhamed-didovic/muhamed-didovic.github.io/main/assets/how-it-works-lever.png)

***

### Why use this scraper

- **Complete boards in one call. Lever returns every open role for a company in a single API response — no pagination, no per-job fetches.**
- **Full job detail, not just titles.** Each row carries the entire job: `descriptionHtml` + plain text, compensation range (when shown), location(s), employment type, workplace type, and the apply URL.
- **Fast and cheap.** No proxies required (the API has no anti-bot), so runs are quick and your cost stays low.
- **Normalized schema.** Output uses a consistent ATS schema shared with our other ATS scrapers (Ashby, Lever, SmartRecruiters, Personio, BambooHR) — write one parser, reuse it across every ATS.
- **Built-in filters.** Narrow by title, location, department, employment type, remote, or posted date — applied before a row is emitted, so you only pay for matches.
- **Works with embedded boards.** Paste a company's own careers-page URL (e.g. `https://www.company.com/careers`) — if it renders a Lever board, the actor discovers the underlying Lever slug automatically.
- **Per-company job counts + a coverage report.** Every row carries `companyTotalJobs` (the board's total open roles), and a separate **company-summary** output lists every input link with its status (`ok` / `no_jobs` / `failed` / `not_lever`) and job count — so you can instantly see which links worked and which didn't.
- **Output order matches input order.** Companies are emitted in the order you listed them.
- **Optional English translation (free).** Lever returns each job in the language the company wrote it in (a board can even mix languages). Turn on `translateToEnglish` to auto-translate non-English titles/descriptions to English — no API key, originals kept alongside.

***

### Overview

[Lever](https://www.lever.co/) is a widely-used recruiting platform. Many high-growth tech companies host their careers on Lever at `https://jobs.lever.co/{company}`. This actor reads each company's jobs through Lever's public postings API and emits one clean, normalized row per open job.

***

### Supported inputs

| Input type | Example | Notes |
|---|---|---|
| **Board URL** | `https://jobs.lever.co/spotify` | The company's public Lever board |
| **Job URL** | `https://jobs.lever.co/spotify/{jobId}` | The org slug is extracted; the whole board is scraped |
| **Bare org slug** | `spotify`, `dlocal`, `voodoo` | The part after jobs.lever.co/ |
| **Embedded careers page** | `https://www.company.com/careers` | A company's own page that renders a Lever board — the Lever slug is detected from the page automatically |

Provide them in **Start URLs** and/or **Organization slugs**. Mix as many companies as you like in one run.

> **How do I find a company's Lever slug?** It's the path segment in their `jobs.lever.co/{slug}` careers URL.

***

### Use cases

- **Job boards & aggregators** — ingest fresh, structured roles from many companies.
- **Recruiting & sourcing tools** — track who's hiring for what, with compensation where disclosed.
- **Market & talent intelligence** — hiring velocity, remote-vs-onsite mix, and salary bands where shown.
- **Lead generation** — companies actively hiring are buying signals for many B2B products.
- **Personal job search** — pull every role across your target companies into one sheet.

***

### How it works

1. **Resolve** each input (board URL, job URL, or slug) to an org slug. If an input is a company's own careers page, the actor fetches it and detects the embedded Lever slug.
2. **Fetch** `https://api.lever.co/v0/postings/{org}?mode=json` — one HTTP call per company.
3. **Each job arrives fully detailed** (description, salary, categories, apply URL).
4. **Normalize** to a common ATS schema and **push** one row per job, in the order companies were given.
5. **Write a coverage report** — a separate `company-summary` dataset (plus a `RUN_SUMMARY.json` key-value record) with one line per input link: its status, the total jobs found, and any error.

Companies are processed in parallel with a sliding-window concurrency cap, then flushed in input order. No proxy is needed; you can supply one for IP rotation at very large scale.

***

### Input configuration

| Field | Type | Default | Description |
|---|---|---|---|
| `startUrls` | array | – | Lever board URLs, job URLs, or slugs (strings or {url} objects). |
| `organizations` | array | – | Bare Lever org slugs, e.g. \["spotify","dlocal"]. Merged with startUrls. |
| `maxItems` | integer | `5000` | Max job rows emitted across the whole run. |
| `maxConcurrency` | integer | `10` | How many companies to fetch in parallel. |
| `titleKeyword` | string | – | Keep only jobs whose title contains this. |
| `location` | string | – | Keep only jobs whose location contains this. |
| `department` | string | – | Keep only jobs whose department contains this. |
| `employmentType` | string | – | Keep only this employment type. |
| `remoteOnly` | boolean | `false` | Keep only remote jobs. |
| `postedAfter` | string | – | Keep only jobs published on/after this date (YYYY-MM-DD). |
| `includeDescription` | boolean | `true` | Include `descriptionHtml` + `descriptionText`. Turn off for leaner/cheaper rows. |
| `includeRawJson` | boolean | `false` | Attach the original Lever payload under `raw`. |
| `translateToEnglish` | boolean | `false` | Free auto-translation of non-English job titles/descriptions to English (originals kept). Best with `includeDescription` on. |
| `proxy` | object | – | Optional. Not required (no anti-bot); use only for IP rotation at scale. |

#### Example input

```json
{
  "startUrls": ["https://jobs.lever.co/spotify"],
  "organizations": ["dlocal", "voodoo"],
  "maxItems": 1000,
  "titleKeyword": "engineer"
}
```

***

### Output

One row per open job. Example (trimmed):

```json
{
  "ats": "lever",
  "org": "spotify",
  "company": "Spotify",
  "companyTotalJobs": 142,
  "jobId": "08d82da1-0c72-44d1-91eb-7fa22dee3d39",
  "globalId": "lever:spotify:08d82da1-0c72-44d1-91eb-7fa22dee3d39",
  "title": "Backend Engineer - Platform",
  "department": "Engineering",
  "team": "Platform",
  "employmentType": "Permanent",
  "workplaceType": "Hybrid",
  "isRemote": false,
  "location": "Stockholm",
  "locations": ["Stockholm"],
  "compensation": null,
  "salaryText": null,
  "descriptionHtml": "<div>About the role…</div>",
  "descriptionText": "About the role…",
  "publishedAt": "2026-05-13T15:23:52.322Z",
  "jobUrl": "https://jobs.lever.co/spotify/08d82da1-%E2%80%A6",
  "applyUrl": "https://jobs.lever.co/spotify/08d82da1-%E2%80%A6/apply",
  "scrapedAt": "2026-06-14T18:12:00.000Z"
}
```

#### Key output fields

| Field | Description |
|---|---|
| `ats` | Always `"lever"` — the source platform. |
| `org` | Company board identifier. |
| `company` | Company display name. |
| `companyTotalJobs` | Total open roles on this company's board (before filters/`maxItems`). Same value on every row from that company. |
| `globalId` | Stable, unique key `ats:org:jobId` — use it to dedupe across runs. |
| `title` | Job title. |
| `department` / `team` | Org grouping as set by the company. |
| `employmentType` | Full-time / Part-time / Intern / Contract / Temporary (as the ATS reports it). |
| `workplaceType` / `isRemote` | Remote / Hybrid / OnSite + a boolean remote flag. |
| `location` / `locations` | Primary location + every listed location. |
| `compensation` / `salaryText` | Salary range when the company publishes one. |
| `descriptionHtml` / `descriptionText` | Full job description as HTML and plain text. |
| `publishedAt` | When the role was published (ISO). |
| `jobUrl` / `applyUrl` | Public posting URL + application URL. |
| `translated` / `detectedLanguage` | *(only with `translateToEnglish`)* Whether the row was translated, and the detected source language. |
| `titleOriginal` / `descriptionTextOriginal` | *(only when translated)* The original, pre-translation text. |

#### Coverage report (which links worked)

Alongside the job rows, the actor writes a **per-input coverage report** so you can verify every link you supplied was processed — and see exactly which ones returned no jobs. It's written to a separate **`company-summary`** dataset (export it as CSV/Excel like the main data) and mirrored to a **`RUN_SUMMARY.json`** record in the run's key-value store.

One row per input link, in the order you gave them:

```json
[
  { "inputOrder": 1, "inputUrl": "https://jobs.lever.co/spotify", "resolvedOrg": "spotify", "embedded": false, "status": "ok",        "totalJobsFound": 142, "jobsScraped": 142, "error": null },
  { "inputOrder": 2, "inputUrl": "https://www.acme.com/careers",  "resolvedOrg": "acmecorp","embedded": true,  "status": "ok",        "totalJobsFound": 18,  "jobsScraped": 18,  "error": null },
  { "inputOrder": 3, "inputUrl": "not-a-real-org",                "resolvedOrg": "not-a-real-org","embedded": false,"status": "failed","totalJobsFound": 0,   "jobsScraped": 0,   "error": "org 'not-a-real-org' not found (404 — not on Lever)" },
  { "inputOrder": 4, "inputUrl": "https://example.com/",           "resolvedOrg": null,     "embedded": false, "status": "not_lever", "totalJobsFound": 0,   "jobsScraped": 0,   "error": "No Lever board found at this input…" }
]
```

| Field | Description |
|---|---|
| `inputOrder` | 1-based position of the link in your input. |
| `inputUrl` | The link exactly as you supplied it. |
| `resolvedOrg` | The Lever slug it resolved to (or `null` if none). |
| `embedded` | `true` if the slug was discovered inside a company's own careers page. |
| `status` | `ok` (jobs found) · `no_jobs` (valid board, 0 open roles) · `failed` (fetch error — see `error`) · `not_lever` (no Lever board at this link). |
| `totalJobsFound` | Open roles on the board. |
| `jobsScraped` | Rows actually emitted for this link (after filters + the `maxItems` cap). |
| `error` | Failure reason when `status` isn't `ok`. |

> **Note on `maxItems`:** because output is emitted in input order, the global `maxItems` cap fills from the top of your list. If an early company is large enough to reach the cap, later companies can show `totalJobsFound > 0` but `jobsScraped: 0` — raise `maxItems` to capture them all.

***

### FAQ

**Do I need a proxy?** No. Lever's postings API is public with no anti-bot. The proxy field is available only for optional IP rotation at very large scale.

**Where does `company` come from?** Lever's postings API doesn't expose a display name, so it's derived from the board slug (e.g. `spotify` → `Spotify`).

**Does it work if Lever is embedded in a company's own careers page?** Yes — paste the careers-page URL and the actor fetches it and detects the underlying Lever slug. If a page loads jobs in a way that hides the slug entirely (rare), that link is reported as `not_lever` in the coverage report so you can spot it.

**How do I know which of my links failed?** Check the `company-summary` dataset (or `RUN_SUMMARY.json`). Every input link is listed with a `status` and, on failure, the reason — nothing is silently dropped.

**Is the output in the same order as my input?** Yes. Companies are emitted in the order you listed them; within a company, jobs follow Lever's own order. (Duplicate links to the same company are scraped once.)

**Can I get every job in English?** Yes — enable `translateToEnglish`. Lever has no server-side "force English" option (the API returns each job exactly as the company wrote it, and a single board can mix languages), so this auto-translates non-English titles/descriptions for free. Originals are preserved in `titleOriginal` / `descriptionTextOriginal`, and only non-English rows are translated.

**Can it discover every company on Lever?** No — Lever has no public directory of all boards (true for every ATS scraper). You supply the companies you care about.

**How many jobs per company?** Whatever they have open — use `maxItems` to cap total output and control cost.

**How fresh is the data?** Live — every run hits Lever in real time.

***

### Support

Found a bug or need a field added? Open an issue on the actor's **Issues** tab in the Apify Console.

***

### Additional services

Need a different ATS or job board? We also build scrapers for Ashby, Greenhouse, Workday, Indeed, LinkedIn, Glassdoor, and many more. Check our Apify Store profile.

***

### Explore more scrapers

- **Ashby / Greenhouse / Lever Jobs Scrapers** — open roles from those ATS platforms.
- **Workday Jobs Scraper** — jobs from any Workday career site.
- **Indeed / LinkedIn / Glassdoor** — the major job boards, fast and structured.

***

### 🤖 For AI Agents & LLM Apps

Compact reference for AI agents calling this actor via the [Apify MCP server](https://mcp.apify.com) or the Apify API (actor: `memo23/lever-scraper`).

**Purpose:** Scrape every open job from any company hosted on Lever (jobs.lever.co) through Lever's public postings API — one request per company returns the full board with descriptions, compensation, location, and apply URL.

**Minimal input:**

```json
{ "startUrls": ["https://jobs.lever.co/spotify"], "maxItems": 50 }
```

**Output:** one dataset row per open job — ats, org, company, companyTotalJobs, jobId, globalId, title, department, team, employmentType, workplaceType, isRemote, location, locations, compensation, salaryText, descriptionHtml, descriptionText, publishedAt, jobUrl, applyUrl, scrapedAt. A separate `company-summary` dataset carries one coverage row per input link (inputOrder, inputUrl, resolvedOrg, embedded, status, totalJobsFound, jobsScraped, error).

**Behaviors an agent should know:**

- Supply companies via `startUrls` (board/job/careers-page URLs or bare slugs) and/or `organizations` (bare slugs); the two are merged and output keeps input order.
- Always set `maxItems` — one large board can exceed 700 roles, and the cap fills from the top of the input list.
- Filters (`titleKeyword`, `location`, `department`, `employmentType`, `remoteOnly`, `postedAfter`) apply before a row is emitted, so you only pay for matches.
- `translateToEnglish` auto-translates non-English rows for free; `includeDescription` off yields leaner rows; `includeRawJson` attaches the raw Lever payload; `enrichEmails` (and dependent `qualifyByPayment`) are opt-in best-effort, off by default.
- No proxy required — Lever's API has no anti-bot; the `proxy` field is optional IP rotation.
- Pay-per-event billing — see the Pricing tab on the actor page.

***

### ⚠️ Disclaimer

This actor collects only **publicly available** job-posting data exposed by Lever's own public postings API. It does not access private, authenticated, or personal data, and does not bypass any access control. You are responsible for using the scraped data in compliance with Lever's terms, the source companies' terms, and all applicable laws (including GDPR/CCPA). Use the data ethically and lawfully.

***

### SEO Keywords

Lever scraper, Lever.co scraper, Lever jobs scraper, jobs.lever.co scraper, Lever postings API, Lever ATS scraper, scrape Lever jobs, Lever careers scraper, ATS job scraper, tech jobs scraper, startup jobs scraper, job postings API, company careers scraper, hiring data, recruiting data.

# Actor input Schema

## `startUrls` (type: `array`):

Lever board URLs (e.g. https://jobs.lever.co/spotify), individual job URLs, or a company's own careers page that embeds a Lever board (e.g. https://www.company.com/careers). Bare slugs also accepted. Output keeps your input order, and a separate 'company-summary' report shows the job count + status for every link.

## `organizations` (type: `array`):

Bare Lever org slugs, e.g. \["dlocal", "voodoo"]. The slug is the part after jobs.lever.co/. Merged with any Start URLs above.

## `maxItems` (type: `integer`):

Hard cap on job rows emitted across the whole run (across all companies). Use it to control billing — large boards like OpenAI have 700+ open roles.

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

How many company boards to fetch in parallel.

## `titleKeyword` (type: `string`):

Keep only jobs whose title contains this text (case-insensitive), e.g. "engineer".

## `location` (type: `string`):

Keep only jobs whose location (primary or secondary) contains this text, e.g. "remote", "London", "New York".

## `department` (type: `string`):

Keep only jobs whose department contains this text, e.g. "Engineering".

## `employmentType` (type: `string`):

Keep only jobs with this exact employment type.

## `remoteOnly` (type: `boolean`):

Keep only jobs flagged remote.

## `postedAfter` (type: `string`):

Keep only jobs published on or after this date (YYYY-MM-DD). Ideal for scheduled monitoring runs that only want fresh roles.

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

Include `descriptionHtml` and `descriptionText` on each row. Turn off for leaner, cheaper rows when you only need title/location/compensation/links.

## `includeRawJson` (type: `boolean`):

Attach the original, unmodified Lever job object to each row under a `raw` field. Useful for advanced fields not in the normalized schema. Off by default to keep rows lean.

## `enrichEmails` (type: `boolean`):

If enabled, tries to find a contact email for the company by discovering its website (Clearbit) and reading its contact/about pages. Adds contactEmail + contactWebsite columns plus a detailed emailEnrichment object. Best-effort.

## `qualifyByPayment` (type: `boolean`):

Requires "Enrich with contact emails". Scans each business's website — reusing the pages already fetched for email discovery, so no extra cost or time — for payment processors and e-commerce platforms (Stripe, Shopify, PayPal, Paddle, Lemon Squeezy, WooCommerce, Square, Chargebee and more). Adds takesPayments (is this a real paying business?), paymentProcessors (which stack), stripeLiveKey (the public key if exposed) and paymentConfidence. Turn raw contacts into monetization-qualified leads. No extra charge — included with each enriched company.

## `translateToEnglish` (type: `boolean`):

Lever returns each job in the language the company wrote it in (a board can even mix languages). Enable this to auto-translate non-English titles/descriptions to English — free, no API key. Originals are kept in `titleOriginal`/`descriptionTextOriginal`, plus `detectedLanguage` + `translated` flags. Only non-English rows are translated. Works best with 'Include full job description' ON. Best-effort.

## `proxy` (type: `object`):

Leave empty — the actor already routes all traffic through its own built-in residential proxy at no extra cost to you. Only set this if you want to use your own proxies.

## Actor input object example

```json
{
  "startUrls": [
    "https://jobs.lever.co/spotify"
  ],
  "organizations": [
    "dlocal",
    "voodoo"
  ],
  "maxItems": 5000,
  "maxConcurrency": 10,
  "remoteOnly": false,
  "includeDescription": true,
  "includeRawJson": false,
  "enrichEmails": false,
  "qualifyByPayment": false,
  "translateToEnglish": false
}
```

# 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 = {
    "startUrls": [
        "https://jobs.lever.co/spotify"
    ],
    "organizations": [
        "dlocal",
        "voodoo"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("memo23/lever-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 = {
    "startUrls": ["https://jobs.lever.co/spotify"],
    "organizations": [
        "dlocal",
        "voodoo",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("memo23/lever-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 '{
  "startUrls": [
    "https://jobs.lever.co/spotify"
  ],
  "organizations": [
    "dlocal",
    "voodoo"
  ]
}' |
apify call memo23/lever-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,memo23/lever-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/MODSbHcBjmLJFtjFv/builds/2XPnCR9taz0lzrPPs/openapi.json
