# Levels.fyi Compensation Scraper (`devilscrapes/levels-fyi-compensation-scraper`) Actor

Scrape verified tech compensation data from Levels.fyi by company — total comp, base salary, stock, bonus, and level breakdowns by job family, normalized into one row per data point for CSV, JSON, or API export.

- **URL**: https://apify.com/devilscrapes/levels-fyi-compensation-scraper.md
- **Developed by:** [DevilScrapes](https://apify.com/devilscrapes) (community)
- **Categories:** Jobs
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.80 / 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/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

<div align="center">
  <img src=".actor/icon.svg" width="160" alt="Devil Scrapes mark" />

## Levels.fyi Compensation Scraper

**💰 $1.82 / 1 000 results**  ·  pay only for results  ·  no credit card to try

*We do the dirty work so your dataset stays clean.* 😈

Scrape verified tech compensation data from Levels.fyi by company — total comp, base salary, stock, bonus, and level breakdowns by job family, normalized into one row per data point for CSV, JSON, or API export.

</div>

***

### 🎯 What this scrapes

Levels.fyi crowdsources tech-industry compensation reports and normalizes them into levels, total comp, base, stock, and bonus per company and job family — the closest thing to a public pay ladder for tech roles. This Actor parses that data straight out of the page's own embedded JSON: point it at one or more companies and it writes one structured row per individual submission (base, stock, and bonus kept separate — the differentiator most compensation scrapers flatten away) or one row per title in a lightweight company-wide overview, ready for comp-benchmarking spreadsheets, recruiting tools, or internal pay-band research.

### 🔥 What we handle for you

- 🎯 **Two granularities, your call** — component-separated individual submissions (base/stock/bonus kept apart) or lightweight company-wide aggregates.
- 🔁 **We retry with exponential backoff** on `408 / 429 / 503` and honour `Retry-After` — up to 5 attempts per page.
- 🌐 **We route every request through Apify Proxy**, residential preferred and pinned to the US, so your dataset isn't tied to one exit IP.
- 🧊 **Clean, typed dataset rows** — Pydantic-validated, ISO-8601 timestamps, deduped by submission ID, JSON / CSV / Excel export straight from the Apify Console.
- 🔍 **Every row carries its submission-count context** — so you always know how much of the underlying sample you're looking at, never presented as more complete than it is.
- 💰 **Pay-Per-Event pricing** — you only pay for rows that hit your dataset. No data, no charge.

### 💡 Use cases

- **Comp benchmarking** — pull Levels.fyi's per-level total comp, base, stock, and bonus for target companies into a spreadsheet or BI tool.
- **Offer negotiation research** — compare a candidate's offer against Levels.fyi's published bands for the same company and job family.
- **Recruiting + TA analytics** — feed normalized, component-separated compensation rows into a sourcing or comp-modeling pipeline.
- **Internal pay-band research** — track how a peer company's published levels and comp move over time.

### ⚙️ How to use it

1. Click **Try for free** at the top of the page.
2. Fill in the input form — most fields have sensible defaults.
3. Click **Start**. Output streams into the run's dataset.
4. Export from **Storage → Dataset** as JSON, CSV, or Excel — or fetch via the API.

### 📥 Input

| Field | Type | Required | Default | Notes |
|---|---|:--:|---|---|
| `mode` | `string` | no | 'company\_job\_family' | 'Company + job family' pulls per-submission compensation rows (base/stock/bonus split) for one or more job families at… |
| `companies` | `array` | **yes** | \['google'] | Levels.fyi company URL slugs, e.g. 'google', 'meta', 'apple' — not arbitrary display names. |
| `jobFamilies` | `array` | no | None | Levels.fyi job-family URL slugs, e.g. 'software-engineer', 'product-manager'. Required (non-empty) when Mode is… |
| `maxResults` | `integer` | no | 200 | Hard cap on dataset rows for this run. |
| `proxyConfiguration` | `object` | no | {'useApifyProxy': True} | Apify Proxy configuration. We resolve RESIDENTIAL pinned to the US automatically at runtime, falling back to our… |

#### Example input

```json
{
  "mode": "company_job_family",
  "companies": [
    "google"
  ],
  "jobFamilies": [
    "software-engineer"
  ],
  "maxResults": 5,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

### 📤 Output

Every row is one dataset item.

| Field | Type | Notes |
|---|---|---|
| `granularity` | `string` | Which mode produced this row — 'individual\_submission' or 'aggregate\_title'. |
| `company` | `string` | Company display name. |
| `company_slug` | `string` | Input companies entry (levels.fyi URL slug). |
| `job_family` | `string` | Job family display name. |
| `job_family_slug` | `string` | Job family URL slug. |
| `title` | `string` | Job title. |
| `level` | `string` | Level code, e.g. 'L3'. Null in overview mode. |
| `track` | `string` | Focus tag / track, e.g. 'ML / AI'. |
| `location` | `string` | Location string. Null in overview mode. |
| `years_of_experience` | `number` | Years of experience. |
| `years_at_company` | `number` | Years at the company. |
| `base_salary` | `number` | Base salary. Null in overview mode — no base/stock/bonus split available at that granularity. |
| `stock_annual_value` | `number` | Average annual stock grant value. |
| `bonus_annual_value` | `number` | Average annual bonus value. |
| `total_compensation` | `number` | Total compensation — per-submission in company\_job\_family mode, median in company\_overview mode. |
| `currency_code` | `string` | ISO currency code. |
| `offer_date` | `string` | ISO 8601 offer date. Null in overview mode. |
| `level_submission_count` | `integer` | The level/title bucket's total submission count. |
| `level_submission_count_last_12_months` | `integer` | The level bucket's trailing-12-month submission count (company\_job\_family mode only) — compare against the rows you… |
| `source_url` | `string` | The fetched page URL this row was parsed from. |
| `scraped_at` | `string` | ISO-8601 UTC row creation timestamp. |

#### Example output

```json
{
  "granularity": "individual_submission",
  "company": "Google",
  "company_slug": "google",
  "job_family": "Software Engineer",
  "job_family_slug": "software-engineer",
  "title": "Software Engineer",
  "level": "L3",
  "track": "ML / AI",
  "location": "New York, NY",
  "years_of_experience": 0.0,
  "years_at_company": 0.0,
  "base_salary": 140000.0,
  "stock_annual_value": 2500.0,
  "bonus_annual_value": 0.0,
  "total_compensation": 142500.0,
  "currency_code": "USD",
  "offer_date": "2026-07-09T17:59:59.627+00:00",
  "level_submission_count": 46,
  "level_submission_count_last_12_months": 1134,
  "source_url": "https://www.levels.fyi/companies/google/salaries/software-engineer",
  "scraped_at": "2026-08-06T09:00:00+00:00"
}
```

### 💰 Pricing

Pay-Per-Event — you pay only when these events fire:

| Event | USD | What it is |
|---|---:|---|
| `actor-start` | $0.02 | One-off warm-up charge per run |
| `result-row` | $0.0018 | Per unique dataset item |

Example: 1 000 results at the rates above ≈ **$1.82**. No subscription, no minimum, no card to start — Apify gives every new account $5 of free credit.

### 🚧 Limitations

Individual-submission rows (`company_job_family` mode) reflect the bounded preview of samples Levels.fyi embeds per level bucket, not its full submission dataset — a bucket with a trailing-12-month count in the thousands might only surface a few dozen sample rows on the page. Every row carries `level_submission_count` and `level_submission_count_last_12_months` so you always know how much of the underlying population a given row-set represents. `company_overview` mode covers only companies and job families Levels.fyi has published; small or under-reported companies may return nothing. This version confirms the default US-locale route only — non-US Levels.fyi pages and non-USD currencies aren't covered yet.

### ❓ FAQ

**Do I need a Levels.fyi account or API key?**

No. This Actor scrapes Levels.fyi's own publicly published compensation pages — no login, no API key.

**What's the difference between the two modes?**

'Company + job family' returns individual-submission rows with base salary, stock, and bonus broken out separately — one row per sample in each level bucket. 'Company overview' returns one aggregate row per title (median total comp + submission count), with no base/stock/bonus split.

**Are the individual-submission rows the complete Levels.fyi dataset?**

No — each level bucket embeds a bounded preview of its submissions, not every one on file. Every row carries `level_submission_count` and `level_submission_count_last_12_months` so you can see exactly how much of the underlying sample you're getting, instead of mistaking a preview for the full population.

**What format do company and job-family slugs need to be in?**

Use Levels.fyi's own URL slugs — e.g. 'google', not 'Google Inc'; 'software-engineer', not 'Software Engineer'. We lowercase and hyphenate spaces as a convenience, but you're responsible for supplying a slug that matches a real Levels.fyi URL.

**What happens if I request a company or job family that doesn't exist?**

That pair returns zero rows and is logged as a failed pair — it never crashes the run. Only if every requested pair comes back empty does the run fail loudly, so you know to double-check your slugs.

### 💬 Your feedback

Spotted a bug, hit a weird edge case, or need a new field? Open an
issue on the Actor's **Issues** tab on Apify Console — we ship
fixes weekly and we read every report.

***

<div align="center">

Built by **[Devil Scrapes](https://apify.com/DevilScrapes)** 😈 — a small fleet of
opinionated public-data Actors. Honest pricing, real engineering, zero fine print.

</div>

# Actor input Schema

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

'Company + job family' pulls per-level compensation rows for one or more job families at each company. 'Company overview' pulls the company-wide summary row instead.

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

Levels.fyi company URL slugs, e.g. 'google', 'meta', 'apple' — not arbitrary display names.

## `jobFamilies` (type: `array`):

Levels.fyi job-family URL slugs, e.g. 'software-engineer', 'product-manager'. Required (non-empty) when Mode is 'Company + job family'; every (company, job family) pair is fetched.

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

Hard cap on dataset rows for this run.

## `proxyConfiguration` (type: `object`):

Apify Proxy configuration. We resolve RESIDENTIAL pinned to the US automatically at runtime, falling back to our standard proxy group if RESIDENTIAL isn't available on your plan.

## Actor input object example

```json
{
  "mode": "company_job_family",
  "companies": [
    "google"
  ],
  "jobFamilies": [
    "software-engineer"
  ],
  "maxResults": 5,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `datasetItems` (type: `string`):

All dataset items as JSON.

## `datasetItemsCsv` (type: `string`):

Same data exported to CSV.

## `datasetView` (type: `string`):

Open the run dataset in the Console.

# 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 = {
    "mode": "company_job_family",
    "companies": [
        "google"
    ],
    "jobFamilies": [
        "software-engineer"
    ],
    "maxResults": 5,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("devilscrapes/levels-fyi-compensation-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 = {
    "mode": "company_job_family",
    "companies": ["google"],
    "jobFamilies": ["software-engineer"],
    "maxResults": 5,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("devilscrapes/levels-fyi-compensation-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "mode": "company_job_family",
  "companies": [
    "google"
  ],
  "jobFamilies": [
    "software-engineer"
  ],
  "maxResults": 5,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call devilscrapes/levels-fyi-compensation-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=devilscrapes/levels-fyi-compensation-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/VNPsweK2rSSF6yTUn/builds/JS0cIHzPfXVqFNyiE/openapi.json
