# Indeed Salary Scraper — Pay by Title, State & Employer (`thenetaji/indeed-salary-scraper`) Actor

Find out what a role actually pays. One row per job title: the average pay and the 10th-to-90th percentile spread, the same figures for every one of the 51 US regions, the employers paying most, the top-paying cities, and the pay for the titles either side of it on the ladder.

- **URL**: https://apify.com/thenetaji/indeed-salary-scraper.md
- **Developed by:** [The Netaji](https://apify.com/thenetaji) (community)
- **Categories:** Jobs, Business, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $17.00 / 1,000 job titles

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

## Indeed Salary Scraper

Find out what a job title pays. One row per title, carrying the average pay and the 10th to
90th percentile spread, the same figures broken down across all 51 US regions, the employers
paying most for the title, the top-paying cities, the pay for the titles either side of it on
the ladder, and non-salary compensation such as cash bonuses.

All of it comes from one request per title. There is no per-state pass to run and nothing to
paginate.

### Accepted input

`titles` is required and is a list of job titles, one per line. Spaces are fine:
`software engineer` and `software-engineer` are the same request, and a title listed both ways
is priced once.

`location` optionally scopes every title to one city and state, such as `San Francisco, CA`.
Leave it empty unless the local distribution itself is the answer — a nationwide row already
contains the per-region breakdown and the top-paying cities, so scoping costs the same request
and returns less than the nationwide answer already held.

`maxItems` bounds the rows saved. It is unbounded by default here, because the list of titles
is already the bound.

```json
{
  "titles": ["software engineer", "registered nurse", "data analyst"]
}
```

### Response fields

```json
{
  "requested_title": "software-engineer",
  "title": "Software Engineer",
  "country": "US",
  "salary_period": "YEARLY",
  "salary_mean": 147240.69,
  "salary_median": null,
  "salary_standard_deviation": 63041.46,
  "percentile_10": 80008.26,
  "percentile_90": 228992.18,
  "salary_currency": "USD",
  "regions": [
    { "region": "WA", "name": "Washington", "yearly_avg_salary": 114226, "sample_count": 1290 }
  ],
  "top_paying_companies": [
    { "name": "Netflix", "salary_mean": 268400, "salary_count": 214, "rating": 4.2 }
  ],
  "top_paid_cities": [{ "name": "San Jose, CA", "salary": { "mean": 168000 } }],
  "related_titles": [{ "title": "senior software engineer", "salary_mean": 168200 }],
  "compensations": [{ "type": "CASH_BONUS", "mean": 5000 }],
  "last_updated_at": "2026-08-09T14:44:30.000Z"
}
```

Fields absent from a title's aggregates are returned as null rather than omitted, so every row
has the same shape.

`regions` is the field worth building on. One row holds all 51 US regions — every state plus
the District of Columbia — each with its own average and sample count. A measured run for
`software engineer` returned 51 regions and 53 top-paying employers from a single request.

`salary_mean` is the headline figure and `salary_period` says what period it is quoted over.
A title paid by the hour is summarised hourly, so a run mixing hourly and salaried titles must
read the period before comparing two rows. `percentile_10` and `percentile_90` give the shape
of the distribution where the average alone gives only its centre; on the measured run they
were 80,008 and 228,992 around an average of 147,241, which is a far wider band than a single
number suggests.

`national` and `local` carry the full aggregates the flat columns were lifted from, including
the same population restated hourly, daily, weekly, monthly and yearly — useful for comparing
an hourly role against a salaried one without assuming an hours-per-year figure.

`top_paying_companies` names employers, each with its mean, sample count and rating. Every
name in it can be handed to
[Indeed Company Scraper](https://apify.com/thenetaji/indeed-company-scraper) for the full
employer profile.

`related_titles` prices the adjacent rungs of a career ladder without a request per rung.

### Questions

**Should a title be run once per state to build a regional table?**
No, and this is the setting most worth getting right. The per-region breakdown for all 51 US
regions is already in the nationwide row, so running the same title once per state would be 51
requests for a subset of what one request already returned. `location` exists for the case
where the local distribution itself is the subject.

**Why is `salary_median` null when `salary_mean` is populated?**
Because Indeed publishes the average for far more titles than it publishes a midpoint for. A
null median beside a populated mean is normal rather than a failed read; the same applies to
`salary_sample_count`, which is stated on some titles and not on others. The percentile pair is
the more consistently available way to see the spread.

**What happens to a title Indeed has never priced?**
It is skipped with a warning naming the title and the run continues to the rest of the list. A
title with no data answers with a successful request carrying no aggregates rather than with an
error, so one unusual title in a list of forty costs one row.

**Why did a title come back under a different name than the one submitted?**
Because Indeed normalises titles to the ones it sees on postings. `requested_title` is what was
sent after hyphenation and `title` is what Indeed calls it, so keeping both shows what a
free-text request resolved to. If a title returns nothing, the plainer form of it usually
returns something.

**How current are the figures?**
`last_updated_at` states when Indeed last recomputed the aggregates for that title. It is
published per title rather than per run, so two titles in one dataset can carry different
dates.

**Why does `regions` have a sample count and the national row sometimes not?**
The regional entries and the national aggregate are published separately by Indeed and it
states counts more consistently on the regions. Where a national count is absent, the regional
counts are the way to judge how much data is behind a figure.

**Is an Indeed account, cookie or API key required?**
No. Nothing of the kind is supplied to the Actor or needed by it.

### Related Actors

[Indeed Company Scraper](https://apify.com/thenetaji/indeed-company-scraper) profiles the
employers named in `top_paying_companies`: ratings, CEO approval and the work happiness survey.

[Indeed Jobs Scraper](https://apify.com/thenetaji/indeed-jobs-scraper) answers what is open
right now rather than what the role pays, and each of its rows carries the pay range the
employer advertised.

# Actor input Schema

## `titles` (type: `array`):

Job titles to price, one per line. Spaces are fine: `software engineer` and `software-engineer` are the same request. A title the site holds no salary data for is skipped with a line in the log rather than failing the run.

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

Optional city and state to scope every title to, such as `San Francisco, CA`.

Usually leave this empty. A nationwide request already carries a per-region breakdown of all 51 US regions and a ranked set of top-paid cities, so scoping to one place costs the same request and returns less than the nationwide answer already contained.

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

Maximum number of rows to save across the whole run. Set 0 for no limit.

## Actor input object example

```json
{
  "titles": [
    "software engineer"
  ],
  "location": "San Francisco, CA",
  "maxItems": 50
}
```

# Actor output Schema

## `dataset` (type: `string`):

All records scraped by this run

# 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 = {
    "titles": [
        "software engineer"
    ],
    "maxItems": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("thenetaji/indeed-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 = {
    "titles": ["software engineer"],
    "maxItems": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("thenetaji/indeed-salary-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 '{
  "titles": [
    "software engineer"
  ],
  "maxItems": 50
}' |
apify call thenetaji/indeed-salary-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,thenetaji/indeed-salary-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/Z0s3nOSdE5gMIPfBF/builds/zTzZLjSlQpnLcAFWE/openapi.json
