# HuntBoard Career Jobs Scraper (`leadproof/huntboard-career-jobs`) Actor

Scrape company career jobs from 13 ATS platforms, including Greenhouse, Ashby, Lever and Workday. Get available full descriptions, salary ranges, filters and change tracking. Live crawling with clear coverage reports.

- **URL**: https://apify.com/leadproof/huntboard-career-jobs.md
- **Developed by:** [Lead Proof](https://apify.com/leadproof) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 jobs

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#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

### Search the HuntBoard job index

Set `mode` to `index` to search a daily-refreshed collection of company-career jobs.
The collection has bounded board coverage and is not a global jobs database.
Live scraping remains the default and supports the original filters and monitoring.

```json
{"mode":"index","indexQuery":"engineer","indexCountry":"US","maxJobs":100}
```

Index keywords match literal words across title, company and description, joined
with AND. `indexCountry`, `indexSource`, `indexCompany` and `indexWorkplace` are exact
filters. Leave live-mode filters and company boards empty in index mode. Unknown
country/workplace values do not match those filters.

Only active records observed within seven days are searchable. Original
observation timestamps and provenance remain in every record; this is not a fresh
visit to each company during your query. `SUMMARY.freshness` describes the window.

`maxJobs` is 1-1000 in index mode. If `SUMMARY.next_cursor` is present, pass it as
`indexCursor` with the same filters to continue within 15 minutes. Pagination is
across a live index, not a frozen snapshot. Check `output_truncated` and
`stop_reason`; a spending cap reached within a page cannot provide a safe resume
cursor. Zero matches produces an empty dataset and a successful summary.

The existing Actor start and per-job prices apply. Returned jobs use the same
billing event as live mode; the code does not add a second index-query charge.
An empty query has no job events but still incurs the Actor start charge.

## HuntBoard Career Jobs Scraper

Direct company-career jobs, useful filters, and auditable change tracking.

Version **0.5.0** builds on HuntBoard's existing ATS connectors. It scans
selected company boards live. It is not yet a continuously refreshed global search
index, and the bundled discovery registry is not a guarantee that every board is
still active. The run summary reports exactly which boards were processed.

### Quick start

```json
{
  "boards": [
    { "platform": "greenhouse", "token": "stripe" },
    { "platform": "ashby", "token": "ramp" }
  ],
  "titleSearch": ["engineer"],
  "hasDescription": true,
  "maxJobs": 100,
  "maxBoards": 2,
  "maxRequests": 20
}
```

Explicit boards choose their own platforms unless you also supply `platforms`.
Leave `boards` empty to scan a bounded selection from the bundled registry. These
defaults scan at most 50 boards, return at most 100 results, and make at most 250
source HTTP requests. `maxJobs` is a ceiling, not a guaranteed result count.

### Pricing

Launch price: **$1 per 1,000 returned records** ($0.001 each), plus **$0.01 per Actor start per GB of allocated memory** (minimum one start event). One active job or closure event is one record. There is no monthly rental. A run returning zero matches still incurs its start charge.

Set a maximum total charge in Apify to cap spending. The Actor stops output when the charge limit is reached. Partial runs can return fewer matches than requested; check `SUMMARY` for source coverage.

### Sources

| Platform | Descriptions | Important coverage limits |
| --- | --- | --- |
| Greenhouse | Included in board response | Source publication date may be absent |
| Ashby | Included | Source salary tiers retained; unlisted jobs excluded |
| Lever | Included, including requirements and closing text | Paginated; source salary range retained |
| Comeet | Included | Needs company UID in `extra`; update date is not a posting date |
| Workable | Included when supplied | A valid empty board is not an error |
| Recruitee | Description and requirements | Country inference is labeled |
| Personio | XML description | `extra` can be `de` or `com` |
| Pinpoint | Description, responsibilities and skills | Fields depend on the employer |
| SmartRecruiters | Optional detail requests | Detail mode returns only checked records; caps make the board partial |
| BambooHR | Optional detail requests | Detail caps make the board partial; posting date is often unavailable |
| Breezy | Optional published JobPosting details | Detail caps make the board partial; source structured salary retained when present |
| Rippling | Optional published job page details | Source employer and salary ranges retained; createdOn is not treated as a posting date |
| Workday | Detail requests | Needs `extra: "wd5\|Careers"`; list/detail caps can make a scan partial |

The default registry selection favors the eight sources with descriptions in
their board response. Other adapters can be selected explicitly. LinkedIn and
Indeed browser workflows are not part of this beta.

### Search and output

`titleSearch`, `descriptionSearch`, `companySearch`, and `locationSearch` use
case-insensitive literal phrases. A match to any phrase in a field is sufficient;
different filters combine with AND. `titleExclusionSearch` removes matching titles.
These are not regular expressions, SQL, or Fantastic.jobs' search syntax.

`countries` takes two-letter codes. `workplaceTypes` accepts `Remote`, `Hybrid`,
and `On-site`. Remote does not mean the employer accepts applicants worldwide.
`employmentTypes` uses normalized values such as `FULL_TIME` and `CONTRACTOR`.
Unknown values stay null and do not match strict filters.

`postedAfter` uses the source posting date. It never substitutes a crawl time or
update time for a missing publication date. `hasSalary` requires a numeric,
source-provided Ashby/Lever/Breezy/Rippling salary range with a currency; it does not estimate pay.
Multiple salary tiers retain their own currency, period and tier name.

Active rows include:

- `job_id`, `board_id`, `external_id`, `source`, `canonical_url`, `apply_url`.
- Company, title, raw locations, country and `country_source`.
- Work arrangement, source employment type and normalized employment type.
- Description, original character count, and a truncation flag. The safety cap is
  100,000 characters instead of the ingestion index's former 2,048-character cap.
- Source posting/update dates, fetch time, first/last observation times.
- Raw structured salary, normalized salary ranges and quality flags.
- `change_type`, stable `event_id`, and `duplicate_group` for grouping similar jobs.

Distinct requisition IDs are retained even when title, company and city match.
Stable IDs deduplicate repeated records from the same board. Similar listings on
different ATS platforms are grouped for inspection, not silently discarded.

### Change tracking

#### Automatic monitoring

Set `monitorName` once and reuse the same input on later runs. No previous run ID
or storage ID is needed. For example:

```json
{
  "boards": [{ "platform": "ashby", "token": "ramp" }],
  "monitorName": "ramp-engineering",
  "titleSearch": ["engineer"],
  "outputMode": "changes",
  "includeClosed": true,
  "maxJobs": 1000,
  "maxBoards": 1,
  "maxRequests": 10
}
```

The first run returns matching jobs as `new` and establishes its baseline. Later
runs automatically select a committed checkpoint and return changes. The same
input can be saved as an Apify task and scheduled. Setting `monitorName` alone
does not create a schedule or start additional runs.

Check `SUMMARY.monitor`: it reports the previous run, checkpoint generation and
whether this run saved a checkpoint. A run with no advanced boards does not
replace the previous checkpoint. Result and charge caps can therefore repeat
already delivered events on retry; deduplicate downstream using `event_id`.

Monitor state is scoped to your account, Actor, monitor name, selected board set,
shard and comparison filters/detail settings. Changing any of these starts a
separate baseline; `initialized: true` reports that condition. Increasing limits
such as `maxJobs` or `maxRequests` keeps the baseline when the selected boards
are unchanged. Keep `includeClosed` enabled throughout a closure-monitoring chain.

Completed checkpoints and compact board states are saved in a named key-value
store, so expiry of an old run's default storage does not break the monitor.
This works with Limited permissions. Monitor storage persists and grows with
history; this version does not automatically delete or archive old checkpoints.
Review retention before high-frequency, large-volume monitoring. Deleting the
monitor store or choosing a new name starts a new baseline.

Each attempt writes immutable content-addressed states and a separate commit.
Failed/incomplete writes cannot expose an unfinished checkpoint. Logical
generations prevent a delayed older branch from replacing newer descendants.
For ordered delivery, let one run finish before starting the next for a monitor.
Overlapping runs can repeat events and do not provide exactly-once delivery.

#### Manual snapshot comparison

Each run writes its own immutable snapshot into its default key-value store.
Pass that store's ID as `previousSnapshotStoreId` on the next run, with the same
filters and detail settings. This avoids a shared mutable cursor and lets callers
replay a known baseline. Use a stable board set and one snapshot chain per shard.

1. Run with `outputMode: "all"` and a result limit large enough to finish each board.
2. Read `SUMMARY` and confirm `snapshot_advanced: true` for the boards you need.
3. On the next run, pass the preceding run's key-value store ID and
   `outputMode: "changes"`. This returns new, updated and reopened matching jobs.
4. Add `includeClosed: true` to receive closure events. A job must be missing from
   **two complete board observations** before closure is inferred.

When detail mode is enabled for SmartRecruiters or BambooHR, a failed or malformed detail request fails the board and preserves its checkpoint. Records beyond the detail cap are omitted, and the board is partial. Missing details therefore do not become false content updates.

A source error, malformed response, pagination cap, invalid record or output cap
must not close missing jobs. Failed/partial boards retain their prior checkpoint.
A result or charge cap does not acknowledge unreturned changes. Retrying may
therefore repeat already delivered events; use `event_id` for idempotent delivery.

Closure records are compact tombstones with identity, title, company, URLs,
observation timestamps and closure evidence. They do not repeat a full description.
Closure is an inference from the board, not an employer-confirmed employment event.
`first_seen_at` means first observed in this snapshot chain, not first posted online.

For manual comparison, snapshots must remain available in Apify storage and the
caller supplies the previous default snapshot store ID. Automatic monitors keep
their own persistent state instead. Do not combine `monitorName` with
`previousSnapshotStoreId`.

### Coverage and limits

`SUMMARY.coverage.observed_quality` measures valid record observations before
search filters, change selection and output caps. It counts nonempty descriptions,
descriptions of at least 200 characters, truncation, countries (source-provided or
inferred), posting dates and structured salary. `SUMMARY.coverage.platforms` breaks
these down by ATS and includes complete, partial, failed and skipped board counts.
Each processed board also reports `observed_quality`. Use these counts to calculate
coverage against `records`; a zero denominator means no measurement, not 100%.
Failed boards, unprocessed listings and listings omitted by detail caps are not in
the field-coverage denominator. These are measurements of the chosen run, not a
claim about every employer. The existing top-level `with_*` counters continue to
refer to emitted active jobs for compatibility, including in changes-only mode.

Breezy and Rippling detail fetching is enabled by `fetchDescriptions` and uses up
to `maxDescriptionsPerBoard` requests per board, in addition to listing requests.
A missing, malformed or mismatched detail page fails the board and preserves the
previous checkpoint. Detail caps mark the board partial. A source-provided empty
description remains empty; the Actor does not invent text. Existing monitors may
emit a one-time update when descriptions, employer names or pay become available.
Breezy keeps the feed publication date; Rippling's creation date is not promoted to
`posted_at`. Salary intervals retain the source unit (for example HOUR or YEAR).

Inspect `SUMMARY.status`: `complete`, `partial`, or `failed`. A successful Apify
process can contain a partial scan; the status message and board results make this
explicit. If all attempted boards fail, the Actor fails after saving its summary.
Results are written in bounded batches and remain available if a later step fails.

`maxRequests` counts list/detail calls, redirects and retries, excluding Apify storage
calls. Transient errors have bounded retries; long Retry-After waits defer the board.
`maxSeconds` bounds source requests, so set the platform timeout higher to allow
result and snapshot writes. `shardCount`/`shardIndex` assign stable board identities
to independent runs. Board order is deterministic; output is not relevance-ranked.

Before connecting to a business-critical workflow, benchmark the actual boards
and required field coverage. Optional descriptions are capped per board and are
not guaranteed. Company names may be board slugs; there is no verified LinkedIn
company enrichment or AI skill/seniority extraction in this version.

### Development and deployment

```bash
npm ci
npm test
## Put an example JSON into storage/key_value_stores/default/INPUT.json
npm start
```

Node 22 or newer is required. The source-only Docker image needs no browser,
residential proxy, or HuntBoard production credentials. The Actor only writes its
own Apify results and snapshot records; there are no D1 ingestion endpoints.

This Actor's public entry point is independent of the internal HuntBoard ingestion pipeline. Use the input examples and inspect coverage before scheduling recurring runs.

Pay-per-event billing uses the event `job` for each returned record and the platform's `apify-actor-start` event. Do not enable the synthetic `apify-default-dataset-item` event alongside `job`, because that would duplicate result charges.

### Support

Open an issue on this Actor's Apify Store page with the affected ATS, a public company board URL, and a shared debug run if available. Do not include credentials or private account data.

### Sources for the API contracts

- [Apify input schema](https://docs.apify.com/actors/development/actor-definition/input-schema)
- [Apify output schema](https://docs.apify.com/actors/development/actor-definition/output-schema)
- [Apify SDK Actor reference](https://docs.apify.com/sdk/js/reference/class/Actor)
- [Greenhouse Job Board API](https://docs.greenhouse.io/job-board.html)
- [Ashby public postings API](https://developers.ashbyhq.com/docs/public-job-posting-api)
- [Lever postings API](https://github.com/lever/postings-api)

# Actor input Schema

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

Live preserves existing scraping and monitoring. Index searches recently collected active jobs without crawling companies during your run.

## `indexQuery` (type: `string`):

Index mode only. Up to 12 words; all must match title, company or description. Not a title-only phrase filter.

## `indexCountry` (type: `string`):

Index mode only. Exact two-letter country code, for example US or GB.

## `indexSource` (type: `string`):

Index mode only. Exact ATS identifier, for example greenhouse or ashby.

## `indexCompany` (type: `string`):

Index mode only. Exact source-reported company name.

## `indexWorkplace` (type: `string`):

Index mode only. Unknown arrangements do not match.

## `indexCursor` (type: `string`):

Copy next\_cursor from SUMMARY and keep filters identical. Expires after 15 minutes; the index is live, not a frozen snapshot.

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

Optional specific boards. Each item contains platform, token, and optional extra. Comeet needs UID in extra; Workday needs wdN|site; Personio accepts de or com. Leave empty to use the bundled registry. See README examples. In index mode leave this empty and use only Index filters.

## `platforms` (type: `array`):

Platforms to scan from the bundled registry. Explicit boards choose their own platforms unless this filter is provided.

## `titleSearch` (type: `array`):

Case-insensitive literal phrase matching. Any phrase may match; filters across fields are combined with AND.

## `titleExclusionSearch` (type: `array`):

Exclude any title containing one of these literal phrases.

## `descriptionSearch` (type: `array`):

Search the title and available description. Any phrase may match; this is literal text, not regex or SQL.

## `companySearch` (type: `array`):

Case-insensitive phrase filter on the source-reported company name or board slug.

## `locationSearch` (type: `array`):

Case-insensitive phrase filter on raw source locations. Remote does not imply worldwide eligibility.

## `countries` (type: `array`):

Two-letter country codes. Unresolved or regional-only locations will not match this strict filter.

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

Unknown work arrangements do not match a filter.

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

Unknown or unmapped source types do not match a filter.

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

YYYY-MM-DD or UTC ISO timestamp. Uses the source publication date. Jobs with unknown posting dates do not match.

## `hasDescription` (type: `boolean`):

Return only jobs containing a nonempty description.

## `hasSalary` (type: `boolean`):

Return only jobs with a numeric salary range and currency supplied by Ashby, Lever, Breezy or Rippling. Text-only salary mentions and AI estimates are not included.

## `maxJobs` (type: `integer`):

Hard limit across active jobs and closure events. Result-capped boards do not advance their snapshot. In index mode the maximum is 1000; SUMMARY contains continuation and freshness details.

## `maxBoards` (type: `integer`):

Maximum boards in this shard. Selection is deterministic and prioritizes historical registry job counts.

## `maxRequests` (type: `integer`):

Includes list requests, description requests, redirects and retries. Apify storage API calls are excluded.

## `maxSeconds` (type: `integer`):

Stops starting source requests after this deadline. Allow a higher platform timeout for flushing results and snapshots.

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

Concurrent board fetches. Each board may use up to five description requests concurrently.

## `maxListPages` (type: `integer`):

Lever and SmartRecruiters use pages of 100; Workday uses pages of 20. A page cap is reported as partial.

## `fetchDescriptions` (type: `boolean`):

Fetches bounded detail pages for SmartRecruiters, BambooHR, Breezy and Rippling. Workday uses detail pages for normalized records. Other sources use descriptions from the list response. Disabling this option reduces coverage on these four sources.

## `maxDescriptionsPerBoard` (type: `integer`):

Maximum detail records per board for SmartRecruiters, BambooHR, Breezy, Rippling and Workday. In detail mode, only jobs with successfully checked detail pages are returned; omitted listings make the board partial.

## `monitorName` (type: `string`):

Optional automatic change tracking. Use the same name on repeated runs, such as engineering-jobs. The first run establishes a baseline and returns matching jobs. Later runs reuse the completed checkpoint automatically. Names use 1-64 letters, numbers, hyphens or underscores. Board/filter changes create a separate baseline; increasing result limits preserves it. Do not combine with a previous snapshot ID.

## `previousSnapshotStoreId` (type: `string`):

Advanced manual comparison: default key-value store ID from a completed run of this Actor with the same filters and detail settings. This is not a dataset ID, run ID or automatic monitor store ID. Leave empty when using a monitor name.

## `outputMode` (type: `string`):

All observed matching jobs, or only new/updated/reopened jobs. Changes mode requires a monitor name or a previous snapshot. A new monitor initially returns all matching jobs as new.

## `includeClosed` (type: `boolean`):

Requires a monitor name or a previous snapshot. A closure is inferred only after two consecutive complete scans of the same board miss a previously observed job. It is not confirmation from the employer.

## `shardCount` (type: `integer`):

Split board identities across this many runs. Keep shard count stable for a snapshot chain.

## `shardIndex` (type: `integer`):

Zero-based shard number, less than shardCount.

## Actor input object example

```json
{
  "mode": "live",
  "hasDescription": false,
  "hasSalary": false,
  "maxJobs": 100,
  "maxBoards": 50,
  "maxRequests": 250,
  "maxSeconds": 300,
  "concurrency": 3,
  "maxListPages": 5,
  "fetchDescriptions": true,
  "maxDescriptionsPerBoard": 25,
  "outputMode": "all",
  "includeClosed": false,
  "shardCount": 1,
  "shardIndex": 0
}
```

# Actor output Schema

## `jobs` (type: `string`):

No description

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

No description

## `snapshot` (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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("leadproof/huntboard-career-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 = {}

# Run the Actor and wait for it to finish
run = client.actor("leadproof/huntboard-career-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 '{}' |
apify call leadproof/huntboard-career-jobs --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,leadproof/huntboard-career-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/d9huMo2AyJdiVvl4Y/builds/axVwKHZE8QH6Hefke/openapi.json
