# Job Boards Scraper — RemoteOK, Arbeitnow, Muse, WeWorkRemotely (`quarry/job-boards-scraper`) Actor

Four public job boards in one unified schema. Same output shape as our ATS Jobs Scraper, so both datasets union without an adapter.

- **URL**: https://apify.com/quarry/job-boards-scraper.md
- **Developed by:** [Quarry](https://apify.com/quarry) (community)
- **Categories:** Jobs, Business, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 job scrapeds

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

## Job Boards Scraper — four boards, one schema

RemoteOK, Arbeitnow, The Muse and WeWorkRemotely in a single unified output. Filter across all four at once instead of writing four adapters.

### Sample output

```json
{
  "id": "arbeitnow:member-of-technical-staff-research-engineer",
  "source": "arbeitnow",
  "board": "arbeitnow",
  "company": "blackforestlabs",
  "title": "Member of Technical Staff - Research Engineer",
  "location": "San Francisco (USA)",
  "remote": false,
  "department": null,
  "employmentType": "Full Time",
  "postedAt": "2026-08-02T23:56:25.000Z",
  "applyUrl": "https://www.arbeitnow.com/jobs/companies/blackforestlabs/…",
  "descriptionText": "We are looking for a research engineer to…",
  "tags": ["ai", "research"],
  "attribution": null
}
```

Every row carries these 14 fields and no others. There is no `salaryText` and no
`updatedAt`: none of the four boards publishes a salary any more (RemoteOK still has
the field but returns `0`/`0` on every row), and none publishes a modification
timestamp. Rather than ship two permanently-null columns, they are not emitted.
`descriptionText` is plain text — entity-encoded markup is decoded and stripped, so
you never receive `&lt;p&gt;` or a live `<p>` tag.

### Why this one

- **One schema across four boards.** `company` means `company` everywhere. Most tools return each board's native field names and leave the normalising to you.
- **Same shape as our ATS Jobs Scraper.** Run both and union the datasets — no adapter, no field mapping. Together they cover public boards *and* company applicant tracking systems.
- **Filter before you pay.** Keywords, location, remote and date filters are applied before rows are billed.
- **Depth when you want it.** The Muse alone exposes 400,000+ jobs across 20,000+ pages. `maxPagesPerSource` controls how deep you go.
- **Attribution passed through.** Where a source supplies attribution text with its data, it is carried in the `attribution` field rather than dropped.

### Input

| Field | What it does |
|---|---|
| `sources` | Which boards. Defaults to all four. |
| `keywords` | Match in title, company, tags or description. |
| `locations` / `remoteOnly` / `postedAfter` | Standard filters. |
| `maxPagesPerSource` | Depth per board. Default 3. |

```json
{ "keywords": ["engineer", "developer"], "remoteOnly": true, "maxPagesPerSource": 5 }
```

### Reliability

Every run writes `RUN_STATS` with per-source page outcomes — `ok`, `empty`, `drift`, `transport`, `rate_limited` — plus the sources that answered and the ones that did not, by name.

**A blocked source is never reported as a quiet one.** A source that fails to respond is counted as `transport` or `rate_limited`, never as `empty`, and the failing source is named in the log. If one source is down the run still succeeds with the other three, because partial coverage is resilience. If *every* source fails, **the run fails** — you get a red run and an explanation instead of a green run and an empty dataset.

`drift` means a source returned records but our parser produced none. That is our bug, not your empty week, and it is reported as such.

When a run legitimately matches nothing — a narrow keyword, `remoteOnly`, a tight `postedAfter` — the dataset gets a single unbilled receipt row instead of being left empty:

```json
{ "type": "no_changes", "sourcesChecked": 4, "sourcesAttempted": 4, "jobsSeen": 635, "checkedAt": "2026-08-05T12:11:02.476Z" }
```

Job rows never carry a `type` field, so your pipeline can branch on it.

### Pricing

Pay per job returned. Filtered-out jobs and failed pages cost nothing.

# Actor input Schema

## `sources` (type: `array`):

Which boards to pull. Defaults to all four.

## `keywords` (type: `array`):

Keep only jobs matching any term in title, company, tags or description.

## `locations` (type: `array`):

Keep only jobs whose location matches any of these, e.g. Berlin, United States. Leave empty for all locations.

## `remoteOnly` (type: `boolean`):

Drop anything not flagged remote by the board.

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

Ignore jobs posted before this date. Leave empty for no cutoff.

## `maxPagesPerSource` (type: `integer`):

The Muse has 20,000+ pages; raise this to go deeper.

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

Hard ceiling on billable jobs for the whole run. 0 = no limit.

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

How many boards to fetch in parallel. Raise for speed, lower if a board rate-limits you.

## Actor input object example

```json
{
  "sources": [
    "remoteok",
    "arbeitnow",
    "themuse",
    "weworkremotely"
  ],
  "keywords": [
    "engineer"
  ],
  "remoteOnly": false,
  "maxPagesPerSource": 3,
  "maxItems": 0,
  "concurrency": 4
}
```

# 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 = {
    "sources": [
        "remoteok",
        "arbeitnow",
        "themuse",
        "weworkremotely"
    ],
    "remoteOnly": false,
    "maxPagesPerSource": 3,
    "maxItems": 0,
    "concurrency": 4
};

// Run the Actor and wait for it to finish
const run = await client.actor("quarry/job-boards-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 = {
    "sources": [
        "remoteok",
        "arbeitnow",
        "themuse",
        "weworkremotely",
    ],
    "remoteOnly": False,
    "maxPagesPerSource": 3,
    "maxItems": 0,
    "concurrency": 4,
}

# Run the Actor and wait for it to finish
run = client.actor("quarry/job-boards-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 '{
  "sources": [
    "remoteok",
    "arbeitnow",
    "themuse",
    "weworkremotely"
  ],
  "remoteOnly": false,
  "maxPagesPerSource": 3,
  "maxItems": 0,
  "concurrency": 4
}' |
apify call quarry/job-boards-scraper --silent --output-dataset

```

## MCP server setup

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

```

## OpenAPI specification

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