Use Ashby Job Board Compensation & Openings Scraper with Python
Scrape public Ashby job boards for any company hiring on jobs.ashbyhq.com. Get every open role with title, department, team, location, remote status, salary/compensation ranges, apply links, and full job descriptions. No login, no API key.
1. Choose the data you need
Review the published README for supported collection modes, examples, and limits. The recipe below uses the current published input definition.
2. Prepare a source-based input
This JSON contains published prefills and required-field defaults. Review every value for your task before running it; it is not a promise of successful collection.
{
"employmentType": "any",
"hasSalaryOnly": false,
"includeCompanyInfo": false,
"includeDescriptionHtml": false,
"includeDescriptionPlain": false,
"includeDetails": false,
"includeUnlisted": false,
"isRemoteOnly": false,
"jobUrls": [],
"maxItems": 1,
"maxItemsPerOrg": 0,
"mode": "byOrg",
"orgSlugs": [
"Ramp"
],
"salaryCurrency": "any",
"sortBy": "default",
"workplaceType": "any"
}
Download input.example.json and save your reviewed copy as input.json.
| Field | Type | Required | Description |
|---|---|---|---|
mode | string | required | What to collect. `byOrg` lists jobs from one or more companies; `jobDetail` fetches specific postings by URL or ID; `boardSummary` returns one departments/locations/compensation summary per company. The legacy values are kept so saved tasks continue to run. |
orgSlugs | array | optional | Ashby board slugs, company names, or board URLs. Examples: `Ramp`, `Neko Health`, `https://jobs.ashbyhq.com/Ramp`. Names are matched to the closest live board automatically. |
jobUrls | array | optional | Public Ashby job posting URLs, e.g. `https://jobs.ashbyhq.com/Ramp/<job-id>`. The `/application` suffix and query strings are accepted. |
orgSlug | string | optional | Used with `jobId` when you do not have a full job URL. Example: `Ramp`. |
jobId | string | optional | The Ashby posting UUID or a full job URL. Used alone or with `orgSlug`. |
searchQuery | string | optional | Case-insensitive text match over title, department, team, location, and description. Descriptions are searched even when `includeDescriptionPlain` is off and in `boardSummary` mode. Examples: `engineer`, `payments`, `München`. |
department | string | optional | Case-insensitive text match over department and team. Examples: `Engineering`, `Sales`, `Design`. |
location | string | optional | Case-insensitive text match over the primary location, secondary locations, city, region, and country. Examples: `New York`, `Remote`, `London`. |
workplaceType | string | optional | Keep only fully remote, hybrid, or on-site jobs. Unlike `isRemoteOnly`, this separates Remote from Hybrid. Jobs with no workplace type are excluded when a specific value is chosen. |
employmentType | string | optional | Keep only one employment type. |
isRemoteOnly | boolean | optional | Keep jobs the board marks as remote. Ashby sets this for both Remote and Hybrid roles; use `workplaceType=Remote` for fully remote jobs. |
publishedAfter | string | optional | Only jobs published on or after this date. Accepts `YYYY-MM-DD`, an ISO datetime, or a relative window such as `24h`, `7d`, or `2w`. |
publishedBefore | string | optional | Only jobs published on or before this date. Accepts `YYYY-MM-DD`, an ISO datetime, or a relative window. |
minAnnualSalary | integer | optional | Keep jobs whose published annual salary range overlaps this minimum. Hourly, monthly, and weekly ranges are annualized. Jobs with no published salary are kept unless `hasSalaryOnly` is on. |
maxAnnualSalary | integer | optional | Keep jobs whose published annual salary range overlaps this maximum. Use with `minAnnualSalary` for a range. |
salaryCurrency | string | optional | Only compare salaries in this currency. `any` compares the numeric range as published (currencies are not converted). |
hasSalaryOnly | boolean | optional | Drop jobs where the employer did not publish a salary range. |
sortBy | string | optional | Order of the returned job records. `default` keeps the board's own order. |
includeUnlisted | boolean | optional | Include jobs marked `isListed:false` (direct-link-only postings that are not shown on the public board). Applies to every mode: `jobDetail` returns a `job_not_listed` error for an unlisted job unless this is on. Off by default. |
includeCompanyInfo | boolean | optional | Fetch each company's public board page once for its display name, logo, and timezone. Adds one request per company. Off in the daily test prefill. |
includeDetails | boolean | optional | Fetch each returned job's public page for all team names, confidentiality, compensation philosophy, direct-apply flag, and remote-eligible countries. Adds one request per job; slower on large runs. |
includeDescriptionPlain | boolean | optional | Add `descriptionPlain` to each job. On by default; turn it off for compact metadata-only rows. |
includeDescriptionHtml | boolean | optional | Add the original `descriptionHtml` to each job. Increases dataset size. |
maxItemsPerOrg | integer | optional | Cap the number of job records from each company after filters. `0` means no per-company cap. |
maxItems | integer | optional | Hard cap on the number of job records (or company summaries) pushed to the dataset. Error records for invalid inputs are additional diagnostics and do not count. |
3. Start an Actor run
Set APIFY_TOKEN in your environment. Python 3.10 or newer is sufficient; this recipe uses no third-party packages.
Execution may incur charges. Review current pricing on Apify. The request asks for a 120-second timeout and a charge limit; these settings are subject to Apify’s pricing and do not guarantee an all-inclusive spending cap.
import json
import os
import urllib.request
# Save the schema-derived example as input.json; review and edit it first.
with open("input.json", encoding="utf-8") as stream:
actor_input = json.load(stream)
token = os.environ["APIFY_TOKEN"]
request = urllib.request.Request(
"https://api.apify.com/v2/acts/uLc5okVRk3hoaiEtP/runs?timeout=120&maxTotalChargeUsd=0.50",
data=json.dumps(actor_input).encode("utf-8"),
headers={"Authorization": "Bearer " + token, "Content-Type": "application/json"},
method="POST",
)
# This starts a potentially billable run. Do not automatically retry this POST.
with urllib.request.urlopen(request, timeout=40) as response:
run = json.load(response)["data"]
print("Run ID:", run["id"])
print("Status:", run["status"])
# Inspect and download results in Apify Console once the run succeeds.
4. Inspect the actual results
Open the run in Apify Console and wait for a successful completion. Use the published output schema to locate results and the dataset schema to understand declared views. Missing or optional record fields must not be treated as guaranteed values.
Jobs and company board summaries from public Ashby job boards - titles, departments, teams, structured locations, workplace types, compensation ranges, descriptions, apply links, and optional company metadata.
Questions before you run
Which inputs are required?
The published required fields are mode
What does this guide verify?
The fields, prefills, and schema links are rendered from the published build. This is a usage recipe, not a paid live extraction test. Source availability and collection results can vary.
What if the run request times out?
Check Apify Console before retrying. The first request may already have created a run. Do not automatically repeat a run-creation POST.
Generated from source build yzfUD4dKeKLHXPa89 and its README and schemas. Source changes regenerate this guide; no third-party editorial claims are inferred.