# Salary Benchmark - US Pay Percentiles for Any Role (`hirebase/salary-benchmark`) Actor

Benchmark any role's pay in a US city or state: annual salary percentiles (p10-p90), the sample size behind them, and where your proposed range sits. Hourly pay is annualized, mislabelled salaries corrected. From live job postings; billed per query.

- **URL**: https://apify.com/hirebase/salary-benchmark.md
- **Developed by:** [Hirebase](https://apify.com/hirebase) (community)
- **Security:** Limited permissions
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $500.00 / 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?

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

## Salary Benchmark - Pay Percentiles for Any Role

Enter a role and a city, state or country, and get **what the market actually pays**: annual salary percentiles from p5 to p95 in the local currency, the number of postings behind them, and - if you give it one - **where your proposed pay range sits**. Built on posted salaries from live job listings, scraped straight from company career pages by [Hirebase](https://www.hirebase.org).

**Why this actor?**

- 🎯 **Where does my range sit?** Put in a proposed range and get its percentile, its distance from p25, the median and p75, and a plain verdict.
- 📏 **Sample size on every answer** - you always see how many postings a number is built on, with a confidence grade and the reasons behind it.
- ⏱ **Hourly roles included** - hourly, daily, weekly and monthly pay is converted to annual, so nurses, warehouse staff and technicians are benchmarked on their whole market.
- 🌍 **Any country** - pay comes back in the benchmark country's currency (US in USD, UK in GBP).
- 🗺 **Compare markets** - list several locations and get one row each.
- 💸 **Billed per query, not per posting** - and never for a location with too little data to answer.

### How to use it

1. Enter the **Job title** (add **Similar titles** to widen the comparison), or paste a public **job posting URL** instead.
2. List one or more **Locations**: `Austin, Texas, United States`, `Texas, United States`, `United States` or `London, England, United Kingdom`.
3. Optionally narrow by experience level, years of experience, workplace type, industry or sub-industry, and turn on **Live postings only** to leave out postings that have closed.
4. Optionally enter a **proposed range** in the location's currency, and whether it's per year, month, day or hour.
5. Run. Each location is one query and one row; a report takes about a minute.

### Example input

```json
{
  "jobTitle": "Senior Software Engineer",
  "locations": ["Austin, Texas, United States"],
  "proposedMin": 160000,
  "proposedMax": 190000,
  "proposedPeriod": "yearly"
}
```

### Output

One row per role and location. Real figures for the example above:

```json
{
  "label": "Senior Software Engineer | Austin, Texas, United States",
  "currency": "USD",
  "period": "yearly",
  "sampleSize": 841,
  "p5": 113147.5,
  "p25": 143000,
  "median": 170950,
  "p75": 202550,
  "p90": 235750,
  "p95": 256000,
  "proposedAnnualMid": 175000,
  "proposedPercentile": 54.1,
  "proposedVerdict": "competitive",
  "proposedVsMedian": 4050,
  "confidence": "high",
  "confidenceNote": "High — defensible for a posted-range decision",
  "postingsFound": 1149,
  "livePostings": 347,
  "expiredPostings": 494,
  "pctDisclosingSalary": 73.2
}
```

Each row also has the percentiles **by experience level**, **by workplace type**, **by location** and **by company size**, the titles compared, and any **fallbacks** - where the search had to be widened because the request was too narrow (a city widened to its state, or fewer years of experience). The full report, including the salary histograms, is saved to the run's key-value store.

### How the numbers are built

The percentiles come from **posted pay ranges** (the midpoint of each), not accepted offers, bonuses, equity or survey data. Hirebase annualizes hourly (x 2,080), daily (x 260), weekly (x 52) and monthly (x 12) pay, and leaves out pay in other currencies, pay that is ambiguous or implausible, and duplicate postings (same employer, title and range).

By default the sample includes postings that closed within the time window - still what the market paid recently, and a bigger sample. Turn on **Live postings only** to compare against open postings alone.

`confidence` is `high`, `medium` or `low`, with `confidenceReasons` saying what holds it back - usually a thin sample, or a search that had to be widened. Treat `low` as directional.

### Great for

- **Setting a posted range** - check a new requisition's range before it goes live, especially where pay-transparency laws apply.
- **Recruiters and agencies** - "this is what the role pays in this market", with the sample size to back it.
- **Job seekers** - know whether an offer is below, within or above market.
- **Comp and people teams** - compare the same role across cities and countries in one run.

### Pricing

Billed **per query**: one row = one role in one location = one charge. A run over three locations is three charges. A location with fewer than 8 postings with pay is not saved and not charged - there's nothing reliable to report. **Max queries** caps a run, so it doubles as a spending limit.

### FAQ

**Do I need an API key?**
No - API access is built into the actor.

**Which countries are covered?**
Any country with enough postings that state pay. Figures are in that country's currency, and the proposed range is read in it too. When you list locations in different countries, the same proposed range is read in each one's currency - run them separately if you mean one fixed amount.

**Why is the sample smaller than the postings found?**
Only postings that state pay can be counted - `pctDisclosingSalary` shows the share - and a few are left out as ambiguous or duplicate.

**Is this legal?**
The data comes from publicly posted job listings on company career pages, accessed via the Hirebase API, and is reported only in aggregate.

# Actor input Schema

## `jobTitle` (type: `string`):

The role to benchmark, e.g. <code>Registered Nurse</code> or <code>Senior Software Engineer</code>. Hirebase widens it with comparable titles in the same pay band.

## `similarTitles` (type: `array`):

Other titles for the same role, one per line, to widen the comparison (e.g. <code>Backend Engineer</code>).

## `jobPostingUrl` (type: `string`):

A public job-posting URL to benchmark instead of a title - Hirebase reads the role from the posting. Takes precedence over the title.

## `experienceLevels` (type: `array`):

Seniority of the role.

## `yoeMin` (type: `integer`):

Compare against postings asking for at least this many years.

## `yoeMax` (type: `integer`):

Compare against postings asking for at most this many years.

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

One per line, each benchmarked separately (one query each): <code>City, State, Country</code>, <code>State, Country</code> or <code>Country</code> - e.g. <code>Austin, Texas, United States</code> or <code>London, England, United Kingdom</code>. Pay is reported in that country's currency. Empty = the whole US.

## `locationTypes` (type: `array`):

Remote, hybrid, or in-person roles.

## `industries` (type: `array`):

Company industry sectors.

## `subIndustries` (type: `array`):

Narrower company sub-industry tags (e.g. <code>Fintech</code>).

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

Compare against postings from the last N days (1-365). Default 90.

## `livePostingsOnly` (type: `boolean`):

Compare against postings that are still open. Off (default) also counts postings that closed within the window above - a bigger sample, and still what the market paid recently.

## `proposedMin` (type: `integer`):

Optional: the low end of a pay range you want to place in the market, in the period below and the location's currency.

## `proposedMax` (type: `integer`):

Optional: the high end of the range.

## `proposedPeriod` (type: `string`):

Hourly, daily and monthly figures are converted to annual (2,080 hours, 260 days, 12 months) before comparing.

## `maxQueries` (type: `integer`):

Safety cap on how many locations this run benchmarks (up to 10). You are charged per query, so this is also your spending cap.

## Actor input object example

```json
{
  "jobTitle": "Senior Software Engineer",
  "locations": [
    "Austin, Texas, United States"
  ],
  "postedWithinDays": 90,
  "livePostingsOnly": false,
  "proposedMin": 160000,
  "proposedMax": 190000,
  "proposedPeriod": "yearly",
  "maxQueries": 3
}
```

# Actor output Schema

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

One dataset item per role and location: pay percentiles and where your range sits.

# 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 = {
    "jobTitle": "Senior Software Engineer",
    "locations": [
        "Austin, Texas, United States"
    ],
    "proposedMin": 160000,
    "proposedMax": 190000
};

// Run the Actor and wait for it to finish
const run = await client.actor("hirebase/salary-benchmark").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 = {
    "jobTitle": "Senior Software Engineer",
    "locations": ["Austin, Texas, United States"],
    "proposedMin": 160000,
    "proposedMax": 190000,
}

# Run the Actor and wait for it to finish
run = client.actor("hirebase/salary-benchmark").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 '{
  "jobTitle": "Senior Software Engineer",
  "locations": [
    "Austin, Texas, United States"
  ],
  "proposedMin": 160000,
  "proposedMax": 190000
}' |
apify call hirebase/salary-benchmark --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,hirebase/salary-benchmark"
        }
    }
}
```

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/Cxy0oyPefvbOmsVcM/builds/uCyhESBIvKXp2ksri/openapi.json
