# Lever Jobs API: Postings from Any Lever Board (Global + EU) (`ailu_studio/lever-jobs-api`) Actor

Lever jobs API: every posting on any Lever board, global (jobs.lever.co) and EU (jobs.eu.lever.co), with full description and salary range when published. Bulk board URLs or slugs; keyword, location, team, commitment, remote and date filters.

- **URL**: https://apify.com/ailu_studio/lever-jobs-api.md
- **Developed by:** [Ailu Studio](https://apify.com/ailu_studio) (community)
- **Categories:** Jobs, Lead generation, Developer tools
- **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 postings

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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 API: every posting from any Lever job board, global (jobs.lever.co) and EU (jobs.eu.lever.co), with description and salary

Palantir, Spotify, OLX, Siteimprove and many other companies publish their openings on Lever at `jobs.lever.co/<slug>` or, for EU-hosted accounts, `jobs.eu.lever.co/<slug>`. This Actor reads Lever's public postings API on both instances and returns every open posting as a clean row: title, team, department, commitment, all locations, country, workplace type, creation date, the **full description** (plain text and HTML) and the **salary** when the company publishes it, either Lever's structured salary range or, as a fallback, a pay range written in the posting text (`salarySource` tells you which). A bare slug is tried on the global instance first and then on the EU instance, so EU boards that look "dead" on `api.lever.co` are found automatically. No API key, bulk board input, and $1.50 per 1,000 postings versus $2.00–$3.00 for the most-used Lever Actors in the Store.

### Use cases

- **Job aggregation**: pull postings from many Lever boards (US and EU) into one job board or newsletter.
- **Salary research**: published ranges by team, commitment and country.
- **Hiring signals**: watch which teams and countries a target company is expanding.
- **Candidate alerts**: daily run with `postedWithinDays: 1`, `team` and `remoteOnly`.
- **AI matching**: plain-text descriptions ready for embeddings.

### Input

| Field | Default | Meaning |
|---|---|---|
| `boards` | `["palantir", "spotify"]` | Lever board URLs (`jobs.lever.co/<slug>`, `jobs.eu.lever.co/<slug>`, posting URLs), `lever-eu:<slug>`, or bare slugs. |
| `keywords` | – | Keep postings whose title or description contains any of these words. |
| `locations` | – | Keep postings whose location or country code contains any of these strings. |
| `team` | – | Keep postings whose team or department contains any of these strings. |
| `commitment` | – | Keep postings whose commitment contains any of these strings (e.g. `Full-time`, `Intern`). |
| `remoteOnly` | `false` | Only remote postings. |
| `postedWithinDays` | `0` | Only postings created in the last N days (0 = off). |
| `maxItems` | `200` | Cap on returned postings for the whole run. |

```json
{ "boards": ["https://jobs.lever.co/spotify", "https://jobs.eu.lever.co/siteimprove", "olx"], "team": ["Engineering"], "commitment": ["Full-time", "Permanent"], "postedWithinDays": 30, "maxItems": 500 }
```

### Output

| Field | Type | Meaning |
|---|---|---|
| `id`, `sourceId`, `source` | string | `lever:<slug>:<postingId>`, the posting UUID, `lever` or `lever-eu`. |
| `company`, `companySlug` | string | Company name from the board page and the board slug. |
| `title`, `team`, `department`, `commitment`, `level` | string | Posting title and Lever categories. |
| `locations`, `country` | string\[], string | All locations and ISO country code. |
| `isRemote`, `workplaceType`, `employmentType` | boolean, string, string | remote/hybrid/onsite; employmentType mirrors commitment. |
| `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryInterval`, `salaryRaw` | number, number, string, string, string | Published salary; `null` when none. |
| `salarySource`, `salaryDescription` | string | `salaryRange` or `description`; Lever's salary note. |
| `postedAt` | ISO 8601 | Posting creation time (UTC). |
| `url`, `applyUrl` | string | Posting and application page. The Actor never applies. |
| `descriptionText`, `descriptionHtml` | string | Full description: intro, requirement lists and closing text. |
| `fetchedAt` | ISO 8601 | Run time. |

```json
{
  "id": "lever:siteimprove:0e1585af-dc06-4cc7-93a9-cae389ace737", "sourceId": "0e1585af-dc06-4cc7-93a9-cae389ace737", "source": "lever-eu",
  "companySlug": "siteimprove", "company": "Siteimprove", "title": "Account Executive (DACH) - German Speaking",
  "department": "GTM", "team": "Enterprise Sales", "commitment": "Full-Time", "level": null, "locations": ["Germany"], "country": "DE",
  "isRemote": true, "workplaceType": "remote", "employmentType": "Full-Time", "postedAt": "2026-05-20T15:22:03.742Z", "updatedAt": null,
  "url": "https://jobs.eu.lever.co/siteimprove/0e1585af-dc06-4cc7-93a9-cae389ace737", "applyUrl": "https://jobs.eu.lever.co/siteimprove/0e1585af-dc06-4cc7-93a9-cae389ace737/apply",
  "salaryMin": 76200, "salaryMax": 95250, "salaryCurrency": "EUR", "salaryInterval": "year", "salaryRaw": "EUR 76,200–95,250 per year", "salarySource": "salaryRange",
  "salaryDescription": "The base salary for this role will fall within the posted range …",
  "descriptionText": "Siteimprove is a global leader in digital accessibility, content optimization, …", "descriptionHtml": "<div>\n<p>Siteimprove is a global leader …", "fetchedAt": "2026-11-04T11:40:04.984Z"
}
```

### How it works and limits

- Per board: `GET api.lever.co/v0/postings/<slug>?mode=json` (or `api.eu.lever.co` for EU accounts) returns all postings with descriptions in one response, plus one request to the public board page for the company display name. Requests are spaced 300 ms per host and retried on 429/5xx. Large boards answer slowly (Palantir, 300+ postings and about 6 MB, takes roughly 5 seconds).
- Salary: Lever's structured `salaryRange` is used when present (Siteimprove: 11 of 12 postings on 2026-11-04). Otherwise the Actor looks for a currency range next to words such as "salary", "base" or "pay" in the posting text (Palantir: 203 of 307, Spotify: 21 of 84). A bare `$` is read as USD only for US postings. Postings without either stay `null`.
- Lever's public API has no "updated" timestamp; `postedAt` is the posting creation time.
- Boards are read in the order given; once `maxItems` is reached the remaining boards are skipped and listed in the run summary (`OUTPUT` record).
- Not covered: internal/confidential postings, application forms, candidate data.

### Pricing

Pay per result: **$1.50 per 1,000 rows** (`job` event). Platform usage is billed separately by Apify. Removed/free rows are not charged.

### Legal

Not affiliated with Lever (Employ Inc.) or the employers. Uses the public Lever Postings API only. Data licence/terms: https://github.com/lever/postings-api. Job content belongs to the employers who published it.

### FAQ

**How do I know if a board is on the EU instance?** You do not need to: pass the slug and the Actor tries both. URLs starting with `jobs.eu.lever.co` go straight to the EU API.

**Why is salary sometimes taken from the description?** Many companies (for example Palantir and Spotify) write the pay range in the posting text instead of Lever's salary field. `salarySource: "description"` marks those rows and `salaryRaw` shows the sentence it came from.

**Can I get only new postings?** Use `postedWithinDays: 1` on a daily schedule, or the *Job Change Tracker* Actor for new/updated/removed detection.

**Does it include closed postings?** No. Lever's public API only lists currently open postings.

# Actor input Schema

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

Lever board URLs or slugs, e.g. "https://jobs.lever.co/palantir", "https://jobs.eu.lever.co/olx", a posting URL, or just "spotify". A bare slug is tried on jobs.lever.co first, then on the EU instance.

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

Only return postings whose title or description contains at least one of these words (case-insensitive), e.g. \["engineer", "sales"]. Leave empty for all postings.

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

Only return postings whose location (or country code) contains one of these strings, e.g. \["London", "New York", "DE"]. "Remote" also matches postings with workplace type remote.

## `team` (type: `array`):

Only return postings whose Lever team or department contains one of these strings (case-insensitive), e.g. \["Engineering", "Sales"].

## `commitment` (type: `array`):

Only return postings whose commitment contains one of these strings, e.g. \["Full-time"], \["Intern"] or \["Contract"]. Each company names commitments its own way.

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

When true, only postings with workplace type remote (or a location that says remote) are returned.

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

Only return postings created in the last N days (0 = no date filter). Use 1 for a daily run that only picks up new postings.

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

Upper limit on returned postings for the whole run (default 200). Raise it for full exports; Palantir alone lists 300+ postings.

## Actor input object example

```json
{
  "boards": [
    "palantir",
    "spotify",
    "https://jobs.eu.lever.co/siteimprove"
  ],
  "keywords": [],
  "locations": [],
  "team": [],
  "commitment": [],
  "remoteOnly": false,
  "postedWithinDays": 0,
  "maxItems": 200
}
```

# Actor output Schema

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

All rows in the default dataset.

# 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": [
        "palantir",
        "spotify",
        "https://jobs.eu.lever.co/siteimprove"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("ailu_studio/lever-jobs-api").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": [
        "palantir",
        "spotify",
        "https://jobs.eu.lever.co/siteimprove",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("ailu_studio/lever-jobs-api").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": [
    "palantir",
    "spotify",
    "https://jobs.eu.lever.co/siteimprove"
  ]
}' |
apify call ailu_studio/lever-jobs-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,ailu_studio/lever-jobs-api"
        }
    }
}
```

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/qa2Xi30TZyY68oa6h/builds/5qT1s4hvCdnPu065x/openapi.json
