# Ashby Jobs Scraper - No API Key (`renzomacar/ashby-jobs-scraper`) Actor

Scrape any Ashby job board with real structured salary ranges, remote flags and direct application links. No API key, no login, no proxy.

- **URL**: https://apify.com/renzomacar/ashby-jobs-scraper.md
- **Developed by:** [Renzo Madueno](https://apify.com/renzomacar) (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 $3.00 / 1,000 job scrapeds

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?

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 — Startup Career Sites, No API Key

Scrape any **Ashby job board** by name. No API key, no login, no cookies, no proxy, no browser. Ashby is the ATS behind a large slice of well-funded startups, and unlike almost every other job platform it publishes **real structured salary ranges** — actual `minValue` / `maxValue` / `currencyCode` numbers, not a sentence you have to parse.

If you are building a compensation dataset, a startup job board, or a recruiting pipeline, this is the highest-quality source in the whole ATS category.

***

### What you get, with the fill rate actually measured

Measured across **7 real boards — 1,234 live postings** (ramp, linear, vanta, notion, openai, posthog, replit):

| Field | Fill rate | Notes |
|---|---|---|
| `title` | 100% | |
| `jobId` | 100% | Ashby UUID |
| `department` | 100% | |
| `team` | 100% | Ashby has a real department→team hierarchy |
| `employmentType` | 100% | Normalised: `FULL_TIME`, `INTERNSHIP`, `CONTRACT`, `TEMPORARY` |
| `location` | 100% | Primary location |
| `locations[]` | 100% | Primary plus every secondary location |
| `country` | **99.8%** | From the posting's postal address |
| `postedAt` | 100% | ISO 8601 |
| `jobUrl` | 100% | |
| `applyUrl` | 100% | Direct link to the application form, verified live |
| `descriptionText` | 100% | Ashby ships a genuine plain-text version — no HTML stripping guesswork |
| `descriptionHtml` | on request | Off by default |
| `isRemote` | 100% | **76.7% straight from Ashby's own field** |
| `workplaceType` | **77.6%** | `REMOTE` / `HYBRID` / `ONSITE` |
| `salaryMin` / `salaryMax` / `salaryCurrency` / `salaryInterval` | **79.9%** | See the breakdown below |
| `salaryText` | 79.9% | The range as Ashby displays it, e.g. `"$211.4K – $290.6K"` |
| `compensationPublic` | 100% | Whether the company opted into showing pay |
| `isListed` | 100% | Ashby hides some evergreen pipelines |

#### The salary breakdown that matters

**79.9% of postings carry a pay range**, and the split is the important part:

- **71.8% (886 of 1,234)** come from Ashby's structured compensation object — `salarySource: "ats_structured"`. These are exact numbers the company entered into their ATS. `salaryMin: 211400`, `salaryMax: 290600`, `salaryCurrency: "USD"`, `salaryInterval: "YEAR"`.
- **8.1% (100 of 1,234)** are parsed out of the description text as a fallback — `salarySource: "description"`.

You can filter to only the trustworthy ones with `salarySource === "ats_structured"`, or just set `salaryOnly: true` to drop rows with no range at all.

Ashby's compensation object also carries equity and bonus components. We read the **Salary** component for the numeric fields and keep the human-readable tier summary (`"$211.4K – $290.6K • Offers Equity"`) in `salaryText`, so the equity signal is not lost.

***

### Remote data you can actually trust

This is the other place Ashby beats every alternative. `isRemote` comes back on 100% of rows, and **76.7% of those are Ashby's own `isRemote` boolean** — `remoteSource: "ats_field"`. That is the company's own answer, not a keyword match on a job description.

Measured across the 1,234 postings: **74.3% flagged remote**, `workplaceType` populated on 77.6% with values `REMOTE`, `HYBRID` and `ONSITE`.

Where Ashby leaves the field blank we fall back to reading the location text (`remoteSource: "inferred_location"`, 0.9% of rows) and otherwise return `unknown` with `isRemote: false`. We never guess from the description body — that method flags office-based roles as remote because it matches company boilerplate, so it is not used anywhere in this fleet.

***

### Input

```json
{
  "companies": ["ramp", "notion", "linear"],
  "maxItems": 1000,
  "salaryOnly": true,
  "remoteOnly": false,
  "titleKeywords": ["engineer"],
  "includeCompensation": true
}
```

#### Accepted company formats

```
ramp
https://jobs.ashbyhq.com/ramp
https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245
https://api.ashbyhq.com/posting-api/job-board/ramp
```

Board names are passed through with your casing preserved, because a few Ashby tenants are case-sensitive.

#### Input aliases

- companies: `companies`, `company`, `boards`, `boardNames`, `companyUrls`, `startUrls`
- limit: `maxItems`, `maxResults`, `limit`, `maxJobs`
- keywords: `titleKeywords`, `keywords`, `searchTitle`
- locations: `locationKeywords`, `locations`, `location`
- departments: `departmentKeywords`, `departments`, `department`
- salary filter: `salaryOnly`, `onlyWithSalary`
- descriptions: `includeDescription`, `includeContent`, `fullDescription`

#### Filters

| Option | What it does |
|---|---|
| `maxItems` | Hard ceiling on rows written. **This is your spend cap.** |
| `maxJobsPerCompany` | Keeps one 754-posting board (OpenAI) from consuming the budget |
| `salaryOnly` | Only rows with a populated `salaryMin` |
| `remoteOnly` | Only `isRemote: true` |
| `employmentTypes` | `FullTime`, `Intern`, `Contract`, `Temporary` |
| `titleKeywords` / `locationKeywords` / `departmentKeywords` | Substring matching |
| `postedAfter` | ISO date |
| `includeUnlisted` | Include `isListed: false` pipelines; off by default |
| `includeCompensation` | Ask Ashby for the pay object; on by default |
| `dedupe` | Drops repeat ids and repeat company+title+location |

***

### Output sample

```json
{
  "source": "ashby",
  "companyToken": "ramp",
  "companyName": "ramp",
  "jobId": "34413f8d-26bf-4bbc-8ade-eb309a0e2245",
  "title": "Security Engineer, Cloud",
  "department": "Engineering",
  "team": "Backend",
  "employmentType": "FULL_TIME",
  "location": "New York, NY (HQ)",
  "locations": ["New York, NY (HQ)", "Remote (Canada)", "Remote (US)", "Miami, FL"],
  "country": "USA",
  "isRemote": true,
  "workplaceType": "HYBRID",
  "remoteSource": "ats_field",
  "salaryMin": 211400,
  "salaryMax": 290600,
  "salaryCurrency": "USD",
  "salaryInterval": "YEAR",
  "salaryText": "$211.4K – $290.6K • Offers Equity",
  "salarySource": "ats_structured",
  "compensationPublic": true,
  "postedAt": "2026-04-07T17:12:35.753+00:00",
  "jobUrl": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245",
  "applyUrl": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245/application",
  "isListed": true,
  "scrapedAt": "2026-08-22T04:12:44.031Z"
}
```

Four saved dataset views ship with the Actor: **Job overview**, **Salary ranges**, **Remote & locations** and **Apply links**.

***

### How errors are handled

**Errors never enter your dataset.** Everything that did not work goes to the `FAILURES` record in the run's key-value store, with reasons that mean different things:

- `board_not_found` — HTTP 404. No Ashby board by that name. Verified: Ashby returns a clean 404 for unknown boards.
- `board_empty` — HTTP 200 with `{"jobs": []}`. The board is real, the company has nothing open. Verified live against a board in exactly this state.
- `unresolvable_input` — could not derive a board name.
- `budget_exhausted` — `maxItems` was hit before this board was written. Never skipped silently.
- `fetch_failed` — network or upstream error after retries.

**If nothing was delivered, the run ends FAILED** with the reason breakdown in the message. You will never get a silently empty dataset.

***

### Pricing and the free tier

Pay per event:

- **Actor start** — one small charge per gigabyte of memory
- **Job scraped** — charged **after** the row is written

Rows are pushed in batches and the per-job event fires only for rows that landed. Filtered-out postings, deduplicated repeats and failures cost you nothing.

**The free tier returns real data.** No API key, no proxy, no credential of any kind — everything here is a public JSON endpoint. `{"companies": ["linear"], "maxItems": 10}` on a free account returns ten real jobs with real salary ranges. No code path throws because you are missing a paid feature.

***

### Speed

One HTTP request per board, gzip-compressed, regardless of how many postings it holds. Measured: **1,234 postings across 7 boards in 1.4 seconds**. Retries use exponential backoff on 429/5xx and never fire on a 404.

Ashby's payloads are large because descriptions come inline — a 136-posting board is about 2.3 MB of JSON — but gzip handles it and it is still a single round trip. Default memory of 1 GB comfortably handles a run of several thousand postings.

***

### Common questions

**Do I need an Ashby API key?** No. `api.ashbyhq.com/posting-api/job-board/<name>` is the public posting API Ashby provides so boards can syndicate listings. The authenticated Ashby API is a different product.

**How do I find a company's board name?** It is the path segment in their Ashby URL. `jobs.ashbyhq.com/ramp` means the name is `ramp`. Companies embedding the board on their own domain still load it from `jobs.ashbyhq.com` — check the network tab or page source.

**Why does a board return zero jobs?** Because the company genuinely has nothing open. It is reported as `board_empty`, distinct from a 404, so you can tell "not on Ashby" from "on Ashby, hiring nobody".

**Is the salary always in USD?** No — `salaryCurrency` carries the real currency code from Ashby. Do not assume.

**What is `compensationPublic`?** Ashby's `shouldDisplayCompensationOnJobPostings`. When false, the company chose not to publish pay, which is why some rows have no range even on a board that mostly does.

**Why are some postings `isListed: false`?** Ashby lets companies keep evergreen or talent-pool postings out of the public list. They are excluded by default; set `includeUnlisted: true` to get them.

**How fresh is the data?** Live on every run. Nothing is cached.

***

### Related Actors in this fleet

- **Greenhouse Jobs Scraper** — the largest board population; salary parsed from text at 57%
- **Lever Jobs Scraper** — team, commitment, workplace type and country at ~99–100%
- **Workday Jobs Scraper** — enterprise career sites by URL
- **Startup Jobs Aggregator** — company handles in, one deduplicated schema out across all three startup ATSs

### Legal

This Actor reads a public, unauthenticated JSON endpoint that Ashby publishes so job boards and aggregators can syndicate postings. It sends no credentials, solves no challenges and bypasses no access control. You are responsible for how you use the data, including any applicable data-protection rules.

# Actor input Schema

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

One per line. A board name ("ramp") or any jobs.ashbyhq.com URL. Aliases accepted in raw JSON input: company, boards, boardNames, companyUrls.

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

Hard ceiling on rows written, across all boards. Aliases: maxResults, limit, maxJobs.

## `maxJobsPerCompany` (type: `integer`):

Stops one huge board from eating the whole budget.

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

Keep only postings whose title contains one of these (case-insensitive). Leave empty for all. Aliases accepted in raw input: keywords, searchTitle.

## `locationKeywords` (type: `array`):

Keep only postings whose location or office list contains one of these. Example: "New York", "Remote", "London".

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

Keep only postings flagged remote. Check the remoteSource field to see whether the flag came from the ATS or was inferred.

## `postedAfter` (type: `string`):

ISO date, e.g. 2026-07-01. Drops anything published before it.

## `departmentKeywords` (type: `array`):

Keep only postings whose department or team matches, e.g. "Engineering".

## `employmentTypes` (type: `array`):

FullTime, PartTime, Intern, Contract, Temporary. Empty means all.

## `salaryOnly` (type: `boolean`):

Ashby publishes a structured range on roughly 7 of 10 postings; this keeps only those.

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

Asks Ashby for the structured pay object. Leave on unless you want the smallest possible payload.

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

Plain-text job description. Turn it off for a much smaller dataset.

## `includeHtmlDescription` (type: `boolean`):

Adds the original HTML alongside the text version. Off by default because it roughly triples dataset size.

## `dedupe` (type: `boolean`):

Drops repeats by job id, and repeats of the same company + title + location published twice.

## `includeUnlisted` (type: `boolean`):

Ashby marks some roles isListed=false (evergreen or hidden pipelines).

## `concurrency` (type: `integer`):

How many requests to run at the same time. Lower it if you hit rate limits.

## Actor input object example

```json
{
  "companies": [
    "ramp",
    "notion",
    "linear"
  ],
  "maxItems": 1000,
  "maxJobsPerCompany": 100000,
  "titleKeywords": [],
  "locationKeywords": [],
  "remoteOnly": false,
  "departmentKeywords": [],
  "employmentTypes": [],
  "salaryOnly": false,
  "includeCompensation": true,
  "includeDescription": true,
  "includeHtmlDescription": false,
  "dedupe": true,
  "includeUnlisted": false,
  "concurrency": 5
}
```

# Actor output Schema

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

One row per open role, including the structured salary range when Ashby publishes it.

# 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",
        "notion",
        "linear"
    ],
    "titleKeywords": [],
    "locationKeywords": [],
    "departmentKeywords": [],
    "employmentTypes": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("renzomacar/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 = {
    "companies": [
        "ramp",
        "notion",
        "linear",
    ],
    "titleKeywords": [],
    "locationKeywords": [],
    "departmentKeywords": [],
    "employmentTypes": [],
}

# Run the Actor and wait for it to finish
run = client.actor("renzomacar/ashby-jobs-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",
    "notion",
    "linear"
  ],
  "titleKeywords": [],
  "locationKeywords": [],
  "departmentKeywords": [],
  "employmentTypes": []
}' |
apify call renzomacar/ashby-jobs-scraper --silent --output-dataset

```

## MCP server setup

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