# Ashby Jobs Scraper - Pay Ranges, True Remote Flag, No API Key

Scrape live job postings from any Ashby job board. Numeric salary ranges where the employer publishes them, a remote flag that excludes hybrid roles, company presets and a new-postings-only mode.

Canonical product page: https://mayd-it.com/data-tools/actors/ashby-jobs-scraper/
Public Actor: https://apify.com/maydit/ashby-jobs-scraper

## Example input

```json
{
  "companies": [],
  "preset": "starter",
  "includeCompensation": true,
  "includeDescription": false,
  "remoteOnly": false,
  "maxResultsPerCompany": 10,
  "maxResults": 10,
  "maxRunSeconds": 240,
  "onlyNewSinceLastRun": false
}
```

## Full guide

![Ashby jobs, with published pay — illustrative workflow](https://api.apify.com/v2/key-value-stores/YGxD9JRpduom8Q5Zd/records/ashby-jobs-scraper-f3b8a9ffea05.png?signature=19olxRNtv2zUbFal7ZalW)

## Ashby jobs, with published pay

Build a hiring watchlist for recruiting, job aggregation or pay research from public Ashby boards. Each saved row is a job posting; salary fields are filled only when the employer publishes them.

[Open this Actor in Console](https://console.apify.com/actors/NwsKYMzdQADiQlj6d/input) · [First run](#try-this-actor) · [Results](#result-preview) · [Cost](#usage-and-pricing) · [Full guide](#full-guide)

## Try this Actor

1. **Choose a small scope.** Use one of the board slugs already documented below, such as `ramp`, in **Ashby boards**. Set **Preset board list** to `none` so the preset is not added. Review both the per-company and total posting caps before starting.
2. **Run and review.** Leave new-postings-only mode off for an initial snapshot. Open the run's dataset for job rows and its key-value-store `SUMMARY` for board failures, empty boards and limit stops.
3. **Use the results.** Export the dataset as CSV for flat columns or JSON for nested compensation details. Keep the posting link, pay interval and source board with your analysis.

**Read this before using the data:** A missing pay range is not a zero salary. Remote eligibility follows published workplace/location evidence. A limited or failed board check cannot establish that a role closed.

## Result preview

The dataset view highlights these columns:

- **Company** · `company`
- **Job title** · `title`
- **Department** · `department`
- **Team** · `team`
- **Location** · `location`
- **Remote** · `remote`

Use the original source links and the field definitions below when interpreting the results. CSV works well for flat columns; JSON preserves nested values.

## Usage and pricing

Base result rate on the FREE tier: **$1 per 1,000 results**. The [Pricing tab](https://apify.com/maydit/ashby-jobs-scraper/pricing) shows your plan's result rate and any Actor Start charge. Set a small result limit for the first run; the requested limit is not a guarantee of that many results.

---

## Full guide

Scrape live job postings from any **Ashby job board** and get them back as flat CSV, JSON or Excel rows. Give it a board slug (`openai`) or a board URL (`https://jobs.ashbyhq.com/openai`) and this Ashby scraper returns matching listed openings within your result and time limits: job title, department, team, employment type, primary and secondary locations, the posting and apply links - and two things most job scrapers leave out:

- **Numeric salary ranges.** Where the employer publishes pay on the posting, you get `salaryMin`, `salaryMax`, `salaryCurrency` and `salaryInterval` as separate columns (for example `190000`, `330000`, `USD`, `1 YEAR`), plus an `offersEquity` flag and Ashby's own summary string. Not a `"$190K - $330K"` string you have to parse yourself.
- **A remote flag based on published workplace and location fields.** In recorded sample runs, Ashby's `isRemote` field was also true on hybrid postings (540 of 832 OpenAI postings had that flag, while 24 had workplace type `Remote`). These are historical sample counts, not current board totals. This Actor's `remote` column is `true` only when the workplace type is `Remote` or the posting lists a remote location such as `Remote (US)`, and it gives you the `workplaceType` (`Remote`, `Hybrid`, `OnSite`) alongside it.

It reads the endpoint Ashby documents as its Job Postings API (developers.ashbyhq.com/docs/public-job-posting-api) and calls it **with no API key, no login and no Ashby account**. Pass one company or fifty, or pick a preset list. Built for recruiters, sourcers, compensation analysts, job-board operators and anyone tracking hiring at a list of companies.

This Actor covers **Ashby only**. A company that is not on Ashby cannot be scraped here; see the FAQ for what to use instead.

### What you get

One row per listed job posting.

| Field | What it is |
| --- | --- |
| `company` | Display name for the boards in the presets (`OpenAI`, `Ramp`, ...). Ashby's response carries no company name, so for any other slug this is the slug itself. |
| `slug` | The Ashby board the row came from, so multi-company runs stay separable. |
| `jobId` | Ashby posting ID (a UUID). Stable - use it to dedupe across runs. |
| `title` | Job title. |
| `department` | Department the employer filed the role under. |
| `team` | Team within the department. |
| `employmentType` | As published: `FullTime`, `PartTime`, `Contract`, `Intern`, `Temporary`. |
| `location` | The posting's primary location as the employer wrote it (`New York, NY (HQ)`, `US - Remote`, `Europe`). |
| `secondaryLocations` | Array of the posting's additional locations (`["San Francisco, CA", "Remote (US)"]`). Empty array when none are published. In recorded sample runs, 38% of rows included secondary locations; that share is not a current platform-wide estimate. |
| `remote` | `true` only when `workplaceType` is `Remote` or one of the listed locations names a remote option (text containing `remote` or `distributed`). Hybrid and on-site roles with no remote location are `false`. Ashby's `isRemote` is deliberately not used - see the limits section. |
| `workplaceType` | `Remote`, `Hybrid` or `OnSite` exactly as Ashby publishes it, or empty when the employer left it unset (about 14% of rows in recorded sample runs). One value per posting, so a `Hybrid` posting that also lists `Remote (US)` has `remote` `true`. |
| `salaryMin` / `salaryMax` | Numeric pay range from the posting's published compensation. Uses Ashby's own roll-up when a posting publishes several ranges (by location or level), so it matches the range Ashby shows on the job page. Empty when the employer publishes no pay. |
| `salaryCurrency` | ISO code of the range: examples seen in recorded sample runs include `USD`, `CAD`, `GBP`, `EUR`, `SGD`, `AUD` and `SEK`. |
| `salaryInterval` | Passed through as Ashby publishes it. Recorded sample runs included `1 YEAR`, `1 HOUR` and `1 MONTH`; interpret each row using its own interval. |
| `salaryTiers` | How many separate ranges the posting publishes (`0` = none, `1` = one range, `4` = four location/level tiers rolled up into the columns above). |
| `offersEquity` | `true`/`false` when the posting publishes a compensation block (whether it lists an equity component); empty when it publishes none. The recorded sample runs exposed an equity-offered flag without numeric equity amounts; this Actor has no equity-amount column. |
| `compensationSummary` | Ashby's own summary string for the posting, for example `$190K - $330K`, `Offers Equity`, `Multiple Ranges` joined with Ashby's dash and bullet characters. |
| `publishedAt` | ISO 8601 UTC timestamp of when the posting went live. Rows are sorted newest first *within each board*; boards are written in the order you listed them. |
| `jobUrl` | Public link to the posting on `jobs.ashbyhq.com`. |
| `applyUrl` | Public link to the application form. |
| `descriptionText` | Only when `includeDescription` is on: the description as plain text, **capped at 5,000 characters**. In a recorded 15-board sample run, 1,960 of 2,604 descriptions reached that cap. Treat a capped value as the opening 5,000 characters, not the complete description. |
| `scrapedAt` | ISO 8601 UTC timestamp of the run. |

A `SUMMARY` record is written to the run's key-value store with the boards that succeeded, were empty or failed (and why), postings seen versus saved, how many saved rows carry a salary and how many are remote, a workplace-type breakdown, and whether the run stopped early on its time budget.

### Input

| Field | Type | Default | What it does |
| --- | --- | --- | --- |
| `companies` | string list | empty | Board slugs or URLs, one per company. Case-insensitive. Accepts `openai`, `https://jobs.ashbyhq.com/openai`, `https://jobs.ashbyhq.com/openai/<job-id>/application` and the API URL. Added to the preset's boards. |
| `preset` | select | `starter` in the form | `starter` (Ramp, Plaid, Notion, Linear), `ai-labs` (OpenAI, Harvey, ElevenLabs, Sierra, Perplexity, Cohere, Cognition), `ai-labs-lite` (Perplexity, Cohere, Cognition), `dev-tools` (Linear, Replit, Supabase, Modal, Lovable), or `none`. If you leave both `preset` and `companies` empty, the run uses `starter` as a sample scope. Source availability, filters and run limits still determine whether rows are saved. |
| `includeCompensation` | boolean | `true` | Fetch and parse the compensation block into the salary columns. |
| `includeDescription` | boolean | `false` | Add `descriptionText` (plain text, capped at 5,000 characters). |
| `titleContains` | string | empty | Keep only postings whose title contains this text. Case-insensitive, partial match. |
| `remoteOnly` | boolean | `false` | Keep only rows where `remote` is `true`. Hybrid roles without a remote location are dropped on purpose. |
| `maxResultsPerCompany` | integer | `200` | Row cap per board. Postings are sorted newest-published first, so a low cap keeps the freshest roles. |
| `maxResults` | integer | `500` | Row cap for the whole run. Your `companies` are processed before the preset's boards. |
| `onlyNewSinceLastRun` | boolean | `false` | Save only postings this Actor has not seen on that board before. See the FAQ for exactly how the first run behaves. |
| `maxRunSeconds` | integer | `240` | Wall-clock budget. When it is reached the run stops cleanly, keeps everything already saved, and logs which boards it did not reach. |

### Example output

A real row from OpenAI's board (2026-09-25). `compensationSummary` is shown here with plain ASCII punctuation; Ashby's real string uses an en dash and bullet characters.

```json
{
  "company": "OpenAI",
  "slug": "openai",
  "jobId": "6fd50ace-941b-4559-992f-e53f9b76c9a0",
  "title": "Security Program Manager",
  "department": "Security",
  "team": "Security",
  "employmentType": "FullTime",
  "location": "US - Remote",
  "secondaryLocations": ["New York City", "Seattle", "Washington, DC", "San Francisco"],
  "remote": true,
  "workplaceType": "Hybrid",
  "salaryMin": 129600,
  "salaryMax": 310000,
  "salaryCurrency": "USD",
  "salaryInterval": "1 YEAR",
  "salaryTiers": 3,
  "offersEquity": true,
  "compensationSummary": "$129.6K - $310K | Offers Equity | Multiple Ranges",
  "publishedAt": "2026-09-24T22:51:44.298Z",
  "jobUrl": "https://jobs.ashbyhq.com/openai/6fd50ace-941b-4559-992f-e53f9b76c9a0",
  "applyUrl": "https://jobs.ashbyhq.com/openai/6fd50ace-941b-4559-992f-e53f9b76c9a0/application",
  "scrapedAt": "2026-09-25T17:37:18.612Z"
}
```

Note the row: `workplaceType` is `Hybrid` and Ashby's `isRemote` is `true`, but the reason `remote` is `true` here is the primary location `US - Remote`. A Hybrid posting listing only office locations comes back `remote: false`.

### FAQ

**Where do I find a company's board slug?**
Open the company's careers page. If it is hosted by Ashby, the address is `https://jobs.ashbyhq.com/<slug>` - copy the slug, or paste the whole URL into `companies`. Companies that embed Ashby on their own domain usually still have a `jobs.ashbyhq.com/<slug>` board; the "Apply" links on their careers page normally reveal it. A slug with no board returns HTTP 404 and is reported for that board only - the rest of the run continues.

**How often is salary actually filled?**
Only where the employer publishes it on the posting. Measured on 2026-09-25: the default `starter` run had a range on 267 of 436 rows (Ramp 149 of 156, Plaid 118 of 121, Notion 0 of 129, Linear 0 of 30); OpenAI's newest 300 postings had one on 221; six AI labs (Perplexity, Cohere, ElevenLabs, Harvey, Sierra, Cognition) had one on 531 of 973 rows, with ElevenLabs publishing none. Published salary coverage varies by employer and posting; the dated measurements are examples, not a forecast for your run.

**Why is there no equity amount column?**
In recorded sample runs across 15 boards, about 1,000 equity components contained an equity-offered indication without numeric amounts. This Actor therefore exports `offersEquity` (`true`/`false`) and the `compensationSummary` string, with no equity-amount column. This describes the supported output and historical observations, not a promise that the source will never publish amounts.

**A posting publishes several ranges. Which one do I get?**
Ashby rolls multiple tiers into one range on the job page (for example four location/level tiers into `$190K - $330K`, flagged `Multiple Ranges`). This Actor uses that same roll-up for `salaryMin`/`salaryMax`, and `salaryTiers` tells you how many tiers stand behind it. On the rare posting where Ashby provides no roll-up, the tiers are aggregated (lowest minimum, highest maximum) in the currency and interval of the first tier.

**Does it also cover Greenhouse, Lever, Workday or SmartRecruiters?**
No. This Actor talks to Ashby and nothing else, and a company that is not on Ashby will 404 here no matter how the slug is spelled. For multi-platform coverage, use our **ATS Job Postings Scraper**, which covers twelve supported ATS platforms, including Greenhouse, Lever, Ashby, Recruitee and Workable. SmartRecruiters is not supported. For Greenhouse alone, use our **Greenhouse Jobs Scraper**.

**What exactly does "only postings new since the last run" do?**
With the option on, the Actor records successfully saved job IDs in the named key-value store `ashby-jobs-scraper-state`, keyed by board (and platform where applicable). The first run has no baseline and returns matching postings up to your result and charge limits. Later runs return IDs not already saved. Prior seen IDs are retained; newly discovered jobs excluded by filters, result caps, or the charge limit are not marked seen and remain eligible for a later run. A run with nothing new succeeds with an empty dataset and a warning. The baseline is written only while this option is enabled. Runs watching the same board share its baseline, so use one schedule per board and avoid concurrent runs against that baseline.

**How fast is it?**
Ashby returns an entire board in a single response with no pagination, so cost per board is one request. Measured locally on 2026-09-25, end to end including writing the rows: across repeated runs the default `starter` run (4 boards, about 435 postings) took 2 to 7 seconds; OpenAI's board (832 postings, a 14 MB response) capped to the newest 300 took 1 to 7 seconds; six AI-lab boards totalling 973 rows took 3 to 12 seconds; all 15 preset boards with full descriptions (2,604 rows) took under 20 seconds. Platform runs add the time to write results over Apify Storage, so expect somewhat more there. Large runs are bounded by `maxRunSeconds`, not by the number of requests.

### Data source, access and limits

- **Source:** Ashby's job-postings endpoint, `https://api.ashbyhq.com/posting-api/job-board/{slug}`, documented by Ashby at developers.ashbyhq.com/docs/public-job-posting-api. This Actor calls it with no key, login, account or cookies, and always with `listedOnly=true`; it additionally drops any posting whose `isListed` field is `false`, which Ashby documents as "should only be available via direct link". Only postings the employer has published on their own board are returned. This Actor never requests anything from `jobs.ashbyhq.com`.
- **Ashby sets its own limits.** Ashby publishes no rate limit for this endpoint and this Actor does not promise one. It sends one request per board, retries transient errors with back-off, and pauses briefly between boards.
- **`remote` is derived, not copied.** Ashby's `isRemote` flag is `true` on hybrid postings (verified on OpenAI, Plaid and Ramp), so this Actor ignores it. `remote` is `true` when `workplaceType` is `Remote` or a listed location contains `remote`/`distributed`; a role described as remote only in the body text reads `false`.
- **Salary is passed through as published, in numbers.** Values, currency and interval come straight from the employer's published compensation block. Ranges labelled OTE (on-target earnings, common for sales roles) are what the employer published; the `compensationSummary` string keeps that wording.
- **Everything else is passed through as published.** Titles, departments, teams, locations and timestamps come from the employer's own Ashby data, including its inconsistencies - department and team names are whatever each company set up, and `workplaceType` is empty when the employer left it unset.
- **No candidate or application data is extracted.** Every row describes a *job posting*, not a person: there is no recruiter name, email or phone field, and none is inferred. `descriptionText` is the employer's own published description reproduced as text, so if an employer wrote a contact address into its posting it stays there. Nothing is collected from candidates or applications.
- **Failures are explicit.** Boards that 404 or error are named in the log and in `SUMMARY.failedSources`, and the run can save matching postings from working boards within its result, time and spending limits. A board that responds with zero listed postings is reported as empty, not as an error. If *every* board fails, the run fails with a message naming the likely cause instead of returning an empty dataset that looks like "no jobs".
- **Billing:** this Actor is billed per result on Apify - the current rate is on the Pricing tab of this Actor's page. There is nothing else to buy; the data source itself needs no key.
- **Not affiliated with Ashby.** Ashby is a trademark of its owner; this Actor is an independent tool that reads publicly reachable job-board data and is not endorsed by Ashby.


#### Saved rows and run charge limits

The Actor writes results one at a time and stops when Apify reports the run charge limit. `SUMMARY.stoppedOnChargeLimit` distinguishes that stop from an empty source. Saved-row counts include only rows accepted by the dataset. For change detection, prior seen IDs are retained and newly discovered IDs enter the baseline only after their row is saved; rows excluded by a result or charge cap remain eligible for a later run. Avoid concurrent monitor runs sharing the same named baseline store.

## Help with your run

Open the **Issues** tab on this Actor with the run URL, the input field that caused trouble and what you expected to receive. Remove API keys and private information before posting.

[Explore more Mayd-It Actors](https://apify.com/maydit)

### Source record quality

A posting needs a usable source ID and title before it can be saved. Identical duplicate entries in a board response are saved once; conflicting entries with the same ID are reported as a source failure. Confirmed empty boards and filters matching nothing produce a warning and an empty dataset. Failed checks remain visible in `SUMMARY`, including when no result could be saved.

## Understand your run

Open **Run report** in the run’s Output tab for saved-record counts, reported source issues and limits, and suggested next steps. Use **CSV export** to take the saved results into a spreadsheet. The existing JSON results and detailed machine-readable summary remain available.

The report reflects this run’s recorded coverage. A successful run or a saved result does not establish that every possible source record was collected.
