# Ashby Jobs Scraper & API (`miladamirzadeh/ashby-jobs-scraper`) Actor

Free Ashby jobs scraper: pay only for platform usage. Extract every open job from Ashby job boards with salary ranges and pay tiers, equity, remote/hybrid/on-site type, locations, teams and descriptions. Filter by team, location, workplace, employment type, keywords or date.

- **URL**: https://apify.com/miladamirzadeh/ashby-jobs-scraper.md
- **Developed by:** [Milad Amirzadeh](https://apify.com/miladamirzadeh) (community)
- **Categories:** Jobs, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

## 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

## Ashby Jobs Scraper & API

Collect every open job from one or many Ashby job boards in a single run: titles, locations, teams, departments, employment type, salary ranges with every pay tier, equity and bonus flags, remote/hybrid/on-site type, full descriptions, and publish dates. Use the results for job-board feeds, recruiting research, salary benchmarking, hiring-trend tracking, and scheduled employer snapshots, with no login or API key.

The Actor is free to use: you pay only for Apify platform usage, which is typically a fraction of a cent per run.

Ashby is the applicant tracking system behind the careers pages of companies such as OpenAI, Ramp, Notion, Linear, Snowflake, Cohere, Plaid, and thousands more. This Actor reads Ashby's public job posting API directly over HTTP. It runs without a browser or proxy, and fetched about 2,000 jobs from six large boards in under 5 seconds in testing.

### What you get

- All open jobs for one or many companies per run, from a board name (`openai`) or any board URL
- Specific jobs by URL (`job_details` mode)
- **Salary ranges** with currency and period, across every pay tier (Ashby often has one tier per location or level), **plus salaries stated only in the job description**: Notion, 1Password, Docker and others write pay there instead of in Ashby's compensation fields
- Every **compensation tier** with its components (salary, equity, bonus, commission), and simple `offersEquity`, `offersBonus`, and `offersCommission` flags
- **Workplace type** (Remote, Hybrid, On-site) and **employment type** (Full-time, Part-time, Internship, Contract, Temporary)
- City, region, country, and an ISO **country code**, even though Ashby stores countries as free text ("USA", "United States")
- Every listed location, team, and department
- The real **company name and website**, which the posting API does not include
- Full description as HTML and as plain text
- Filters for workplace type, employment type, department or team, location or country, keywords, publish date, and salary availability
- A per-company run summary showing open, matched, and saved counts, and any board names that were not found

### Quick start

#### All jobs from several companies

```json
{
  "mode": "company_jobs",
  "companySlugs": ["openai", "ramp", "https://jobs.ashbyhq.com/notion"],
  "maxJobsPerCompany": 9999
}
```

#### Filtered: recent remote or hybrid full-time engineering roles in the US

```json
{
  "mode": "company_jobs",
  "companySlugs": ["openai", "ramp", "notion", "cohere", "plaid"],
  "workplaceTypes": ["remote", "hybrid"],
  "employmentTypes": ["fulltime"],
  "filterLocation": "US",
  "jobKeywords": ["engineer", "engineering"],
  "postedAfter": "30 days",
  "maxJobsPerCompany": 200
}
```

#### Specific jobs

```json
{
  "mode": "job_details",
  "jobUrls": [
    "https://jobs.ashbyhq.com/linear/d3bc1ced-3ce4-4086-a050-555055dbb1ff"
  ]
}
```

#### How to find a company's board name

Open the company's Ashby job board or any of its job postings. The board name is the path segment right after `jobs.ashbyhq.com/`:

| URL | Board name |
| --- | --- |
| `https://jobs.ashbyhq.com/openai` | `openai` |
| `https://jobs.ashbyhq.com/ramp/34413f8d-...` | `ramp` |
| `https://jobs.ashbyhq.com/notion/e7c18c11-.../application` | `notion` |

You can paste any of these URLs directly. Case does not matter. Some companies embed their Ashby board on their own careers site; open any job there, and the application link usually points to `jobs.ashbyhq.com/<board>`.

### Input reference

| Field | Type | Default | Behavior |
| --- | --- | --- | --- |
| `mode` | string | `company_jobs` | `company_jobs` lists all open jobs for each company. `job_details` fetches specific jobs. |
| `companySlugs` | array | `["linear"]` | Board names or URLs. Used in `company_jobs` mode. Duplicates are removed. |
| `jobUrls` | array | `[]` | Ashby job URLs or `board/jobId`. Used in `job_details` mode. |
| `includeContent` | boolean | `true` | Save the description as HTML (`content`) and plain text (`descriptionText`). Salaries in the text are read either way. |
| `workplaceTypes` | array | `[]` | Any of `remote`, `hybrid`, `onsite`, using the type the employer set in Ashby. |
| `employmentTypes` | array | `[]` | Any of `fulltime`, `parttime`, `intern`, `contract`, `temporary`. |
| `filterDepartment` | string | empty | Case-insensitive partial match against the department or team. |
| `filterLocation` | string | empty | Case-insensitive partial match against every listed location, city, region, and country. `Remote` also matches jobs with the Remote workplace type. A two-letter country code such as `DE`, `US`, or `UK` matches the country exactly, using the country named in the location when the address has none. A US state name such as `California` also finds its code ("San Francisco, CA"), and state codes that are not country codes (`NY`, `TX`) match the state. |
| `jobKeywords` | array | `[]` | Up to 20 keywords or phrases. A job matches when **any** of them appears as a whole word in its title or description. |
| `postedAfter` | string | empty | `YYYY-MM-DD` or relative (`7 days`). Inclusive, compared as UTC dates. |
| `onlyWithSalary` | boolean | `false` | Keep only jobs with a salary, from Ashby's compensation or the description. |
| `maxJobsPerCompany` | integer | `500` | Maximum jobs saved per company after filtering, newest first. Use `9999` for all. |
| `proxyConfiguration` | object | off | Optional. The API is public, so a proxy is not required. |

All filters that you set must match (AND). Filters apply only in `company_jobs` mode.

Keyword matching uses whole words, so `AI` matches "AI Engineer" but not "email". Add every form you want to catch, such as `engineer` and `engineering`.

### Output

Each saved job looks like this:

```json
{
  "jobId": "34413f8d-26bf-4bbc-8ade-eb309a0e2245",
  "title": "Security Engineer, Cloud",
  "companyName": "Ramp",
  "companySlug": "ramp",
  "companyWebsite": "https://ramp.com",
  "location": "New York, NY (HQ)",
  "allLocations": ["New York, NY (HQ)", "Remote (Canada)", "Remote (US)", "Miami, FL"],
  "city": "New York City",
  "region": "NY",
  "country": "USA",
  "countryCode": "US",
  "workplaceType": "Hybrid",
  "department": "Engineering",
  "team": "Backend",
  "employmentType": "Full-time",
  "salaryMin": 211400,
  "salaryMax": 290600,
  "salaryCurrency": "USD",
  "salaryInterval": "year",
  "salaryDescription": "$211.4K – $290.6K • Offers Equity",
  "salarySource": "ashby",
  "offersEquity": true,
  "offersBonus": false,
  "offersCommission": false,
  "compensationTiers": [
    {
      "title": null,
      "summary": "$211.4K – $290.6K • Offers Equity",
      "components": [
        { "type": "EquityPercentage", "min": null, "max": null, "currency": null, "interval": null },
        { "type": "Salary", "min": 211400, "max": 290600, "currency": "USD", "interval": "year" }
      ]
    }
  ],
  "url": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245",
  "applyUrl": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245/application",
  "content": "<h1><strong>About Ramp</strong></h1><p>Ramp is building the smart infrastructure for finance teams...",
  "descriptionText": "ABOUT RAMP\n\nRamp is building the smart infrastructure for finance teams...",
  "publishedAt": "2026-04-07T17:12:35+00:00",
  "scrapedAt": "2026-10-01T03:00:00+00:00"
}
```

| Field | Meaning |
| --- | --- |
| `jobId` | Ashby job ID (UUID). |
| `title` | Job title. |
| `companyName` / `companySlug` / `companyWebsite` | Company name and website from the hosted Ashby board, and the board name. The name falls back to the board name if unavailable. |
| `location` / `allLocations` | Primary location, and every location the job lists. |
| `city` / `region` / `country` / `countryCode` | Address of the primary location. `country` is as written by the employer; `countryCode` is the ISO code when recognized. |
| `workplaceType` | `Remote`, `Hybrid`, `On-site`, or `null` when the employer left it unset. |
| `department` / `team` | Ashby's department and team. |
| `employmentType` | `Full-time`, `Part-time`, `Internship`, `Contract`, or `Temporary`. |
| `salaryMin` / `salaryMax` / `salaryCurrency` / `salaryInterval` | Salary range across all pay tiers and its period (`year`, `month`, `week`, `day`, `hour`). A single figure has equal min and max. `null` when no salary is stated. |
| `salaryDescription` | Ashby's compensation summary, or the sentence the salary was read from. |
| `salarySource` | `ashby` when the employer filled in Ashby's compensation, `description` when the salary was read from the job text, `null` when neither has one. |
| `offersEquity` / `offersBonus` / `offersCommission` | Whether the published compensation includes these; `null` when no compensation is published. |
| `compensationTiers` | Every pay tier (often one per location or level), with its components: `type` (`Salary`, `EquityPercentage`, `EquityCashValue`, `Bonus`, `Commission`), `min`, `max`, `currency`, and `interval`. |
| `url` / `applyUrl` | Hosted job page and application page. |
| `content` / `descriptionText` | Full description as HTML and as plain text. `null` when `includeContent` is off. |
| `publishedAt` | When the job was published (UTC). |
| `scrapedAt` | When the job was collected (UTC). |

Results appear in the default dataset. Use the **Job overview** view for compact metadata, salaries, and links, or the **Job descriptions** view for text.

#### Run summary

Each run also writes an `OUTPUT` record to the default key-value store. It shows every target's status (`ok`, `not_found`, or `error`) and its open, matched, and saved job counts, so you can spot mistyped board names without reading the log. When no target succeeds, the run is marked as failed.

### API and exports

Run the Actor from your application with the standard Apify API:

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/miladamirzadeh~ashby-jobs-scraper/run-sync-get-dataset-items?token=<APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "companySlugs": ["openai", "ramp"],
    "workplaceTypes": ["remote"],
    "maxJobsPerCompany": 50
  }'
```

You can also use the generated Python, JavaScript, CLI, OpenAPI, and MCP examples in the Actor's **API** tab. Datasets export to JSON, CSV, Excel, XML, and HTML.

### Recurring workflows

Save a tested input as an Apify Task and schedule it daily or weekly. For daily runs, set `postedAfter` to `1 day` so each run saves only jobs published since the day before.

Typical uses:

- Job-board and aggregator ingestion
- Salary benchmarking across companies that publish pay ranges
- Tracking hiring volume by team, location, or workplace type
- Alerts for new roles at target companies, through Apify integrations such as Slack, Google Sheets, Make, or Zapier

Each run produces an independent dataset. The Actor does not keep history between runs.

### Practical limitations

- You need each company's Ashby board name. Ashby has no public directory of all boards.
- Only listed jobs are available. Unlisted and internal jobs are not exposed by the public API.
- Ashby records when a job was published, not when it was last edited, so there is no "updated after" filter.
- Some employers leave the workplace type unset; those jobs are excluded when you filter by workplace type.
- `salaryMin` and `salaryMax` span all pay tiers. When tiers use different currencies, Ashby's summary (and these fields) keep the first currency; every tier is still in `compensationTiers`.
- Salaries read from the description (`salarySource: "description"`) are the first salary the text states. When a job lists several ranges (per city or level), the first one is saved and `salaryDescription` shows its context. Amounts labeled OTE (on-target earnings), bonuses, perks, and business figures such as deal sizes are skipped. A bare `$` is read as USD unless the text names another currency code.
- Filters apply only in `company_jobs` mode.
- Closed jobs return `not_found` in `job_details` mode.

### Pricing

The Actor is **free**. There is no per-run or per-result fee; you pay only for the Apify platform usage (compute units) of your runs, which the Apify free plan's monthly credit covers for most users.

Runs are cheap because the Actor makes plain HTTP requests with no browser or proxy. It runs at 512 MB by default and used about 215 MB in testing. Saving every job from OpenAI, Snowflake, Harvey, ClickHouse, Cohere, and ElevenLabs (about 2,000 jobs) took under 5 seconds.

### Troubleshooting and support

If a run returns no jobs:

1. Open the `OUTPUT` record. A `not_found` status means the board name is wrong or the company does not use Ashby.
2. Open the company's job posting in a browser and copy the board name from the URL.
3. Remove the filters temporarily to rule out an overly narrow match.

For support, open an Actor issue with your input (without tokens), the run link, and what you expected to see.

# Actor input Schema

## `mode` (type: `string`):

<b>Company jobs</b> lists every open job on the given Ashby boards. <b>Job details</b> fetches specific jobs by URL.

## `companySlugs` (type: `array`):

Ashby board names such as <code>openai</code> or <code>ramp</code>, or board URLs such as <code>https://jobs.ashbyhq.com/openai</code>. The board name is the path segment right after <code>jobs.ashbyhq.com/</code>. Case does not matter. Used in <b>Company jobs</b> mode.

## `jobUrls` (type: `array`):

Specific jobs as Ashby URLs (<code>https://jobs.ashbyhq.com/linear/d3bc1ced-3ce4-4086-a050-555055dbb1ff</code>) or <code>board/jobId</code>. Used in <b>Job details</b> mode.

## `includeContent` (type: `boolean`):

Save the full description as HTML (<code>content</code>) and plain text (<code>descriptionText</code>). Turn off for smaller, faster exports. Salaries stated in the description are read either way.

## `workplaceTypes` (type: `array`):

Keep only jobs with any of these workplace types, as set by the employer in Ashby. Jobs where the employer left it unset are excluded when this is used. Leave empty for all.

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

Keep only jobs with any of these employment types. Leave empty for all.

## `filterDepartment` (type: `string`):

Keep only jobs whose department or team contains this text (case-insensitive), e.g. <code>Engineering</code>.

## `filterLocation` (type: `string`):

Keep only jobs where any listed location, city, region or country contains this text (case-insensitive), e.g. <code>London</code> or <code>Germany</code>. <code>Remote</code> also matches jobs with the Remote workplace type. A two-letter country code such as <code>DE</code>, <code>US</code> or <code>UK</code> matches the country exactly (<code>CA</code> is Canada). A US state name such as <code>California</code> also finds its code ("San Francisco, CA"), and state codes that are not country codes (<code>NY</code>, <code>TX</code>) match the state.

## `jobKeywords` (type: `array`):

Keep jobs whose title or description contains <b>any</b> of these keywords as a whole word or phrase (case-insensitive). Up to 20 keywords.

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

Keep jobs published on or after this UTC date. Relative values such as <code>1 day</code> suit scheduled runs.

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

Keep only jobs with a salary, either from the employer's Ashby compensation or stated in the job description (see <code>salarySource</code>).

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

Maximum number of matching jobs saved per company, newest first. Set a high value such as 9999 to save every open role.

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

Optional. Ashby's job posting API is public, so a proxy is not required.

## Actor input object example

```json
{
  "mode": "company_jobs",
  "companySlugs": [
    "openai",
    "ramp"
  ],
  "jobUrls": [],
  "includeContent": true,
  "workplaceTypes": [],
  "employmentTypes": [],
  "jobKeywords": [],
  "onlyWithSalary": false,
  "maxJobsPerCompany": 500,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

## `overview` (type: `string`):

No description

## `details` (type: `string`):

No description

## `summary` (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 = {
    "companySlugs": [
        "openai",
        "ramp"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("miladamirzadeh/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 = { "companySlugs": [
        "openai",
        "ramp",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("miladamirzadeh/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 '{
  "companySlugs": [
    "openai",
    "ramp"
  ]
}' |
apify call miladamirzadeh/ashby-jobs-scraper --silent --output-dataset

```

## MCP server setup

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