# BambooHR Jobs Scraper (`devilscrapes/bamboohr-jobs-scraper`) Actor

Scrape every open job posting from any BambooHR-powered company career site via BambooHR's own public JSON endpoints, no login or browser required. Get titles, locations, departments, and full descriptions, ready for recruiter pipelines or as a hiring-intent signal.

- **URL**: https://apify.com/devilscrapes/bamboohr-jobs-scraper.md
- **Developed by:** [DevilScrapes](https://apify.com/devilscrapes) (community)
- **Categories:** Jobs
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 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.

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

<div align="center">
  <img src=".actor/icon.svg" width="160" alt="Devil Scrapes mark" />

## BambooHR Jobs Scraper

**💰 $1.50 / 1 000 results**  ·  pay only for results  ·  no credit card to try

*We do the dirty work so your dataset stays clean.* 😈

Scrape every open job posting from any BambooHR-powered company career site (`{subdomain}.bamboohr.com/careers`) through BambooHR's own careers JSON endpoints — no login, no per-company setup. Feed it subdomains or career-site URLs and get titles, departments, locations, posting dates, and optional full descriptions as clean typed rows.

</div>

***

### 🎯 What this scrapes

BambooHR is one of the most widely deployed SMB HR platforms, and every company running a public careers page on it shares the same JSON contract: a `/careers/list` endpoint that returns the current open-postings feed, and a `/careers/{id}/detail` endpoint that returns the full posting body. This Actor talks to both directly: point it at one or more BambooHR subdomains — bare (`adobe`) or a full `https://adobe.bamboohr.com/careers` URL — and it pulls every open job, optionally fetches each posting's full description, and normalizes everything into one row schema across every tenant you throw at it.

### 🔥 Features

- 🛡️ **Browser fingerprint rotation** — `curl-cffi` rotates real Chrome / Firefox TLS and HTTP/2 handshakes across requests, so every call looks like a browser, never a bare Python client.
- 🔁 **Retries with exponential backoff** on `408 / 429 / 5xx`, up to 5 attempts, `Retry-After` honoured — a company that hiccups mid-run doesn't take the whole job down with it.
- 🌐 **Proxy session rotation** via Apify Proxy — a fresh session and exit IP whenever a request needs one, at no extra step for you.
- 🏢 **Per-company failure isolation** — an unclaimed subdomain or a company with zero current openings just returns zero rows; one bad entry in a batch never sinks the others.
- 📝 **Optional full descriptions** — flip `fetchFullDescription` on for the complete HTML body plus status, country, and posting date; leave it off for a fast titles-and-locations pull.
- 🧊 **Clean, typed rows** — Pydantic-validated, ISO-8601 timestamps, stable job IDs. Export JSON / CSV / Excel straight from the Apify Console.
- 💰 **Pay only for results that land** — a job that a company genuinely has zero of costs nothing beyond the flat per-run warm-up fee.

### 💡 Use cases

- **Recruiting & sourcing** — build a pipeline against every employer running BambooHR, in one normalized schema.
- **Hiring-intent signal for sales & BD** — open reqs surface headcount growth, new-market entry, and tech-stack shifts before they hit the news.
- **Job-board aggregation** — add BambooHR coverage alongside Workday, SmartRecruiters, Greenhouse, Lever, Ashby, Workable, and Teamtailor in one pipeline.
- **Labor-market & HR-tech research** — sample hiring demand across an industry, region, or company size band.
- **Competitive-intel tracking** — watch a competitor's live openings to infer team growth and roadmap direction.

### ⚙️ How to use it

1. Click **Try for free** at the top of the Store listing.
2. Add one or more entries to **Companies** — a bare subdomain like `adobe`, or a full `https://adobe.bamboohr.com/careers` URL.
3. Optionally set **Max jobs per company** and toggle **Fetch full description**.
4. Click **Start**. Rows stream into the dataset as each company's board is fetched.
5. Export from **Storage → Dataset** as JSON, CSV, or Excel — or pull via the Apify API.

### 📥 Input

| Field | Type | Required | Default | Notes |
|---|---|:--:|---|---|
| `companies` | `array` | ✅ | — | 1–1000 entries: bare BambooHR subdomain (`"adobe"`) or full `https://{subdomain}.bamboohr.com` URL. A custom-domain URL is rejected — use the bare subdomain instead. |
| `maxJobsPerCompany` | `integer` | no | `100` | Cap rows emitted per company, applied after parsing (1–5000). |
| `fetchFullDescription` | `boolean` | no | `true` | Fetches `/careers/{id}/detail` per job to populate `job_status`, `location_country`, `date_posted`, `description_html`, and `description_text`. Off = faster, cheaper runs; those five fields stay `null`. |
| `proxyConfiguration` | `object` | no | `{"useApifyProxy": true}` | Apify Proxy configuration. |

#### Example input

```json
{
  "companies": ["adobe", "nycballet"],
  "maxJobsPerCompany": 10,
  "fetchFullDescription": true,
  "proxyConfiguration": { "useApifyProxy": true }
}
```

### 📤 Output

One dataset item per job posting. Fields marked **(detail)** are populated only when `fetchFullDescription` is on.

| Field | Type | Notes |
|---|---|---|
| `company` | `string` | Normalized input subdomain (not any display name) — stays stable across repeated runs. |
| `job_id` | `integer` | BambooHR's numeric posting ID. |
| `job_title` | `string` | Posting title. |
| `job_url` | `string` | Public posting URL — always populated, even with descriptions off. |
| `department` | `string \| null` | Department label, when set. |
| `employment_type` | `string \| null` | e.g. `Full-Time`, when set. |
| `job_status` | `string \| null` | e.g. `Open` **(detail)**. |
| `location_city` | `string \| null` | Primary listed city. |
| `location_state` | `string \| null` | Primary listed state/region. |
| `location_country` | `string \| null` | Full country name **(detail)**. |
| `is_remote` | `boolean \| null` | Tri-state — `null` means the employer left it unspecified, not "on-site." |
| `date_posted` | `date \| null` | ISO `YYYY-MM-DD` **(detail)**. |
| `description_html` | `string \| null` | Full posting body, verbatim HTML **(detail)**. |
| `description_text` | `string \| null` | Same body with HTML tags stripped **(detail)**. |
| `scraped_at` | `datetime` | UTC ISO-8601 fetch timestamp. |

#### Example output

```json
{
  "company": "adobe",
  "job_id": 15,
  "job_title": "IT Security Engineer",
  "job_url": "https://adobe.bamboohr.com/careers/15",
  "department": "IT",
  "employment_type": "Full-Time",
  "job_status": "Open",
  "location_city": "Mayfair",
  "location_state": "London, City of",
  "location_country": "United Kingdom",
  "is_remote": null,
  "date_posted": "2025-11-29",
  "description_html": "<p><strong>About Us</strong></p>...",
  "description_text": "About Us Our mission is simple...",
  "scraped_at": "2026-07-26T12:00:00Z"
}
```

### 💰 Pricing

Pay-Per-Event — you're charged only when these fire:

| Event | USD | What it covers |
|---|---:|---|
| `actor-start` | $0.005 | One-off warm-up per run |
| `result` | $0.0015 | Per job posting written to the dataset |

**Example**: 1 000 postings ≈ **$1.51**. No subscription, no minimum, no card to start — Apify gives every new account $5 of free credit.

### 🚧 Limitations

- **Only `bamboohr.com` subdomains** — a company that fronts its BambooHR careers page with a custom domain isn't resolvable; point this Actor at the underlying `{subdomain}.bamboohr.com` address instead.
- **No compensation field** — BambooHR's public feed doesn't expose structured salary data on any company we've checked, so we don't invent it.
- **Descriptions cost an extra fetch** — `fetchFullDescription` adds one detail request per posting (it changes runtime, not the per-result price). Leave it off for the fastest pull.
- **No discovery mode** — you supply the subdomains; this Actor doesn't crawl a directory of BambooHR customers.
- **Point-in-time snapshot** — returns each company's board as it stands right now; schedule recurring runs to track changes over time.

### ❓ FAQ

**Do I need a BambooHR account or API key?** No — every field comes from BambooHR's own careers-page JSON feed, the same one the public career site itself loads.

**Does one run handle several companies?** Yes. List every subdomain in `companies` (up to 1 000) and every row comes back identically shaped, tagged with `company`.

**What happens if a subdomain doesn't exist or has zero open jobs?** Both are normal outcomes, not errors — you get zero rows for that company and the run keeps going, with a status message summarizing how many companies had openings.

**Why doesn't `maxJobsPerCompany` change the price?** You're charged per row written, not per HTTP call — capping results just controls dataset size, not the per-result rate.

**Is this legal?** We fetch public, unauthenticated job-posting data that employers publish for candidates to browse. No login, no private endpoints.

### 💬 Your feedback

Spotted a bug, hit a company that behaves differently, or need an extra field? Open an issue on the Actor's **Issues** tab in Apify Console — we ship fixes weekly and read every report.

***

<div align="center">

Built by **[Devil Scrapes](https://apify.com/DevilScrapes)** 😈 — a small fleet of
opinionated public-data Actors. Honest pricing, real engineering, zero fine print.

</div>

### 🔗 Related Devil Scrapes Actors

Hiring intel across the whole ATS landscape? These siblings cover the other major job-board platforms — same keyless, pay-per-result approach:

- [Teamtailor Jobs Scraper](https://apify.com/DevilScrapes/teamtailor-jobs-scraper) — postings from any Teamtailor-hosted employer board
- [Workable Jobs Scraper](https://apify.com/DevilScrapes/workable-jobs-scraper) — postings from any Workable-hosted employer board
- [SmartRecruiters Jobs Scraper](https://apify.com/DevilScrapes/smartrecruiters-jobs-scraper) — postings from any SmartRecruiters-hosted employer
- [Workday Jobs Scraper](https://apify.com/DevilScrapes/workday-jobs-scraper) — postings from any Workday-powered career site
- [Multi-ATS Jobs Scraper](https://apify.com/DevilScrapes/multi-ats-jobs-scraper) — one normalized feed across Greenhouse, Lever & Ashby

Browse the full fleet at [apify.com/DevilScrapes](https://apify.com/DevilScrapes).

# Actor input Schema

## `companies` (type: `array`):

Bare BambooHR subdomain, e.g. "adobe" (the employer identifier in https://{subdomain}.bamboohr.com/careers), or a full https://{subdomain}.bamboohr.com URL (with or without a trailing /careers path) — the subdomain is regex-extracted. A plain string with no "bamboohr.com" substring is used as the literal subdomain; a custom-domain URL is rejected — use the bare {subdomain}.bamboohr.com subdomain instead.

## `maxJobsPerCompany` (type: `integer`):

Cap job postings emitted per companies entry, applied after parsing.

## `fetchFullDescription` (type: `boolean`):

When enabled, fetches /careers/{id}/detail per job to populate job\_status, location\_country, date\_posted, description\_html, and description\_text. When disabled, those five fields stay null and the detail call is never issued (cheaper, faster runs).

## `proxyConfiguration` (type: `object`):

Apify Proxy configuration. No anti-bot behaviour has been observed on BambooHR's public careers JSON endpoints, so the standard (non-residential) group is enough.

## Actor input object example

```json
{
  "companies": [
    "adobe",
    "nycballet"
  ],
  "maxJobsPerCompany": 100,
  "fetchFullDescription": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `datasetItems` (type: `string`):

All dataset items as JSON.

## `datasetItemsCsv` (type: `string`):

Same data exported to CSV.

## `datasetView` (type: `string`):

Open the run dataset in the Console.

# 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 = {
    "companies": [
        "adobe",
        "nycballet"
    ],
    "maxJobsPerCompany": 100,
    "fetchFullDescription": true,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("devilscrapes/bamboohr-jobs-scraper").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 = {
    "companies": [
        "adobe",
        "nycballet",
    ],
    "maxJobsPerCompany": 100,
    "fetchFullDescription": True,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("devilscrapes/bamboohr-jobs-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "companies": [
    "adobe",
    "nycballet"
  ],
  "maxJobsPerCompany": 100,
  "fetchFullDescription": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call devilscrapes/bamboohr-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=devilscrapes/bamboohr-jobs-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/iBd8J0MtCRdm9uV39/builds/wdZwZYn7zPRdKjerd/openapi.json
