Ashby Jobs Scraper — Company Job Boards avatar

Ashby Jobs Scraper — Company Job Boards

Pricing

from $1.45 / 1,000 posting saveds

Go to Apify Store
Ashby Jobs Scraper — Company Job Boards

Ashby Jobs Scraper — Company Job Boards

Every open role on any company's Ashby job board as structured data: title, department and team, location with country, employment type, pay range where shown, posted date and full text on request. One request per board. Incremental mode charges only for what changed. No personal data.

Pricing

from $1.45 / 1,000 posting saveds

Rating

0.0

(0)

Developer

Adderley Data

Adderley Data

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

6 hours ago

Last modified

Share

What does Ashby Jobs Scraper do?

Ashby Jobs Scraper reads every open role on any company's public Ashby job board and returns it as structured data you can load straight into a spreadsheet, a database or a model. Ashby is an applicant tracking system, and the companies that use it publish their open roles at jobs.ashbyhq.com/<name>; if a careers page's "Apply" button leads there, this Actor reads it.

Give it a board name (acme) or any jobs.ashbyhq.com link for the company, and for each posting you get the title, the department and team, the employment type, the work arrangement, the location parsed to city, region and country from the posting's own address, every other location the posting lists, the pay range where the company shows one, the date it was published and a link to the posting — and, if you ask for it, the full text of the posting.

What makes it different:

  • One request per board. The whole board, every posting's full text and its pay details come back in a single call to Ashby's public job-posting API. No HTML parsing, no page-by-page crawling, nothing to break when a careers page is redesigned. A thousand postings across fifty companies is fifty requests.
  • Pay as numbers, from Ashby's own pay data first. Where the company shows pay on a posting, salary carries min, max, currency and period from Ashby's structured compensation block, with Ashby's own summary line (€110K – €185K • Offers Equity) kept in salary.raw. Where it does not, but the posting's text states a range ("The salary range for this role is …"), that range is read and its sentence kept in salary.raw. Otherwise the fields are null: nothing is estimated, and pay a company has chosen not to show is never read.
  • Incremental mode. Put the Actor on a schedule and each run returns only postings that are new, changed, back again or gone. Ashby publishes no last-updated time, so the Actor compares each posting's fields and the words of its text itself: an edit to the wording is caught, a change of formatting is not. Unchanged postings are skipped and not charged.
  • One stable schema. Every row has every field, every time. Unknown is null, never a missing key, so nothing downstream breaks on a sparse posting. The schema is versioned (job.v1) and every Adderley Data jobs Actor uses it, so an Ashby board, a Greenhouse board and a job board sit in the same table.
  • No personal data. There is no recruiter name, email or phone field, contact details inside posting text are redacted by default, and postings a company has not listed on its board are left out.
  • Bounded cost. You set a maximum number of results; the run stops there. It also stops at the spending limit you set on the run in Apify.

What Ashby data can you extract?

FieldWhat it holds
idStable across runs: source:market:sourceJobId. Use it as your primary key.
titleJob title as listed.
company.nameThe hiring company, where the listing names one.
advertiser.nameThe business that placed the listing — often a recruitment agency. Never a person.
location.rawLocation text as listed. Several locations are joined with |.
location.suburbSuburb, when the listing states one.
location.cityCity or area, when the listing states one.
location.regionState or region, e.g. VIC.
location.postcodePostcode, when the source provides it.
location.countryISO 3166-1 alpha-2 country code.
workArrangementon_site, hybrid, remote or unknown.
employmentTypesNormalised: full_time, part_time, contract, casual, temporary, internship, volunteer.
salary.rawThe salary text exactly as shown, or null when the listing shows none.
salary.minLower bound as a number, when the text contains one.
salary.maxUpper bound as a number. Equal to min for a single figure.
salary.currencyISO 4217. Taken from the text, otherwise the market default.
salary.periodhour, day, week, month or year; null when the text does not say.
salary.includesSupertrue / false when the text says so ("plus super", "inc. super"); otherwise null.
classificationsThe source's category and subcategory pairs.
teaserThe short summary shown on the results page.
bulletPointsSelling points shown on the results page.
postedAtWhen the listing was posted, ISO 8601 UTC.
updatedAtThe source's own last-modified time, ISO 8601 UTC. Published by ATS and API sources; null where the site does not show one.
expiresAtExpiry, ISO 8601 UTC, where the source states one.
isPromotedtrue for paid placements. A listing shown both promoted and organic is returned once.
urlLink to the listing.
descriptionNull unless requested. text, optional sanitised html, and contactsRedacted.
changeTypeIncremental runs: NEW, UPDATED, REAPPEARED, EXPIRED (or UNCHANGED if you ask for those). Otherwise null.
firstSeenAtIncremental runs: when this monitor first saw the listing.
contentHashSHA-256 over the fields that define a change. Compare it to detect edits yourself.
scrapedAtWhen this row was produced, ISO 8601 UTC.
sourceSource key, e.g. seek.
marketMarket key, e.g. au, nz.
sourceJobIdThe source's own identifier for the listing.
company.sourceCompanyIdThe source's identifier for the company, when exposed.
company.urlThe company's page on the source site, when exposed.
advertiser.sourceAdvertiserIdThe source's identifier for the advertiser.
schemaVersionAlways job.v1. Breaking changes ship as job.v2 in a new Actor version, never silently.

How the fields are filled for this source:

  • market is always global: an Ashby board is the company's, not a country's.
  • company.name is the board name as it appears in the board's link — acme for jobs.ashbyhq.com/acme. Ashby's API does not publish the company's display name, and the Actor does not guess one.
  • classifications holds Ashby's department as category and its team as subcategory.
  • location.raw lists every location the posting names, the primary one first, joined with |. city, region, postcode and country come from the primary location's address. A posting located in a bloc such as the European Union has no single country, so country is null; a city name is never turned into a country.
  • employmentTypes comes from Ashby's employment type (full time, part time, intern, contract, temporary) and workArrangement from its workplace type (on-site, hybrid, remote).
  • postedAt is when the company last published the posting. updatedAt and expiresAt are always null: Ashby's feed carries neither.

How much does it cost to scrape Ashby job boards?

You pay per posting saved to your dataset — $1.75 per 1,000 postings on Apify's Starter plan — plus $0.005 each time a run starts. There is no monthly rental.

Apify planPricePer listing
Free$1.75 per 1,000 listings$0.00175
Starter (Bronze)$1.75 per 1,000 listings$0.00175
Scale (Silver)$1.60 per 1,000 listings$0.00160
Business (Gold)$1.45 per 1,000 listings$0.00145

Plus $0.005 per run start. Compute and proxy are included in these prices.

What you runCost (USD, Starter plan)
100 listings, one run$0.18
1,000 listings, one run$1.75
10,000 listings, one run$17.50
50,000 listings, one run$87.50
A daily incremental monitor finding about 150 new or changed listings a day, for a month$8.03

Descriptions cost nothing extra here: they arrive in the same request as the listing. Use incremental mode for anything you run more than once — after the first run you pay only for what changed.

How to scrape an Ashby job board

  1. Find the company's board. On its careers page, the "Apply" link or the embedded job list points at jobs.ashbyhq.com/<name>. The name is what you need; the whole link works too.
  2. Open the Actor in Apify Console and go to the Input tab. Paste one name or link per line into Job boards. Up to 500 boards per run.
  3. Optionally filter: Title keywords, Locations, Departments, Published within (days).
  4. Set Maximum results. This is also your cost cap.
  5. Press Start. When the run finishes, open the Output tab and export as JSON, CSV, Excel, XML or HTML, or read the dataset through the Apify API.

A posting that matches more than one board or filter is returned once.

Input

FieldTypeDefaultWhat it does
boardsarray—One entry per company: the board name (in https://jobs.ashbyhq.com/acme it is "acme"), or any jobs.ashbyhq.com link — the board, one of its jobs, or its embed. Up to 500 boards per run; each is one request.
keywordsarray—Keep postings whose title contains every word of any keyword, in any order — "engineer data" matches "Senior Data Engineer". Leave empty for all titles.
locationsarray—Keep postings where any listed location, its address or the workplace type contains this text, e.g. "London", "Canada", "Remote", "Hybrid". Leave empty for all locations.
departmentsarray—Keep postings whose department or team contains this text, e.g. "Engineering". Leave empty for all departments.
postedWithinDaysinteger—Keep postings published in the last N days. Ashby dates a posting from when it was last published. Leave empty for any time.
maxResultsinteger100The run stops once this many postings are saved. You are charged per posting saved, so this is also your cost cap.
includeDescriptionbooleanfalseOn: the full posting text comes back with each row, read from the same request as the listing, so it costs no extra requests. Off: listing fields only.
descriptionFormattext, text_and_html"text"Plain text, or plain text plus sanitised HTML.
redactContactsbooleantrueOn by default: email addresses and phone numbers inside description text are replaced with [redacted]. This Actor never outputs recruiter names or contact fields.
incrementalbooleanfalseRemember what earlier runs saw and save only postings that are new, changed or gone. Unchanged postings are skipped and not charged. Put the Actor on a schedule with this on.
stateKeystring—Optional name for this monitor, e.g. "competitor-engineering". Runs with the same key share memory. Left empty, a key is derived from the boards and filters themselves.
emitExpiredbooleantrueIncremental mode only. When a complete run no longer finds a posting it saw before, save one row with changeType EXPIRED.
emitUnchangedbooleanfalseIncremental mode only. Saves (and charges for) every posting, labelled UNCHANGED where nothing moved.
proxyConfigurationobject{"useApifyProxy":true}Apify Proxy, automatic group, is the default and is what this Actor is tested with.
maxConcurrencyinteger4Parallel requests. The default is deliberately modest.
maxRequestsPerMinuteinteger90An upper bound on request rate across the whole run.

A typical input:

{
"boards": [
"ashby"
],
"keywords": [
"engineer"
],
"maxResults": 100
}

Output

One row per posting. This is a synthetic example in the exact shape the Actor returns:

{
"schemaVersion": "job.v1",
"id": "ashby:global:4f9c2d1e-7a3b-4c5d-9e8f-0a1b2c3d4e5f",
"source": "ashby",
"market": "global",
"sourceJobId": "4f9c2d1e-7a3b-4c5d-9e8f-0a1b2c3d4e5f",
"url": "https://jobs.ashbyhq.com/example-freight/4f9c2d1e-7a3b-4c5d-9e8f-0a1b2c3d4e5f",
"title": "Senior Data Engineer",
"company": {
"name": "example-freight",
"sourceCompanyId": "example-freight",
"url": "https://jobs.ashbyhq.com/example-freight"
},
"advertiser": {
"name": "example-freight",
"sourceAdvertiserId": "example-freight"
},
"location": {
"raw": "Melbourne | Sydney",
"suburb": null,
"city": "Melbourne",
"region": "Victoria",
"postcode": null,
"country": "AU"
},
"workArrangement": "hybrid",
"employmentTypes": [
"full_time"
],
"salary": {
"raw": "A$150K – A$175K • Offers Equity",
"min": 150000,
"max": 175000,
"currency": "AUD",
"period": "year",
"includesSuper": null
},
"classifications": [
{
"category": "Engineering",
"subcategory": "Data Platform"
}
],
"teaser": null,
"bulletPoints": [],
"postedAt": "2026-09-15T03:12:00.000Z",
"updatedAt": null,
"expiresAt": null,
"isPromoted": false,
"description": null,
"changeType": "NEW",
"firstSeenAt": "2026-09-21T19:30:12.000Z",
"contentHash": "8d0b6c2f4e1a7390b5c8d2e6f1a4b7c09e3d5f2a8b1c4e7d0a6f9b2c5e8d1a47",
"scrapedAt": "2026-09-21T19:30:12.000Z"
}

The Output tab has two table views: Overview (the fields most people want, flattened) and Changes (for incremental runs).

Incremental mode: monitor new roles across companies

Turn on Incremental mode and run the same input on a schedule — hourly, daily, weekly. The Actor keeps a small record of what it has seen and every row tells you what happened:

changeTypeMeaning
NEWFirst time this monitor has seen the posting
UPDATEDSeen before, and the title, location, department or team, employment type, work arrangement, pay, published date or the wording of the posting has changed
REAPPEAREDWas reported as expired and is back
EXPIREDSeen before and no longer listed on the board. One row, once
UNCHANGEDOnly if you turn on Also save unchanged postings

How it behaves, so there are no surprises:

  • The first run returns everything as NEW. From the second run you pay only for the difference.
  • Ashby does not say when a posting was last edited, so the Actor keeps a short fingerprint of each posting's words and compares it with the last run. An edit to the wording counts as a change even when you have not asked for descriptions; a change of formatting, spacing or punctuation alone does not.
  • EXPIRED is only ever reported by a complete run. If a run hits your result cap or your spending limit, or a board cannot be read, nothing is declared expired — a posting on a board the run never read is not gone.
  • Runs share memory when they share a State key. Leave it empty and the key is derived from the boards and filters themselves, so the same input always continues the same monitor. Name it (competitor-engineering) if you want to change filters later without starting again.
  • A posting not seen for 45 days is forgotten.

Descriptions and contact details

Full descriptions are off by default. Turn on Include full descriptions and each row carries description.text (and sanitised description.html if you choose that format). Because Ashby returns the posting text in the same response as the listing, this costs no extra requests and no extra time.

Postings sometimes contain a recruiter's email address or phone number. With Redact contact details on — the default — those are replaced with [redacted] and description.contactsRedacted is true. Redaction covers email addresses, phone numbers and personal profile addresses (linkedin.com/in/…): a name or another web address written into the prose of a posting ("I'm the head of data here…") stays as the company wrote it. Neither description.text nor the optional HTML carries the address behind a link: the HTML keeps each link's words and drops its address, and drops images. The Actor never returns recruiter names or contact details as fields, under any setting. If your use case is contacting individuals, this is the wrong tool.

What people use it for

  • Competitor and market hiring signals. Which companies are opening which roles, in which departments, teams and locations, and how often — a daily monitor across a list of boards is one scheduled run.
  • Pay-transparency datasets. The ranges companies choose to show, across roles and locations, as numbers with Ashby's own wording kept beside them.
  • Job aggregators and alert products. A clean feed of new postings from a curated list of employers, deduplicated and labelled by change.
  • Sales and partnership research at company level. Growth signals from hiring, without collecting anything about the individuals involved.
  • Research and teaching. A clean, repeatable dataset with a documented schema.

Using the API

Run it from code with the Apify client, using your own API token:

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('adderleydata/ashby-jobs-scraper').call({"boards":["ashby"],"keywords":["engineer"],"maxResults":100});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.length, items[0]?.salary);
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("adderleydata/ashby-jobs-scraper").call(run_input={"boards":["ashby"],"keywords":["engineer"],"maxResults":100})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
print(item["title"], item["location"]["raw"], item["salary"]["raw"])

Schedules, webhooks and the Make, Zapier, n8n and Google Sheets integrations all work the way they do for any Apify Actor. The Actor runs with limited permissions and is priced per event, so AI agents can call it through Apify's MCP server as well.

The Actor reads Ashby's public job-posting API — the same feed a company's own careers page can be built from, documented by Ashby for that use, with no login, no key and no rate tricks — and returns facts about job postings. It does not log in, does not solve CAPTCHAs, does not read postings a company has kept off its board, and does not collect personal information.

What you do with the data is your responsibility. Each company's postings are its own copyright — analyse them, do not republish them. If your project touches personal information, privacy law applies to you wherever you are. This is general information, not legal advice.

Questions

Where do I find a company's board name? On its careers page, follow any job's "Apply" link: the address is jobs.ashbyhq.com/<name>/<job id>. Paste either the whole link or just the name. Company sites that embed their Ashby board load it from jobs.ashbyhq.com/<name>/embed; that link works too.

A board I gave came back as "not found". Ashby answers "Not Found" when no board has that name. The run names the board in its log and reports it as not found, never as an empty board, and the other boards in the run are still read. Check the name on the company's careers page: it is the part after jobs.ashbyhq.com/ in any of its job links. A missing board is asked for once, not retried, and a run in which every board is missing finishes with an empty dataset and a message naming each one.

Does it need an Ashby login or API key? No. The job-posting API is public.

Why is salary empty for some postings? Each company decides whether its postings show pay. When a posting shows none in Ashby's pay block and states no range in its text either, salary stays null rather than holding a guess. A range is read from the text only when a sentence states two amounts as the pay for the role.

Why does salary.raw say "Multiple Ranges"? Some companies set a different range for each location or level. min and max are then Ashby's own summary: the lowest minimum and the highest maximum across the ranges in the currency the summary line shows. Ranges set in another currency are not part of that summary.

Can I get recruiter emails or phone numbers? No, by design.

How current is the data? It is read from Ashby while your run is in progress. postedAt is when the company last published the posting; scrapedAt records when the row was produced.

The field I need is not there. Open an issue on the Issues tab. Fields are added to the schema without breaking existing ones.

Support

Use the Issues tab on this page. We read it every day. Include the run ID and what you expected to see.

Other Adderley Data Actors

Every Actor in a vertical returns the same fields, so adding a source needs no new code on your side.

About

Made by Adderley Data, Melbourne — https://adderleydata.com. Not affiliated with, endorsed by or sponsored by Ashby, Inc. Ashby is a trade mark of its owner and is used here only to describe what this Actor reads.