# levels.fyi Salary Scraper — Tech Compensation & Salary Data API (`herus13/levels-fyi-salary-scraper`) Actor

Scrape tech compensation data from levels.fyi: base salary, stock, bonus, and total compensation by company, job family, level, and location. Per-offer records or aggregated percentile bands.

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

## Pricing

from $4.00 / 1,000 salary records

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/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

## levels.fyi Salary Scraper — Tech Compensation Data

levels.fyi Salary Scraper is an Apify actor that extracts tech compensation data from [levels.fyi](https://www.levels.fyi) — per-offer salary records and per-level aggregate/percentile bands — by company, job family, and location.

Use it to benchmark comp bands before a negotiation, build a compensation-intelligence dashboard, track how a company's pay moves over time, or feed a recruiting/market-research pipeline — exported to JSON, CSV, or Excel.

### Table of contents

- [What the levels.fyi Salary Scraper does](#what-the-levelsfyi-salary-scraper-does)
- [Use cases](#use-cases)
- [How to scrape levels.fyi salary data](#how-to-scrape-levelsfyi-salary-data)
- [levels.fyi scraper input](#levelsfyi-scraper-input)
- [What data you get](#what-data-you-get)
- [Pricing](#pricing)
- [Recommended proxies for levels.fyi](#recommended-proxies-for-levelsfyi)
- [Why this levels.fyi scraper](#why-this-levelsfyi-scraper)
- [FAQ](#faq)
- [Rate this actor](#rate-this-actor-)
- [Related actors](#related-actors)

### What the levels.fyi Salary Scraper does

- 💰 **Per-offer records** — individual self-reported comp entries: base, stock, bonus, total compensation, title, years of experience, and location.
- 📊 **Per-level aggregates** — company/level rollups with percentile bands (p10–p90) for total compensation and base salary, plus sample count.
- 🏢 **Company + job family targeting** — pass explicit company slugs (`google`, `meta`) and a job family (`software-engineer`, `product-manager`, `data-scientist`), or enable **Discover all companies** to enumerate the whole levels.fyi company index.
- 🌍 **Location filters** — narrow to specific levels.fyi location slugs (`united-states`, `india`), or leave empty for the default all-location view.
- ⚡ **HTTP-only, no anti-bot** — no login, no CAPTCHA, no browser tier; runs are fast and proxy is optional.

### Use cases

**Negotiation preparation** — Per-level aggregate and percentile bands show not just the median for a title at a company but the spread, which is the number that actually matters when deciding what to ask for.

**Compensation-intelligence dashboards** — Query by company, job family, and location to build the internal benchmark a compensation team needs, refreshed on a schedule rather than bought once a year.

**Tracking how a company's pay moves** — Running the same company set over months shows band drift, which tends to lead public announcements about compensation policy.

**Recruiting and market research** — Per-offer records alongside aggregates let a recruiter quote a credible range for a role in a specific market instead of a national average that fits nobody.

### How to scrape levels.fyi salary data

1. Click **Try for free** and open the actor.
2. Enter one or more `companies` (slugs like `google`, `meta`) **or** turn on `discover_all` to enumerate every company on levels.fyi.
3. Set `job_family` (defaults to `software-engineer`) and, optionally, `locations`.
4. Choose `mode` — **one per run**: `records` (one row per individual offer, the default) **or** `aggregates` (one summary row per level: averages + percentile bands). Each mode yields a clean single-schema dataset; run twice if you want both.
5. Click **Start** and watch results stream into the dataset.
6. Export as **JSON, CSV, or Excel**, or pull from the [Apify API](https://docs.apify.com/api/v2).

Individual salary records for two companies (the default):

```json
{
  "companies": ["google", "meta"],
  "job_family": "software-engineer",
  "mode": "records"
}
```

Aggregated salary bands only, capped discovery run across the whole company index:

```json
{
  "discover_all": true,
  "max_companies": 50,
  "mode": "aggregates"
}
```

### levels.fyi scraper input

| Field | Type | Default | Description |
|---|---|---|---|
| `companies` | string\[] | — | Company names or levels.fyi slugs (e.g. `google`, `Meta` — auto-slugified). Provide this or enable `discover_all`. |
| `job_family` | string | `software-engineer` | levels.fyi job-family slug, e.g. `software-engineer`, `product-manager`, `data-scientist`. |
| `locations` | string\[] | — | Optional levels.fyi location slugs for metro-specific pay. Empty = nationwide. See [Locations](#locations) for common slugs. |
| `mode` | enum | `records` | One per run. `records` = one row per individual offer; `aggregates` = one summary row per level (averages + percentile bands). Never mixed, so each run's dataset has a single clean schema. |
| `discover_all` | boolean | `false` | Enumerate every company on levels.fyi (ignores `companies`). Use `max_companies` to cap. |
| `max_companies` | int | — | Cap on how many companies to enumerate when `discover_all` is on. |
| `max_records` | int | — | Cap on per-offer salary records per company/location. |
| `transport` | enum | `auto` | HTTP engine: `auto` (curl\_cffi), `curl_cffi`, `httpx`, or `primp`. |
| `proxy` | object | — | Optional Apify Proxy configuration. Not required for correctness — see [Recommended proxies](#recommended-proxies-for-levelsfyi). |

### Locations

Compensation varies widely by metro (SF Bay Area vs. the rest of the US can differ by tens of thousands). Pass one or more `locations` slugs to scrape metro- or country-specific pay; each is scraped separately and the `location_name` output field tells you which one a row belongs to. Leave `locations` empty for nationwide numbers.

**Common US metros:** `san-francisco-bay-area`, `greater-seattle-area`, `new-york-city-area`, `greater-los-angeles-area`, `greater-boston-area`, `greater-chicago-area`, `greater-austin-area`, `greater-dallas-area`, `greater-houston-area`, `atlanta-area`, `greater-san-diego-area`, `raleigh-durham-area`, `greater-detroit-area`, `philadelphia-area`, `phoenix-area`

**Countries:** `united-states`, `india`, `united-kingdom`, `canada`, `germany`, `netherlands`, `ireland`, `australia`, `singapore`, `israel`, `france`, `switzerland`, `poland`, `japan`, `brazil`, `mexico`, `spain`, `sweden`

**Any other location:** open it on levels.fyi and copy the last path segment of the URL — `…/salaries/software-engineer/locations/`**`<slug>`**.

### What data you get

#### Aggregated salary bands (`mode: aggregates`)

One summary row per company/level. Sample from a live run (Google, L3, software-engineer, United States):

```json
{
  "company": "Google",
  "level": "l3",
  "job_family": "software-engineer",
  "level_name": "L3",
  "scraped_at": "2026-07-17T07:09:42.077676+00:00",
  "count": 33,
  "base": 161788,
  "stock": 34280,
  "bonus": 8702,
  "total": 204770,
  "tc_p10": 176000,
  "tc_p25": 208000,
  "tc_p50": 293000,
  "tc_p75": 403000,
  "tc_p90": 486500,
  "base_p10": 151000,
  "base_p25": 168000,
  "base_p50": 200000,
  "base_p75": 218000,
  "base_p90": 242000,
  "location_name": "United States"
}
```

| Field | Description |
|---|---|
| `company`, `level`, `job_family` | Company name, level identifier, and job family the aggregate covers. |
| `count` | Number of self-reported data points behind this rollup. |
| `base`, `stock`, `bonus`, `total` | Average annual base salary, stock grant value, bonus, and total compensation (USD). |
| `tc_p10`…`tc_p90` | Total-compensation percentile bands where levels.fyi exposes them; unpublished percentiles are `null`. |
| `base_p10`…`base_p90` | Base-salary percentile bands, same null-if-unpublished rule. |
| `location_name`, `scraped_at` | Location the aggregate covers and capture timestamp. |

#### Individual salary records (`mode: records`, default)

One row per self-reported offer. Sample from a live run (Google, L3):

```json
{
  "company": "Google",
  "level": "L3",
  "uuid": "b6e2f5b0-7c9a-4b8e-9d1a-2f6a8c3d5e9f",
  "scraped_at": "2026-07-17T09:12:44.118203+00:00",
  "title": "Software Engineer",
  "job_family": "software-engineer",
  "focus_tag": "DevOps",
  "years_of_experience": 1,
  "years_at_company": null,
  "offer_date": null,
  "location": "Los Angeles, CA",
  "country_id": null,
  "dma_id": null,
  "base_salary": 140000,
  "avg_annual_stock_grant_value": 20000,
  "avg_annual_bonus_value": null,
  "total_compensation": 160000,
  "gender": null
}
```

| Field | Description |
|---|---|
| `company`, `level`, `title`, `focus_tag` | Company, level, job title, and specialization tag (e.g. `DevOps`) as self-reported. |
| `years_of_experience`, `years_at_company` | Reporter's tenure, when disclosed. |
| `location` | Free-text city/region as reported. |
| `base_salary`, `avg_annual_stock_grant_value`, `avg_annual_bonus_value`, `total_compensation` | Annualized compensation components (USD). |
| `uuid`, `scraped_at` | levels.fyi's own record identifier and capture timestamp. |

### Pricing

This actor uses **pay-per-event** pricing — you pay for what you scrape, not for time. Pricing below is **provisional** until Console monetization is finalized (see the Monetization tab for current live pricing).

| Event | USD | Per 1,000 |
|---|---|---|
| Actor start (per run) | $0.001 | — |
| Salary record scraped (`salary-record`) | $0.002 | $2 |
| Salary aggregate scraped (`salary-aggregate`) | $0.001 | $1 |

| Typical run | Cost |
|---|---|
| 1 company, aggregates (~7 levels) | ~$0.008 |
| 1 company, records (~50 offers) | ~$0.101 |
| 10 companies, records (~500 offers) | ~$1.001 |

### Recommended proxies for levels.fyi

**Proxy is optional.** levels.fyi's public salary pages are server-rendered and require no login or CAPTCHA, so a proxy is not required for correctness — only useful for scale (avoiding shared-IP rate limits on large `discover_all` runs).

If you run your own scrapers (inside or outside Apify) and need reliable proxies for scale, we use **[DataImpulse](https://dataimpulse.com/?aff=404588\&utm_source=apify)** — pay-as-you-go IPs with per-country targeting and no monthly minimum:

👉 **[Get DataImpulse proxies](https://dataimpulse.com/?aff=404588\&utm_source=apify)** (referral link)

### Why this levels.fyi scraper

- **No anti-bot tax** — HTTP-only, no browser, no CAPTCHA solving; runs are fast and cheap because levels.fyi's public salary pages need none of that.
- **Two granularities, one actor** — per-offer records for the raw distribution, or per-level aggregates with percentile bands; pick one per run for a clean single-schema dataset.
- **Company discovery built in** — `discover_all` enumerates the full levels.fyi company index instead of requiring you to hand-curate slugs.
- **Validated output** — every row is Pydantic-validated before it's pushed; malformed entries are dropped, not shipped with garbage fields.
- **Open source** — the underlying `levels-fyi-scraper` Python package ships a Typer CLI and a FastAPI server; the Apify wrapper is a thin layer.

### FAQ

**What's the difference between records and aggregates?** Records are individual self-reported offers (one row per person) — pick this (the default) for the raw, granular data. Aggregates are per-level rollups levels.fyi computes from those same reports — one row per level with averages plus percentile bands — pick this for quick benchmarking. You choose one `mode` per run so each dataset stays a single clean schema; run the actor twice if you want both.

**Do I need a proxy?** No. levels.fyi's public salary pages have no known anti-bot layer, so a proxy is optional. It only helps at scale on large `discover_all` runs. For your own scrapers, we recommend [DataImpulse](https://dataimpulse.com/?aff=404588\&utm_source=apify).

**Why are some percentile fields `null`?** levels.fyi doesn't publish every percentile band (p10/p25/p75, etc.) for every company/level/location combination — only the bands it actually surfaces are populated; the rest are `null` rather than guessed.

**What does `discover_all` do, and how do I limit it?** It ignores `companies` and enumerates every company slug on levels.fyi's public index instead. Set `max_companies` to cap how many it processes — useful to control run cost and duration.

**Can I scrape multiple job families or locations in one run?** One `job_family` per run by design (it's part of the URL levels.fyi serves). `locations` accepts multiple slugs, and each is fetched per company.

**Is scraping levels.fyi legal?** This actor collects only publicly available, aggregated and self-reported compensation data. You are responsible for complying with levels.fyi's terms and applicable laws. Do not use this data to identify or target individual reporters.

### Rate this actor ⭐

If the levels.fyi Salary Scraper saved you time, please **leave a review on its Apify Store page** — ratings help other people find it and tell us what to build next. Hit a bug or missing field? Open an issue or contact us through the actor's **Issues** tab and we'll fix it fast — recency and reliability are what keep this actor ranking.

### Related actors

Building a compensation or hiring-intelligence pipeline? Pair this actor with our other scrapers — same proxy config format, same Pydantic-validated output, all open source.

- **[Indeed Job Scraper](https://apify.com/herus13/indeed-scraper)** — cross-reference open roles at the same companies you're benchmarking here.
- **[LinkedIn Jobs Scraper](https://apify.com/herus13/linkedin-jobs-scraper)** — pull live job postings to pair against comp bands.
- **[Google Play App Search & Reviews Scraper](https://apify.com/herus13/google-play-reviews-scraper)** — another HTTP-only, no-anti-bot actor if you're assembling a lightweight-scrape toolkit.

# Actor input Schema

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

<p>Company names or levels.fyi slugs, e.g. <code>google</code>, <code>Meta</code>. Leave empty and enable Discover all companies to enumerate every company.</p>
## `job_family` (type: `string`):

levels.fyi job-family slug, e.g. software-engineer, product-manager, data-scientist.

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

<p>Optional levels.fyi location slugs for metro-specific pay (comp varies a lot by area — e.g. SF Bay Area vs. the rest of the US). Each location is scraped separately and the output <code>location_name</code> field tells you which one a row is for. Empty = nationwide.</p><p><b>Common US metros:</b> <code>san-francisco-bay-area</code>, <code>greater-seattle-area</code>, <code>new-york-city-area</code>, <code>greater-los-angeles-area</code>, <code>greater-boston-area</code>, <code>greater-chicago-area</code>, <code>greater-austin-area</code>, <code>greater-dallas-area</code>, <code>greater-houston-area</code>, <code>atlanta-area</code>, <code>greater-san-diego-area</code>, <code>raleigh-durham-area</code>, <code>greater-detroit-area</code>, <code>philadelphia-area</code>, <code>phoenix-area</code>.</p><p><b>Countries:</b> <code>united-states</code>, <code>india</code>, <code>united-kingdom</code>, <code>canada</code>, <code>germany</code>, <code>netherlands</code>, <code>ireland</code>, <code>australia</code>, <code>singapore</code>, <code>israel</code>, <code>france</code>, <code>switzerland</code>, <code>poland</code>, <code>japan</code>, <code>brazil</code>, <code>mexico</code>, <code>spain</code>, <code>sweden</code>.</p><p><b>Any other location:</b> open it on levels.fyi and copy the last path segment of the URL (…/salaries/software-engineer/locations/<b>&lt;slug&gt;</b>).</p>
## `mode` (type: `string`):

<p>Pick ONE per run — each produces a clean, single-schema dataset (mixing the two would make the CSV/Excel export full of empty cells).</p><ul><li><b>Individual salary records</b> — one row per submitted offer: level, focus/specialization, years of experience, exact base + stock + bonus + total comp, and city. Best for granular analysis, negotiation, or building your own stats.</li><li><b>Aggregated salary bands</b> — one summary row per level (e.g. L3, L4, L5): headcount, average base/stock/bonus/total, and total-comp & base percentiles (p10/p25/p50/p75/p90). Best for quick benchmarking — "what does an L5 make here?" — without crunching raw offers.</li></ul>
## `discover_all` (type: `boolean`):

Enumerate every company on levels.fyi (ignores the Companies list). Use Max companies to cap.

## `max_companies` (type: `integer`):

Cap on how many companies to enumerate when Discover all companies is on.

## `max_records` (type: `integer`):

Cap on per-offer salary records per company/location.

## `transport` (type: `string`):

HTTP engine used to fetch pages. Auto uses curl\_cffi.

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

Proxy settings. Apify Proxy recommended.

## Actor input object example

```json
{
  "companies": [
    "google",
    "meta"
  ],
  "job_family": "software-engineer",
  "locations": [
    "san-francisco-bay-area"
  ],
  "mode": "records",
  "discover_all": false,
  "transport": "auto"
}
```

# Actor output Schema

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

Per-offer salary records and/or per-level aggregates for each requested company/job-family/location

# 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": [
        "google",
        "meta"
    ],
    "job_family": "software-engineer",
    "locations": [
        "san-francisco-bay-area"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("herus13/levels-fyi-salary-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": [
        "google",
        "meta",
    ],
    "job_family": "software-engineer",
    "locations": ["san-francisco-bay-area"],
}

# Run the Actor and wait for it to finish
run = client.actor("herus13/levels-fyi-salary-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 '{
  "companies": [
    "google",
    "meta"
  ],
  "job_family": "software-engineer",
  "locations": [
    "san-francisco-bay-area"
  ]
}' |
apify call herus13/levels-fyi-salary-scraper --silent --output-dataset

```

## MCP server setup

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

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/duDNHEGeq9xGrzfgl/builds/VGyiLZhLZdTlSwlfY/openapi.json
