# Ashby Salary Scraper - Startup Job Pay Ranges (`datagrit/ashby-salary-scraper`) Actor

Ashby job postings with normalized annual salary ranges, equity flags and new-since-last-run detection.

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

## Pricing

Pay per event

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

### What does Ashby Salary Scraper do?

Ashby Salary Scraper reads the public job boards of companies that hire through Ashby and returns every posting as a clean record, with the published salary range converted to yearly amounts. Pay is the point: you can keep only postings with a salary, set a minimum annual pay, pick the currency of the pay range, and get equity, bonus and commission flags plus every location pay tier next to each role. Export the data as JSON, CSV or Excel, call it through the Apify API, or plug it into n8n, Make and AI agents through MCP.

### Who is it for?

- **Recruiters and sourcers** who want a feed of new roles at fast-growing companies, filtered by team and location.
- **Job seekers and career coaches** who compare pay ranges across startups before negotiating.
- **Compensation and HR analysts** who benchmark salary bands by role, level, location and currency.
- **Sales teams** who use hiring as a buying signal and want to know which companies are opening roles right now.

### How to use it

1. Enter the companies you want as Ashby board names or URLs. The name is the last part of the careers link: `jobs.ashbyhq.com/ramp` means the name is `ramp`. Some board names contain spaces, such as `jobs.ashbyhq.com/the%20browser%20company`; paste the link as it is or type `the browser company`.
2. Add filters if you need them: title keywords, department, team, location, fully remote only, only postings with a salary, minimum annual salary, salary currency, or how recent the posting is.
3. For a recurring feed, switch on **Only postings new since my last run** and schedule the Actor. Each run then returns only the matching postings it has not delivered to you before.
4. Download the dataset or fetch it from the API.

### Example output

| company | title | location | workplaceType | salaryMin | salaryMax | salaryCurrency | payTierCurrencies | salaryAnnualMax |
|---|---|---|---|---|---|---|---|---|
| ramp | Security Engineer, Cloud | New York, NY (HQ) | Hybrid | 211400 | 290600 | USD | USD | 290600 |
| zapier | Finance Manager, Marketing | NAMER | Remote | 158300 | 237500 | USD | USD, CAD | 237500 |

```json
{
  "company": "zapier",
  "id": "cb1aec2c-05cd-4598-8117-bd1f7ed9a49f",
  "title": "Finance Manager, Marketing",
  "department": "Finance",
  "employmentType": "FullTime",
  "location": "NAMER",
  "isRemote": true,
  "workplaceType": "Remote",
  "publishedAt": "2026-09-21T20:13:45.355Z",
  "hasSalary": true,
  "salaryMin": 158300,
  "salaryMax": 237500,
  "salaryCurrency": "USD",
  "salaryInterval": "1 YEAR",
  "salaryAnnualMin": 158300,
  "salaryAnnualMax": 237500,
  "equityOffered": true,
  "bonusMentioned": true,
  "commissionMentioned": false,
  "salarySummary": "$158.3K - $237.5K",
  "payTiers": 2,
  "payTierCurrencies": "USD, CAD",
  "payTierSummaries": "USD $158.3K – $237.5K • Offers Equity • Offers Bonus | CA$158.3K – CA$237.5K • Offers Equity • Offers Bonus",
  "jobUrl": "https://jobs.ashbyhq.com/zapier/cb1aec2c-05cd-4598-8117-bd1f7ed9a49f",
  "found": true
}
```

### What data do you get?

Each record contains the title, department, team, employment type, primary and secondary locations with country, region and city plus one allLocations column that joins them, the workplace type and Ashby's remote flag, publication date and age, the posting and application links, and the compensation: the headline range as published with its interval and currency, the same range annualised, equity, bonus and commission flags, the salary text shown on the posting, the number of location-based pay tiers with their currencies and summaries. The full description text is available on request.

Annualising multiplies monthly pay by 12, weekly by 52, daily by 260 and hourly by 2080. Amounts stay in the currency of the headline range: the Actor does not convert currencies, so combine the minimum salary filter with the currency filter.

#### What the live boards looked like

Measured by us on 30 September 2026 on the live Ashby API: the openai board listed 838 postings, 677 of them with a salary; ramp listed 155 postings, 148 with a salary (144 yearly, 4 monthly ranges); zapier listed 11 postings, 9 with a USD headline range, 8 of which add a Canadian pay tier. Boards such as deel and vercel existed but had no open postings.

#### Remote, hybrid and on-site

Ashby sets its `isRemote` flag to true for hybrid roles as well: on openai all 524 Hybrid postings carried it (measured 30 September 2026). The **Remote roles only** switch therefore keeps workplace type Remote and drops Hybrid and on-site roles; on that day it returned 29 of 838 openai postings (24 marked Remote, 5 without a workplace type whose location says remote, such as "Bangalore - Remote").

### How much does it cost?

You pay per posting returned. Pricing depends on your Apify plan: a small fee when a run starts, then a price per result that is lower on paid plans. The Apify free plan includes monthly credit you can use to try it. The status row of an empty run is never charged, and you can set a maximum spend on the run: the Actor stops when the limit is reached. It reads a public API over plain HTTP, so runs are fast and light on platform resources.

### Input

- **Companies** – Ashby board names or URLs, letter case does not matter and names with spaces work. Boards that do not exist, and entries that are not a board name or an Ashby link, are skipped and listed in the run status; if none of them can be read, the run fails.
- **Job title keywords** – match words in the job title.
- **Departments, Teams** – match words in the department, or in the team one level below it. Companies name departments differently (OpenAI files sales roles under Go To Market), so check a first run.
- **Locations** – match whole words or phrases in `allLocations` (primary and secondary locations, city, region and country): `US` matches "Remote (US)" but not "Australia" or "Houston".
- **Remote roles only** – workplace type Remote; Hybrid and on-site are excluded.
- **Only postings with a published salary** – skip postings without a pay range.
- **Minimum annual salary, Salary currencies** – pay filters on the headline range (`salaryCurrency`). A posting with a USD headline and an extra CAD tier counts as USD; every tier currency is in `payTierCurrencies`.
- **Published within days** – at most N × 24 hours old.
- **Only postings new since my last run, State key** – incremental feed, see the FAQ.
- **Include description text** – adds the plain-text description.
- **Maximum results** – total limit for the run.

### Is it legal to scrape this data?

The Actor reads only the public job board API that Ashby offers to employers for publishing their open roles. It does not log in, bypass access controls or collect candidate data. Job postings can contain personal data such as a recruiter name in the description, so if you request descriptions you are responsible for handling them in line with applicable data protection law. This description is not legal advice.

### FAQ

**Which companies are covered?** Any company that publishes its roles through Ashby. Board names that return "not found" and entries that are not a board name or an Ashby link are listed in the run status, so you can spot typos, and boards that exist but have no open postings are named there too, even when your filters removed every posting from the other boards.

**Why do some postings have no salary?** Employers decide whether to publish pay. Use the salary filter to keep only postings that show it.

**How does the only-new mode work?** The Actor keeps a memory in a storage on your account, separately for every company and every combination of filters. Only postings actually returned to you are remembered, so a posting cut off by the result limit or excluded by your filters comes back in a later run, and a second schedule with different filters gets its own complete feed. Two schedules with identical filters share one memory unless you give each a different state key. Postings that leave the board are forgotten after 30 days. Your first run returns everything that matches.

**What happens when a run finds nothing?** You get one free status row with `found: false`, and the run status says why: the board has no open postings, your filters matched nothing, or there is nothing new since your last run.

**How fresh is the data?** Every run reads the live job boards.

**Something looks wrong.** Open an issue with the input you used; changes at the source are fixed quickly.

### Related Actors

Other public-data Actors from the same publisher are listed on the Store profile.

# Changelog

This Actor's version history is a separate document: https://apify.com/datagrit/ashby-salary-scraper/changelog.md

# Actor input Schema

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

Ashby job board names or URLs, one per company. The name is the last part of the careers URL: jobs.ashbyhq.com/ramp means the name is ramp; names with spaces work too (jobs.ashbyhq.com/the%20browser%20company or the browser company). Boards that do not exist and entries that are not a board name or an Ashby link are skipped and listed in the run status; if none of the boards can be read, the run fails.

## `keywords` (type: `array`):

Optional. Keep a posting when its job title contains any of these words (case-insensitive), for example engineer or designer. Leave empty for all roles.

## `departments` (type: `array`):

Optional. Keep only postings whose department contains one of these words (case-insensitive), for example Engineering or Sales. Companies name departments differently (OpenAI files sales roles under Go To Market), so check the department column of a first run.

## `teams` (type: `array`):

Optional. Keep only postings whose team, the level below the department, contains one of these words (case-insensitive), for example Backend or Sales. Combined with departments, both must match.

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

Optional. Keep only postings whose primary or secondary locations, country, region or city contain one of these as a whole word or phrase, for example London, Germany or US (US matches Remote (US) but not Australia). Countries appear as the employer writes them, for example USA or United States.

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

Keep only fully remote postings: workplace type Remote. Hybrid and on-site roles are excluded even though Ashby flags Hybrid roles as isRemote. When the employer did not set a workplace type, the isRemote flag decides, and without it the word remote in the primary location.

## `onlyWithSalary` (type: `boolean`):

Skip postings that do not show a salary range. Employers in some regions must publish pay, so this is a quick way to build a pay dataset.

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

Keep only postings whose top salary, converted to a yearly amount, reaches this value in the currency of the headline range (salaryCurrency). 0 disables the filter. Monthly, weekly, daily and hourly pay is annualised (12, 52, 260 and 2080 periods). Currencies are not converted, so combine it with the currency filter.

## `salaryCurrencies` (type: `array`):

Optional. Keep only postings whose headline salary range (salaryMin/salaryMax, output field salaryCurrency) is in one of these currencies, as three-letter codes such as USD, GBP or EUR. A posting with a USD headline and an extra Canadian pay tier counts as USD; all tier currencies are listed in payTierCurrencies.

## `publishedWithinDays` (type: `integer`):

Keep only postings published at most N x 24 hours before the run. 0 disables the filter.

## `onlyNewSinceLastRun` (type: `boolean`):

Return only postings this Actor has not already delivered to you for the same company and the same filters. The memory is kept in a storage on your account, separately for every company and every combination of filters (and state key), and only postings actually returned in a run are remembered, so postings cut off by the result limit or excluded by other filters come back later. The first run returns everything that matches.

## `stateKey` (type: `string`):

Optional name for the only-new memory. Two schedules with the same filters share one memory; give each a different state key (for example team-a and team-b) when each of them must receive every new posting.

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

Adds the plain-text job description, cut to 8000 characters. Off by default to keep the dataset small.

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

Stop after this many postings in total across all companies.

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

Optional proxy. Leave disabled: the Ashby job board API is public and needs none.

## Actor input object example

```json
{
  "companies": [
    "ramp"
  ],
  "keywords": [],
  "departments": [],
  "teams": [],
  "locations": [],
  "remoteOnly": false,
  "onlyWithSalary": false,
  "minAnnualSalary": 0,
  "salaryCurrencies": [],
  "publishedWithinDays": 0,
  "onlyNewSinceLastRun": false,
  "stateKey": "",
  "includeDescription": false,
  "maxItems": 50,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

All extracted records as a dataset.

# 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": [
        "ramp"
    ],
    "maxItems": 50,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("datagrit/ashby-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": ["ramp"],
    "maxItems": 50,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("datagrit/ashby-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 '{
  "companies": [
    "ramp"
  ],
  "maxItems": 50,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call datagrit/ashby-salary-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,datagrit/ashby-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/psYOomVAr9JDzv52K/builds/d3FP37yKyh6i7rRLk/openapi.json
