# Ashby Job Board Scraper (`usestring/ashby-jobs`) Actor

Collect every open job posting from any Ashby-hosted job board, with structured city, state and country.

- **URL**: https://apify.com/usestring/ashby-jobs.md
- **Developed by:** [String](https://apify.com/usestring) (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 $0.75 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#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 Job Board Scraper — whole board, structured locations

Collect every open job posting from any Ashby-hosted job board on `jobs.ashbyhq.com`. Give this Actor
an Ashby board URL or company slug, and it returns the whole board with title, company, department,
employment type, remote flag, published date and apply URL.

Ashby hydrates its boards from a public posting API, and this Actor reads that API rather than the
rendered page. **One request returns every open posting on an Ashby board**, with a second request to
the board page for the company's display name — a measured run averaged 174 postings across 5.75
requests.

Because the Ashby posting API carries a structured postal address, `city`, `state` and `country` come
back as separate fields rather than being guessed out of a display string.

No Ashby account, login, API key or cookies are used. This Actor reads the public Ashby job board
that a logged-out visitor sees.

### What it returns

| Field | Type | Notes |
| --- | --- | --- |
| `jobId` | string | Ashby's own posting ID — stable, use it to de-duplicate |
| `title` | string | |
| `company` | string | The organisation's display name, read from the Ashby board page |
| `tenant` | string | The Ashby board slug, e.g. `ramp` |
| `location` | string | The posting's display location as Ashby shows it |
| `city` | string | From Ashby's structured `addressLocality` — not parsed out of the display string |
| `state` | string | From `addressRegion` |
| `country` | string | From `addressCountry` |
| `isRemote` | boolean | Ashby's own remote flag on the posting, not inferred from text |
| `department` | string | The posting's department, falling back to its team |
| `employmentType` | string | Ashby's own employment type, e.g. `"FullTime"` |
| `postedAt` | string | ISO 8601 timestamp — the posting's `publishedAt` |
| `jobUrl` | string | Direct link to the posting |
| `boardUrl` | string | The board a person would open, `https://jobs.ashbyhq.com/<tenant>` |
| `sourceUrl`, `collectedAt` | string | Provenance for every row |

### Input

```json
{ "boards": ["https://jobs.ashbyhq.com/ramp", "linear"], "maxItems": 1000 }
```

| Field | Description |
| --- | --- |
| `boards` | Ashby board URLs or company slugs. Required, 1–100. |
| `maxItems` | Cap on dataset items. Default 1000, maximum 50,000. Free plans stop at 250 requests and 250 results — see below. |
| `concurrency` | Boards fetched in parallel. Default 2, maximum 5. |

A company slug, a board URL and a deep posting or application URL all resolve to the same Ashby board
and are fetched once rather than billed several times.

### Use cases

- Tracking which companies are hiring, and for which teams, across a watchlist of Ashby boards
- Competitor hiring signals — headcount direction by department, read from the employer's own board
- Filling an ATS, job board or talent CRM with live postings straight from the employer
- Recruiting lead lists filtered on `city`, `state`, `country` or `isRemote`
- Monitoring a board over time by re-running on a schedule and diffing on `jobId`

### Reliability

The Ashby posting API is structured JSON published by Ashby itself, so there is no markup contract to
break and no model in the path: a field is either present on the posting or absent.

`isRemote`, `city`, `state`, `country`, `employmentType` and `postedAt` all come from the API rather
than from text parsing, so they do not drift between runs for the same posting.

The company display name is the one field the posting API omits, so it is read from the board page in
a second request. If that page cannot be read, the postings are still returned and only `company` is
`null` — a blocked page costs one field rather than the whole board.

A board that cannot be read is recorded in the run's `SUMMARY` under `failures` rather than silently
returning fewer rows, and a run where every board failed exits with an error.

### Frequently asked questions

**How do I find a company's Ashby board URL?** An Ashby board lives at
`https://jobs.ashbyhq.com/<slug>`, where the slug is the company's own name — `ramp`, `linear`. You
can pass the full URL or just the slug.

**Do I need an Ashby account, API key or login?** No. This Actor reads the public Ashby posting API
and the public board page. No account, no cookies, no session.

**How many job postings does one request return?** The Ashby posting API returns every open posting
on a board in a single response, so a board costs two requests regardless of its size. A measured run
averaged 174 postings across 5.75 requests.

**Does it work for any company on Ashby?** Yes — any board hosted on `jobs.ashbyhq.com`, addressed by
its slug. Nothing is hardcoded per company.

**Are the city, state and country reliable?** Yes, more so than on most job boards: Ashby publishes a
structured postal address per posting, so `city`, `state` and `country` are read directly from
`addressLocality`, `addressRegion` and `addressCountry` rather than split out of a display string.

**Can I get the full job description or salary?** No. This Actor returns the board's posting fields
listed above; follow `jobUrl` for the full description.

### Limitations

Board fields only: no job description body, no compensation, no application counts and no recruiter
contacts. A board with zero open postings is reported as a failure rather than as an empty result,
because an unknown Ashby slug is indistinguishable from one. Only `jobs.ashbyhq.com` boards are
accepted — a URL on any other host is rejected rather than fetched.

### Free plan limit

Runs started from an Apify **free plan** stop at **250 requests and 250 results**, and the run
reports that it reached the limit. Any paid plan runs the full input and `maxItems` you set.

The limit exists because this Actor fetches through our own infrastructure, which Apify does not
cover for free-plan runs. It binds on requests as well as results so that a large input list cannot
spend those fetches for rows the run will not return.

# Actor input Schema

## `boards` (type: `array`):

Ashby board URLs or company slugs.

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

Global cap on dataset items. Runs started from an Apify free plan stop at 250 requests and 250 results; any paid plan runs the full amount.

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

Targets fetched in parallel.

## Actor input object example

```json
{
  "boards": [
    "https://jobs.ashbyhq.com/ramp"
  ],
  "maxItems": 1000,
  "concurrency": 2
}
```

# Actor output Schema

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

Collect every open job posting from any Ashby-hosted job board, with structured city, state and country.

## `summary` (type: `string`):

Item count, failure count and every target that failed, with its error.

# 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 = {
    "boards": [
        "https://jobs.ashbyhq.com/ramp"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("usestring/ashby-jobs").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 = { "boards": ["https://jobs.ashbyhq.com/ramp"] }

# Run the Actor and wait for it to finish
run = client.actor("usestring/ashby-jobs").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 '{
  "boards": [
    "https://jobs.ashbyhq.com/ramp"
  ]
}' |
apify call usestring/ashby-jobs --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,usestring/ashby-jobs"
        }
    }
}

```

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/TYCtfonQKnu0PFRCs/builds/wClErKeQs0XgvIWeN/openapi.json
