# Ashby Jobs Scraper: Job Board API with Compensation (`arman-bd/ashby-jobs-scraper`) Actor

Scrape any Ashby-hosted job board: title, team, location, employment type, remote flag and published compensation ranges when disclosed. Bulk boards, no login.

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

## Pricing

from $2.00 / 1,000 job scrapeds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Ashby Jobs Scraper: Job Board API with Compensation

![Ashby Jobs Scraper — Every posting on any Ashby board — with the published compensation range when disclosed](https://api.apify.com/v2/key-value-stores/ZQOcNAOHrIgTacAmy/records/ashby-jobs-scraper.jpg)

### What it does

Scrape any Ashby-hosted job board: **title, team, location, employment type, remote flag and published compensation ranges when disclosed**. Bulk boards, no login. Give it a list of boards and it returns one structured record per job.

Ashby is the ATS behind a large slice of well-funded startups, and unlike most ATS feeds, its public posting API can return the **compensation tiers** a company attaches to a role. That makes it the single best free source for salary-transparency data at scale.

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `boards` | array | - | **Required.** Ashby org slugs (`ramp`) or full board URLs. Mixed input is fine, URLs are normalised to slugs automatically. |
| `includeCompensation` | boolean | `true` | Ask Ashby for published salary ranges. Turn off for a slightly smaller, faster payload. |
| `searchTerms` | array | `[]` | Keep only titles containing one of these terms (case-insensitive). Empty = all. |
| `remoteOnly` | boolean | `false` | Keep only postings Ashby flags `isRemote`. |
| `maxJobsPerBoard` | integer | `0` | Cap saved jobs per board **after** filtering. `0` = no limit. |

```json
{
 "boards": ["ramp", "https://jobs.ashbyhq.com/notion"],
 "includeCompensation": true,
 "searchTerms": ["engineer", "security"],
 "remoteOnly": false,
 "maxJobsPerBoard": 100
}
```

**Combinations that make sense.** `searchTerms` and `remoteOnly` combine with AND, a job must match a title term *and* be remote. `maxJobsPerBoard` applies **after** filtering, so `searchTerms: ["engineer"], maxJobsPerBoard: 25` gives you 25 engineering roles, not 25 jobs of which some are engineering. Setting `includeCompensation: false` blanks the three pay fields; it does not drop rows.

### Output

One dataset item per job. Sample record (real values from `ramp`):

```json
{
 "board": "ramp",
 "jobId": "34413f8d-26bf-4bbc-8ade-eb309a0e2245",
 "title": "Security Engineer, Cloud",
 "team": "Backend",
 "location": "New York, NY (HQ)",
 "employmentType": "FullTime",
 "isRemote": true,
 "compensationMin": 211400,
 "compensationMax": 290600,
 "currency": "USD",
 "publishedAt": "2026-04-07T17:12:35.753+00:00",
 "applyUrl": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245/application",
 "descriptionPlain": "ABOUT RAMP\n\nRamp is building the smart infrastructure for finance teams…",
 "scrapedAt": "2026-08-06T12:00:00.000Z"
}
```

| Field | Meaning |
|---|---|
| `board` | The org slug the record came from |
| `jobId` | Ashby's posting UUID, stable, use it as your primary key |
| `title` | Job title, trimmed (Ashby occasionally leaks a leading space) |
| `team` | Ashby's `team`, falling back to `department` when a board only fills one |
| `location` | Primary location string as published |
| `employmentType` | `FullTime`, `PartTime`, `Intern`, `Contract`, `Temporary` |
| `isRemote` | Ashby's own remote flag, not a keyword guess |
| `compensationMin`, `compensationMax` | Annual salary band from the posting's **salary** component. `null` when the company does not disclose pay |
| `currency` | ISO code for the band, e.g. `USD`. `null` when there is no band |
| `publishedAt` | ISO timestamp the posting went live |
| `applyUrl` | Direct application link |
| `descriptionPlain` | Full description as plain text, Ashby's own plain rendering, with an HTML-to-text fallback |
| `scrapedAt` | Run timestamp |

A `RUN_SUMMARY` record in the key-value store carries per-run counts, per-source failures and the applied filters:

```json
{
 "boardsRequested": 2,
 "boardsFailed": 0,
 "failures": [],
 "jobsSaved": 212,
 "jobsWithCompensation": 168,
 "filters": {
 "searchTerms": [],
 "remoteOnly": false,
 "maxJobsPerBoard": 0,
 "includeCompensation": true
 },
 "finishedAt": "2026-08-06T12:00:04.117Z"
}
```

### Use cases

**1. Build a salary-transparency dataset.** Sweep a list of Ashby boards with compensation on, then keep the rows that actually disclose pay.

```json
{
 "boards": ["ramp", "vanta", "notion"],
 "includeCompensation": true
}
```

**2. Track compensation bands by role and region.** Narrow to one job family and run it on a schedule, diff `compensationMin` / `compensationMax` on `jobId` over time.

```json
{
 "boards": ["ramp", "vanta"],
 "searchTerms": ["engineer", "engineering manager"],
 "includeCompensation": true
}
```

**3. Source roles that publish pay up front.** Remote-only, capped, ready to hand to a sourcer.

```json
{
 "boards": ["ramp", "notion", "vanta"],
 "remoteOnly": true,
 "searchTerms": ["designer", "product manager"],
 "maxJobsPerBoard": 50
}
```

### Limits and behaviour

- **Compensation is optional per posting.** Many boards publish no bands at all, `linear`, for example, returns `shouldDisplayCompensationOnJobPostings: false` and empty tiers on every role. Rows are **never dropped** for missing pay; `compensationMin`, `compensationMax` and `currency` come back `null`. Check `RUN_SUMMARY.jobsWithCompensation` to see how much of a run actually carries pay.
- **Salary components only.** Ashby also publishes equity, bonus and commission components. Those have no comparable min/max, so the pay fields report the **salary** component alone, annual when a posting lists both annual and monthly bands.
- **A failing board never aborts the run.** An unknown slug returns 404 and is recorded in `RUN_SUMMARY.failures`; the Actor only errors out if *every* board fails.
- **Transient errors are retried.** 429 and 5xx get three attempts with linear backoff. 404 and malformed payloads fail fast.
- **No pagination.** Ashby returns a board's entire published set in one response, so a run is one request per board plus a 250 ms politeness gap.
- **Public data only.** No authentication, no personal data, no access-control bypass.

### FAQ

**Do I need a proxy?** No. Proxy configuration is not required to run this Actor.

**Do I need an account on the source?** No. You supply no credentials.

**What happens if a source is unavailable?** It is reported in `RUN_SUMMARY.failures` and the run continues with the remaining boards.

**Can I schedule it?** Yes, it is designed for scheduled runs. Diff on `jobId` plus `publishedAt` to spot new roles.

**How do I find a board slug?** Open the company's Ashby careers page: `https://jobs.ashbyhq.com/ramp` → the slug is `ramp`. You can paste the whole URL; the Actor extracts it.

**Why is a board empty?** Either the org has no published roles (an empty `jobs` array is a valid 200 response), or your filters excluded everything. `RUN_SUMMARY` reports listed-vs-matched-vs-saved counts per board, which tells the two apart.

**Can I integrate it with something else?** Yes, Apify API, client libraries, webhooks, scheduled runs, dataset exports (JSON/CSV/Excel) or MCP. Output is structured JSON.

### API example

```bash
curl -X POST "https://api.apify.com/v2/acts/arman-bd~ashby-jobs-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
 -H "Content-Type: application/json" \
 -d '{"boards": ["ramp"], "includeCompensation": true, "maxJobsPerBoard": 25}'
```

# Actor input Schema

## `boards` (type: `array`):

Ashby org slugs or job-board URLs. The slug is the company segment in the board URL. for https://jobs.ashbyhq.com/ramp the slug is 'ramp'. Full URLs are accepted and normalized automatically.

## `includeCompensation` (type: `boolean`):

Request published salary ranges from the Ashby posting API. Compensation is optional per posting. rows without a disclosed range still come back, with null pay fields.

## `searchTerms` (type: `array`):

Keep only jobs whose title contains at least one of these terms (case-insensitive). Leave empty to keep every job.

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

Keep only postings Ashby flags as remote. Leave off to keep on-site and hybrid roles too.

## `maxJobsPerBoard` (type: `integer`):

Cap the number of jobs saved per board after filtering. Set 0 for no limit.

## Actor input object example

```json
{
  "boards": [
    "ramp",
    "https://jobs.ashbyhq.com/notion"
  ],
  "includeCompensation": true,
  "searchTerms": [
    "engineer",
    "designer"
  ],
  "remoteOnly": false,
  "maxJobsPerBoard": 0
}
```

# Actor output Schema

## `items` (type: `string`):

Every record the run produced.

## `runsummary` (type: `string`):

The RUN\_SUMMARY record from the run's key-value store.

# 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 = {
    "boards": [
        "ramp",
        "vanta"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("arman-bd/ashby-jobs-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 = { "boards": [
        "ramp",
        "vanta",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("arman-bd/ashby-jobs-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "boards": [
    "ramp",
    "vanta"
  ]
}' |
apify call arman-bd/ashby-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=arman-bd/ashby-jobs-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/Gxd05RIXLDvfDV2Pl/builds/3aQXMzfw1f6bgelWV/openapi.json
