# Job Salary Data Scraper: Pay Transparency Jobs Feed (`leadsbrary/pay-transparency-salary-jobs-feed`) Actor

Job salary data scraper for Greenhouse, Lever and Ashby boards: returns only postings with a disclosed pay range (min, max, currency, period, annual pay, country, remote). Export JSON, CSV, Excel.

- **URL**: https://apify.com/leadsbrary/pay-transparency-salary-jobs-feed.md
- **Developed by:** [Alexandre Manguis](https://apify.com/leadsbrary) (community)
- **Categories:** Jobs, Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.36 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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

## Job Salary Data Scraper: Pay Transparency Jobs Feed

**Job salary data scraper for public Greenhouse, Lever and Ashby company job boards.** It returns only postings that disclose a numeric pay range, normalised to min, max, currency and period, plus an annualised min/max/mid so rows are comparable. Export as JSON, CSV, Excel or via the API.

### What does it do?

You give it company job boards (for example `greenhouse:coinbase`, `lever:outreach`, `ashby:ramp`, or a public board URL), or switch on the built-in list of 35 boards. The Actor reads the public board APIs and keeps **only the postings with a disclosed salary range**. Postings without pay data are dropped, so you are never billed for them.

Each row contains the raw range (`salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod`), annualised values (`annualSalaryMin`, `annualSalaryMax`, `annualSalaryMid`), the range width in percent, a title family, a seniority guess, the country, a remote flag and the job URL.

### Who is it for / use cases

- **Compensation and HR teams**: benchmark advertised pay for a role across competitors.
- **Recruiters and sourcers**: find companies that publish pay and filter by country, remote or minimum salary.
- **Job seekers and career coaches**: build a salary-transparent job list.
- **Data teams and journalists**: track pay-transparency disclosure over time (pay-transparency laws in several US states and the EU push employers to publish ranges).
- **Job boards and newsletters**: feed a "jobs with salary" section.

### Features

- Only postings with a numeric pay range are returned and billed.
- Supports Greenhouse, Lever and Ashby public boards; vendor is auto-detected from a board URL.
- Normalises periods (year, month, hour, ...) to annual figures. No FX conversion: values stay in the posting's own currency.
- Filters: ATS vendor, countries, title keywords, remote only, minimum annual salary, currencies.
- Optional plain-text description (up to 5,000 characters).
- Stops cleanly when your maximum charge for the run is reached.
- Invalid board entries are logged and skipped.

### Input

| Field | Type | Description |
|---|---|---|
| `companies` | string list | Boards to scan: `greenhouse:stripe`, `lever:outreach`, `ashby:ramp` or a public board URL. |
| `useBuiltInSeedList` | boolean | Also scan the built-in list (35 boards, limited by `maxCompanies`). Default `false`. |
| `atsSources` | list | `greenhouse`, `lever`, `ashby`. Default all three. |
| `countries` | string list | ISO 3166-1 alpha-2 codes (US, GB, DE). Empty = all. |
| `titleKeywords` | string list | Keep titles containing any of these words. |
| `remoteOnly` | boolean | Keep only remote postings. |
| `minAnnualSalary` | integer | Minimum annualised maximum, in the posting's own currency. |
| `currencies` | string list | ISO 4217 codes (USD, EUR). Empty = all. |
| `maxResults` | integer | Stop after this many rows (each is billed). Default 100. |
| `maxCompanies` | integer | Safety cap on boards scanned. Default 50. |
| `includeDescription` | boolean | Add the plain-text description. Default `false`. |
| `proxyConfiguration` | object | Apify datacenter proxy (default on). |

At least one of `companies` or `useBuiltInSeedList` is required; otherwise the run fails with a clear message.

### Output

Real rows from a production run (Coinbase board):

```json
{
  "jobId": "greenhouse:coinbase:8093264",
  "ats": "greenhouse",
  "companySlug": "coinbase",
  "companyName": "Coinbase",
  "title": "Accounting Manager, GL Operations & Intercompany",
  "titleFamily": "finance",
  "seniority": "manager",
  "department": "Finance",
  "employmentType": null,
  "location": "Remote - USA",
  "country": "US",
  "isRemote": true,
  "salaryMin": 166345,
  "salaryMax": 195700,
  "salaryCurrency": "USD",
  "salaryPeriod": "YEAR",
  "annualSalaryMin": 166345,
  "annualSalaryMax": 195700,
  "annualSalaryMid": 181022.5,
  "rangeWidthPct": 17.6,
  "salaryLabel": "Annual base salary range (excluding equity and bonus)",
  "salaryRangesCount": 1,
  "salaryRanges": [{"min": 166345, "max": 195700, "currency": "USD", "period": "YEAR", "label": "Annual base salary range (excluding equity and bonus)"}],
  "salarySource": "greenhouse.pay_input_ranges",
  "salaryPeriodSource": "label",
  "jobUrl": "https://www.coinbase.com/careers/positions/8093264?gh_jid=8093264",
  "applyUrl": null,
  "publishedAt": "2026-07-29T00:03:36.000Z",
  "updatedAt": "2026-09-28T17:18:44.000Z",
  "description": null,
  "scrapedAt": "2026-10-05T15:37:30.737Z"
}
```

An hourly posting looks like `salaryMin: 40, salaryMax: 40, salaryPeriod: "HOUR", annualSalaryMid: 83200`. Some boards publish a single value, so min can equal max (`rangeWidthPct: 0`).

#### Output fields & fill rates

Fill rates are measured on production runs and depend on the boards you scan. The first table comes from the medium run `KGv42gkaxhtTFQtjc` (40 rows). That run returned **Greenhouse rows only** (`companyName` is filled on every row), so it describes Greenhouse.

| Field | Fill rate (Greenhouse rows, 40/40 unless stated) |
|---|---|
| `jobId`, `ats`, `companySlug`, `companyName`, `title`, `titleFamily`, `jobUrl` | 100% |
| `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod`, `salaryLabel`, `salaryRanges`, `salaryRangesCount`, `salarySource`, `salaryPeriodSource` | 100% |
| `annualSalaryMin`, `annualSalaryMax`, `annualSalaryMid`, `rangeWidthPct` | 100% |
| `department`, `location`, `country`, `isRemote`, `publishedAt`, `updatedAt`, `scrapedAt` | 100% |
| `seniority` | 87.5% (inferred from the title; null when the title has no seniority word) |
| `employmentType`, `applyUrl` | 0%: Greenhouse's public board API does not provide them |
| `description` | 0% unless `includeDescription` is enabled |

Mixed proof run `K7KmU5rLiHj9hLAyK` (input `lever:outreach` + `ashby:ramp`, 20 rows, first row from Ashby `ramp`, `salarySource: "ashby.compensationTiers"`). Its rows are most likely Ashby only, because the 20-row cap is reached on the first board, so it is treated as an Ashby proof:

| Field | Fill rate (20 rows) |
|---|---|
| `employmentType`, `applyUrl` | 100% |
| `companyName`, `updatedAt` | 0% (not provided for these vendors) |
| `salaryLabel` | 35% |
| `seniority` | 40% |
| `description` | 0% unless `includeDescription` is enabled |
| all other fields in the table above | 100% |

The per-row vendor split of that run is not recorded in the report, so it does not prove Lever. Lever is proven by a dedicated Lever-only run (`INPUT.lever-only.json`: `lever:outreach` + `lever:zoox`, `atsSources: ["lever"]`, 10 rows), listed in `TEST_REPORT.json` under `production.proof_runs`; the extractor is also covered by unit tests on a real Lever fixture.

What differs by vendor (from the extractor code, covered by unit tests, and consistent with the runs above):

| Field | Greenhouse | Lever | Ashby |
|---|---|---|---|
| `companyName`, `updatedAt` | filled | always null (use `companySlug`) | always null (use `companySlug`) |
| `employmentType`, `applyUrl` | always null | filled when the board sets them | filled when the board sets them |
| `salaryLabel` | filled (pay-range title) | always null | filled when the tier has a title |
| `salaryPeriod` source | inferred from the range label (`salaryPeriodSource: "label"`) | structured | structured |

### How to use via API

**curl**

```bash
curl -X POST "https://api.apify.com/v2/acts/~pay-transparency-salary-jobs-feed/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"companies": ["greenhouse:coinbase", "lever:outreach", "ashby:ramp"], "remoteOnly": true, "maxResults": 50}'
```

**JavaScript**

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });
const run = await client.actor('YOUR_USERNAME/pay-transparency-salary-jobs-feed').call({
    companies: ['greenhouse:coinbase', 'ashby:ramp'],
    countries: ['US'],
    minAnnualSalary: 150000,
    maxResults: 50,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.length, items[0]);
```

**Python**

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("YOUR_USERNAME/pay-transparency-salary-jobs-feed").call(run_input={
    "companies": ["greenhouse:coinbase", "lever:outreach"],
    "remoteOnly": True,
    "includeDescription": False,
    "maxResults": 50,
})
items = client.dataset(run["defaultDatasetId"]).list_items().items
print(len(items), items[0])
```

### Pricing

Pay per result: **one event per salary row delivered**. Price per 1,000 results depends on your Apify plan:

| Plan | Per 1,000 results |
|---|---|
| Free | $0.54 |
| Bronze | $0.47 |
| Silver | $0.41 |
| Gold and above | $0.36 |

Platform usage (compute, proxy) is billed separately by Apify; in the measured medium run it was about $0.0004 for 40 rows (roughly $0.01 per 1,000 results). Set `maxResults` or the run's maximum charge to cap spend.

### Troubleshooting

- **"Invalid input: Provide companies or enable useBuiltInSeedList"**: add at least one board or turn on the built-in list.
- **Few or no rows**: not every company publishes pay ranges, and only matching postings are returned. Loosen filters (countries, keywords, `minAnnualSalary`) or add more boards.
- **A board is skipped**: check the log; the slug may be wrong or the board not public. Use `ats:slug` or the public board URL.
- **`country` is null**: the location text could not be mapped to a country; use `location`.
- **Currencies differ**: no FX conversion is done; filter with `currencies` or convert downstream.

### FAQ

**Which sources are supported?** Public Greenhouse, Lever and Ashby job boards. Other job sites are not covered.

**Does it log in or use private data?** No. It reads publicly available board data only.

**Is the salary the total compensation?** It is whatever the employer discloses; see `salaryLabel` (often "base salary excluding equity and bonus").

**Can I schedule it?** Yes, use Apify schedules and compare runs via `jobId` and `updatedAt`.

**Is it legal?** The data is published by employers, often because of pay-transparency rules. You remain responsible for your use and for complying with applicable laws and the sites' terms.

### Keywords

job salary data scraper, salary range scraper, pay transparency jobs, jobs with salary, Greenhouse jobs scraper, Lever jobs scraper, Ashby jobs scraper, salary benchmarking data, compensation data, remote jobs salary, job postings API, ATS jobs feed

### Hashtags

\#salarydata #paytransparency #jobsdata #greenhouse #lever #ashby #compensation #recruiting #hrtech #remotejobs

# Changelog

This Actor's version history is a separate document: https://apify.com/leadsbrary/pay-transparency-salary-jobs-feed/changelog.md

# Actor input Schema

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

Boards to scan: 'greenhouse:stripe', 'lever:outreach', 'ashby:ramp' or a public board URL (boards.greenhouse.io/x, jobs.lever.co/x, jobs.ashbyhq.com/x). Invalid entries are logged and skipped.

## `useBuiltInSeedList` (type: `boolean`):

Adds a curated list of 35 public boards known to publish pay ranges (limited by 'Max companies').

## `atsSources` (type: `array`):

Only scan boards of these vendors.

## `countries` (type: `array`):

Keep only postings in these ISO 3166-1 alpha-2 countries (e.g. US, GB, DE). Empty = all.

## `titleKeywords` (type: `array`):

Keep only postings whose title contains any of these words (case-insensitive). Empty = all.

## `remoteOnly` (type: `boolean`):

Keep only postings detected as remote.

## `minAnnualSalary` (type: `integer`):

Keep rows whose annualised maximum is at least this value, in the posting's own currency (no FX conversion).

## `currencies` (type: `array`):

Keep only these ISO 4217 currencies (e.g. USD, EUR). Empty = all.

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

Stop after this many salary rows (each one is billed).

## `maxCompanies` (type: `integer`):

Safety cap on the number of boards scanned per run.

## `includeDescription` (type: `boolean`):

Add the plain-text job description (max 5,000 chars). Makes datasets larger.

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

Datacenter proxy used for the public board APIs.

## Actor input object example

```json
{
  "companies": [
    "greenhouse:coinbase",
    "lever:outreach",
    "ashby:ramp"
  ],
  "useBuiltInSeedList": false,
  "atsSources": [
    "greenhouse",
    "lever",
    "ashby"
  ],
  "countries": [],
  "titleKeywords": [],
  "remoteOnly": false,
  "currencies": [],
  "maxResults": 20,
  "maxCompanies": 50,
  "includeDescription": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

No description

# 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": [
        "greenhouse:coinbase",
        "lever:outreach",
        "ashby:ramp"
    ],
    "maxResults": 20,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("leadsbrary/pay-transparency-salary-jobs-feed").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": [
        "greenhouse:coinbase",
        "lever:outreach",
        "ashby:ramp",
    ],
    "maxResults": 20,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("leadsbrary/pay-transparency-salary-jobs-feed").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 '{
  "companies": [
    "greenhouse:coinbase",
    "lever:outreach",
    "ashby:ramp"
  ],
  "maxResults": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call leadsbrary/pay-transparency-salary-jobs-feed --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,leadsbrary/pay-transparency-salary-jobs-feed"
        }
    }
}
```

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/RzVn3EMF6omTnyjZ9/builds/7LBwK0jBybfQYhuzc/openapi.json
