# Pay Band Calculator — Salary Ranges from Your Own Job Data (`northbell/pay-band-calculator`) Actor

Salary bands from job data you already have: median and p10-p90 yearly pay by department, seniority, title or location, split by currency. Give numbers or whole posting text; bonuses are not read as pay. For pay equity and offers. No scraping.

- **URL**: https://apify.com/northbell/pay-band-calculator.md
- **Developed by:** [Northbell](https://apify.com/northbell) (community)
- **Categories:** Jobs, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 pay bands

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

## Pay Band Calculator

**You already have the job data — your own openings, an ATS export, a competitor feed, the output of a job scraper. What you need is the band:** what does a senior engineer get here, what does the middle 80% look like, and how many jobs is that based on. Give this Actor the jobs, with the pay as numbers or as the posting text, and get **median, p10, p25, p75 and p90 yearly pay** per department, seniority, title or location — one band per currency, never mixed. Nothing is scraped: the Actor only reads what you send.

**$0.005 per band** ($5 per 1,000 bands), plus $0.01 per run.

**The last step of a three-step job data toolkit.** Each step reads data you already have:

1. [Job Posting Parser](https://apify.com/northbell/job-posting-parser): posting text → pay, skills, remote, visa
2. [Job Title Normalizer](https://apify.com/northbell/job-title-normalizer): title → department, seniority, core title
3. **Pay Band Calculator** (this one): jobs → median and p10–p90 yearly pay per group

The rows of the Job Posting Parser and of our [LinkedIn Salary Scraper](https://apify.com/northbell/linkedin-jobs-salary-scraper) go in as they are, and so do other scrapers' rows that have `title` and `description`.

### Example

Input (five jobs: numbers, an hourly rate, and one posting given as text):

```json
{
  "postings": [
    { "title": "Senior Software Engineer", "location": "Austin, TX", "salaryMin": 150000, "salaryMax": 185000, "currency": "USD", "period": "year" },
    { "title": "Software Engineer II", "location": "Remote, US", "salaryMin": 120000, "salaryMax": 150000, "currency": "USD" },
    { "title": "Sr. Software Engineer (Backend)", "location": "New York, NY", "description": "The base salary range for this role is $165,000 - $200,000 per year. Sign-on bonus: $15,000." },
    { "title": "Software Engineer", "location": "Denver, CO", "salaryMin": 55, "salaryMax": 65, "currency": "USD", "period": "hour" },
    { "title": "Product Manager", "location": "Austin, TX", "salaryMin": 140000, "salaryMax": 170000, "currency": "USD" }
  ],
  "groupBy": ["function", "seniority"]
}
```

Output (a real run of this input, shortened):

| function | seniority | currency | postingsWithPay | p10 | median | p90 | lowSample |
|---|---|---|---|---|---|---|---|
| engineering | senior | USD | 2 | 169,000 | 175,000 | 181,000 | true |
| engineering | mid | USD | 2 | 125,820 | 129,900 | 133,980 | true |
| product | mid | USD | 1 | 155,000 | 155,000 | 155,000 | true |

"Senior" and "Sr." land in the same band; the $55–65 hourly rate counts as $124,800 a year; the $15,000 sign-on bonus is not read as pay. With five jobs every band is `lowSample` — with your real data the bands fill up. On 320 real LinkedIn postings (122 with pay) the same run gave 13 bands across USD, CAD, EUR, AUD and INR.

### Run it again and again

- **On a schedule** — save your input as a task and add a **Schedule**: every week or month on that period's postings. Send each run to Google Sheets, Slack or email with an integration on the task.
- **Watch for changes** — Run the same **Group by** each time: the bands line up period by period.
- **In bulk** — Send the whole period's postings in **Postings** in one call.

### How a band is made

- **One number per job:** the middle of its range, as yearly pay (hour × 2,080, day × 260, week × 52, month × 12). A job with only "from X" or "up to X" counts at that number. The period is read from `period` or `salaryPeriod`; a yearly figure already worked out (`annualMin`/`annualMax` or `annualSalaryMin`/`annualSalaryMax`) is used as it is.
- **Pay given as text** is read with the same reader as our [LinkedIn Salary Scraper](https://apify.com/northbell/linkedin-jobs-salary-scraper): an amount counts only when it carries a pay signal of its own, so bonuses, funding and budgets are left out. A bare `$` is read as the local dollar when the location is in Canada, Australia and other dollar countries.
- **Department, seniority and the title without level words** come from the title, with the same rules as the [Job Title Normalizer](https://apify.com/northbell/job-title-normalizer).
- **Currencies are never converted or mixed:** each currency gets its own band.
- **Fewer than 5 jobs with pay:** the band is still returned, with `lowSample: true` and a note.

### What you get

| Field | |
|---|---|
| `function`, `seniority`, `coreTitle`, `location` | the grouping you chose |
| `currency` | the band's currency |
| `postingsWithPay` | jobs in the band with readable pay |
| `postingsWithoutPay` | jobs with the same grouping but no readable pay (they have no currency, so the count is shown on every currency's band of that group) |
| `p10`, `p25`, `median`, `p75`, `p90`, `min`, `max` | yearly pay |
| `lowSample`, `note` | fewer than 5 jobs with pay |

### Use it for

- **Offers and pay reviews** — where a number sits against your own openings or the market data you collected
- **Pay transparency** — the band you will publish, checked against what is already out there
- **Market reports** — bands from any job scraper's output, without spreadsheets

### Pricing

**$0.005 per band** ($5 per 1,000 bands), plus $0.01 per run. A run over 10,000 jobs grouped by department and seniority is typically a few dozen bands. Bands with no pay are free.

### For AI agents

Call with `{"postings": [{"title": "...", "salaryMin": 100000, "salaryMax": 130000, "currency": "USD"}], "groupBy": ["seniority"]}` — a few seconds. Each row has `type: "band"`.

### Related

Bands straight from live LinkedIn postings for a title and place: [LinkedIn Salary Benchmark](https://apify.com/northbell/linkedin-salary-benchmark). Pay, skills, remote and visa per posting: [Job Posting Parser](https://apify.com/northbell/job-posting-parser).

# Actor input Schema

## `postings` (type: `array`):

Your jobs. Each one either with numbers {"title": "...", "location": "...", "salaryMin": 120000, "salaryMax": 150000, "currency": "USD", "period": "year"} (period: year, month, week, day or hour) or with the posting text {"title": "...", "location": "...", "description": "..."} - the pay is then read from the text. Up to 50,000 per run.

## `groupBy` (type: `array`):

One band per combination of these, and per currency. Department, seniority and the title without level words are read from the title with the same rules as the Job Title Normalizer.

## Actor input object example

```json
{
  "postings": [
    {
      "title": "Senior Software Engineer",
      "location": "Austin, TX",
      "salaryMin": 150000,
      "salaryMax": 185000,
      "currency": "USD",
      "period": "year"
    },
    {
      "title": "Software Engineer II",
      "location": "Remote, US",
      "salaryMin": 120000,
      "salaryMax": 150000,
      "currency": "USD"
    },
    {
      "title": "Sr. Software Engineer (Backend)",
      "location": "New York, NY",
      "description": "The base salary range for this role is $165,000 - $200,000 per year. Sign-on bonus: $15,000."
    },
    {
      "title": "Software Engineer",
      "location": "Denver, CO",
      "salaryMin": 55,
      "salaryMax": 65,
      "currency": "USD",
      "period": "hour"
    },
    {
      "title": "Product Manager",
      "location": "Austin, TX",
      "salaryMin": 140000,
      "salaryMax": 170000,
      "currency": "USD"
    }
  ],
  "groupBy": [
    "function",
    "seniority"
  ]
}
```

# Actor output Schema

## `bands` (type: `string`):

Median and p10-p90 yearly pay per group and currency.

# 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 = {
    "postings": [
        {
            "title": "Senior Software Engineer",
            "location": "Austin, TX",
            "salaryMin": 150000,
            "salaryMax": 185000,
            "currency": "USD",
            "period": "year"
        },
        {
            "title": "Software Engineer II",
            "location": "Remote, US",
            "salaryMin": 120000,
            "salaryMax": 150000,
            "currency": "USD"
        },
        {
            "title": "Sr. Software Engineer (Backend)",
            "location": "New York, NY",
            "description": "The base salary range for this role is $165,000 - $200,000 per year. Sign-on bonus: $15,000."
        },
        {
            "title": "Software Engineer",
            "location": "Denver, CO",
            "salaryMin": 55,
            "salaryMax": 65,
            "currency": "USD",
            "period": "hour"
        },
        {
            "title": "Product Manager",
            "location": "Austin, TX",
            "salaryMin": 140000,
            "salaryMax": 170000,
            "currency": "USD"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("northbell/pay-band-calculator").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 = { "postings": [
        {
            "title": "Senior Software Engineer",
            "location": "Austin, TX",
            "salaryMin": 150000,
            "salaryMax": 185000,
            "currency": "USD",
            "period": "year",
        },
        {
            "title": "Software Engineer II",
            "location": "Remote, US",
            "salaryMin": 120000,
            "salaryMax": 150000,
            "currency": "USD",
        },
        {
            "title": "Sr. Software Engineer (Backend)",
            "location": "New York, NY",
            "description": "The base salary range for this role is $165,000 - $200,000 per year. Sign-on bonus: $15,000.",
        },
        {
            "title": "Software Engineer",
            "location": "Denver, CO",
            "salaryMin": 55,
            "salaryMax": 65,
            "currency": "USD",
            "period": "hour",
        },
        {
            "title": "Product Manager",
            "location": "Austin, TX",
            "salaryMin": 140000,
            "salaryMax": 170000,
            "currency": "USD",
        },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("northbell/pay-band-calculator").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 '{
  "postings": [
    {
      "title": "Senior Software Engineer",
      "location": "Austin, TX",
      "salaryMin": 150000,
      "salaryMax": 185000,
      "currency": "USD",
      "period": "year"
    },
    {
      "title": "Software Engineer II",
      "location": "Remote, US",
      "salaryMin": 120000,
      "salaryMax": 150000,
      "currency": "USD"
    },
    {
      "title": "Sr. Software Engineer (Backend)",
      "location": "New York, NY",
      "description": "The base salary range for this role is $165,000 - $200,000 per year. Sign-on bonus: $15,000."
    },
    {
      "title": "Software Engineer",
      "location": "Denver, CO",
      "salaryMin": 55,
      "salaryMax": 65,
      "currency": "USD",
      "period": "hour"
    },
    {
      "title": "Product Manager",
      "location": "Austin, TX",
      "salaryMin": 140000,
      "salaryMax": 170000,
      "currency": "USD"
    }
  ]
}' |
apify call northbell/pay-band-calculator --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,northbell/pay-band-calculator"
        }
    }
}
```

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/xiFza0ey7CeZTmJh9/builds/hjPFmEN6A4ZAd7wOo/openapi.json
