# Remote Jobs Scraper: RemoteOK, WWR, Remotive, Himalayas +2 (`mersetti/remote-jobs-aggregator`) Actor

One unified feed of remote jobs from Remote OK, We Work Remotely, Remotive, Himalayas, Jobicy and Arbeitnow. Salary, tags, region restrictions, cross-board duplicate merging, keyword and category filters, and new-job monitoring.

- **URL**: https://apify.com/mersetti/remote-jobs-aggregator.md
- **Developed by:** [Max Prozapas](https://apify.com/mersetti) (community)
- **Categories:** Jobs, Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Remote Jobs Scraper / API — Remote OK, We Work Remotely, Remotive, Himalayas, Jobicy, Arbeitnow

Get **one unified feed of remote jobs** from the biggest free remote job boards in a single run.
The Actor collects listings from Remote OK, We Work Remotely, Remotive, Himalayas, Jobicy and Arbeitnow,
converts them to **one clean format**, extracts **salary ranges**, and **merges duplicates** that are posted on several
boards — every result lists all boards it was found on, with a link back to each original posting.

**Monitor mode** remembers what it has already seen and outputs **only new remote jobs** on every scheduled run —
you pay only for fresh openings.

### Why this Actor

- **6 remote job boards in one Actor**: Remote OK, We Work Remotely, Remotive, Himalayas, Jobicy, Arbeitnow.
- **One schema for every board**: title, company, logo, location / region restrictions, employment type, category,
  tags, salary, posting date, description, apply link and source link.
- **Cross-board de-duplication**: the same job on Remote OK, Remotive and We Work Remotely becomes **one result** with
  a `sources` list (normalized company + title key, e.g. `lemonio|senior-react-full-stack-developer`).
- **Salary**: structured pay from the boards that provide it, plus ranges parsed from salary text and descriptions
  (`$35,3k- $52k`, `$45-$120/Hour`, `$150-170K`, `€50.000 - €60.000`), with currency and period (YEAR / MONTH / HOUR).
- **Filters**: keywords (title, company, tags), exclude keywords, categories, posted within N days.
- **New-job monitoring** with a free silent baseline run.
- **Reliable**: each board is fetched independently — one board being down never stops the run; a per-board report is
  saved to `SUMMARY`.
- **Polite & compliant**: official public APIs / RSS feeds only, identifying User-Agent, delays between pages,
  retries with backoff, attribution kept on every item.

### Supported job boards

| Source (`sources` value) | Endpoint | Jobs per run (approx.) | Salary | Notes |
|---|---|---|---|---|
| Remote OK (`remoteok`) | `remoteok.com/api` | latest ~100 | yes (+ description) | Attribution required — link back to Remote OK |
| Remotive (`remotive`) | `remotive.com/api/remote-jobs` | public feed (delayed 24h) | salary text | Attribution required; advise max. 4 runs/day |
| We Work Remotely (`weworkremotely`) | RSS: main feed + 10 category feeds | up to ~400 | from description | Region + allowed countries |
| Himalayas (`himalayas`) | `himalayas.app/jobs/api` (+ search) | up to 1,000 | yes | Location & timezone restrictions, seniority |
| Jobicy (`jobicy`) | `jobicy.com/api/v2/remote-jobs` | up to 1,000 | yes | Credit Jobicy, apply via original job URL |
| Arbeitnow (`arbeitnow`) | `arbeitnow.com/api/job-board-api` | remote subset of latest ~3,000 | from description | EU/Germany-focused, only remote jobs kept |

When `keywords` are set, Himalayas and Jobicy are also **searched server-side** with each keyword, so you get matching
jobs beyond the newest page.

### Use cases

- **Remote job boards & newsletters**: fill a niche board ("remote Python jobs", "remote design jobs") with fresh
  listings from six sources, without duplicates.
- **Job alerts**: schedule every few hours with `onlyNewJobs` and send new matching jobs to Slack, email or Telegram.
- **Job seekers**: one searchable spreadsheet of remote jobs with salary and region restrictions.
- **Market research**: which companies hire remotely, for which roles, at what salary.
- **AI agents & RAG**: structured, de-duplicated remote job data with descriptions.

### Input

| Field | Type | Description |
|---|---|---|
| `sources` | array | Job boards to use: `remoteok`, `remotive`, `weworkremotely`, `himalayas`, `jobicy`, `arbeitnow`. Default: all. |
| `keywords` | array | Keep jobs whose title, company or tags contain ANY keyword (whole words incl. plurals, `engineer` → `Engineering`). |
| `excludeKeywords` | array | Drop jobs whose title or company contains any of these words (e.g. `senior`, `sales`). |
| `categories` | array | Keep jobs whose category or tags contain any of these texts (`software`, `devops`, `design`, `marketing`…). |
| `postedWithinDays` | integer | Only jobs posted in the last N days (0 = any). |
| `includeDescription` | boolean | Add plain-text `descriptionText` (default on). |
| `maxJobsPerSource` | integer | Max matching jobs per board, newest first (default 100, max 1000). |
| `onlyNewJobs` | boolean | Monitor mode: output only jobs not seen in previous runs. |
| `monitorId` | string | Name of the saved monitor state — use different IDs for different searches. |
| `baselineOnFirstRun` | boolean | First monitor run only saves the current jobs (no output, no cost). Default on. |

#### Example input

```json
{
  "sources": ["remoteok", "remotive", "weworkremotely", "himalayas", "jobicy", "arbeitnow"],
  "keywords": ["engineer", "developer"],
  "excludeKeywords": ["senior", "lead"],
  "categories": ["software", "engineering", "programming", "devops"],
  "postedWithinDays": 7,
  "includeDescription": false,
  "maxJobsPerSource": 40
}
```

### Output

Each dataset item is one unique remote job. Real example (description shortened) — the same posting was found on
Remote OK and We Work Remotely and merged into one result; the logo and category were filled in from We Work Remotely:

```json
{
  "source": "remoteok",
  "sourceName": "Remote OK",
  "jobId": "1137506",
  "title": "Technical Customer Support",
  "company": "QuickMail",
  "companyLogo": "https://wwr-pro.s3.amazonaws.com/logos/0064/3699/logo.gif",
  "location": "Anywhere",
  "locationRestrictions": [],
  "remote": true,
  "employmentType": "Full-Time",
  "seniority": null,
  "category": "Customer Support",
  "tags": ["customer support", "Technical Support and Customer Support"],
  "salary": { "min": 30000, "max": 50000, "currency": "USD", "period": "YEAR", "text": null, "extractedFrom": "source" },
  "postedAt": "2026-10-12T16:20:10.000Z",
  "descriptionText": "QuickMail is a cold email and LinkedIn outreach platform for markets you can't afford to burn…",
  "applyUrl": "https://remoteOK.com/remote-jobs/remote-technical-customer-support-quickmail-1137506",
  "sourceUrl": "https://remoteOK.com/remote-jobs/remote-technical-customer-support-quickmail-1137506",
  "dedupKey": "quickmail|technical-customer-support",
  "sources": [
    {
      "source": "remoteok",
      "sourceName": "Remote OK",
      "jobId": "1137506",
      "sourceUrl": "https://remoteOK.com/remote-jobs/remote-technical-customer-support-quickmail-1137506",
      "applyUrl": "https://remoteOK.com/remote-jobs/remote-technical-customer-support-quickmail-1137506"
    },
    {
      "source": "weworkremotely",
      "sourceName": "We Work Remotely",
      "jobId": "quickmail-technical-customer-support",
      "sourceUrl": "https://weworkremotely.com/remote-jobs/quickmail-technical-customer-support",
      "applyUrl": "https://weworkremotely.com/remote-jobs/quickmail-technical-customer-support"
    }
  ],
  "duplicateCount": 1,
  "scrapedAt": "2026-10-14T09:54:01.476Z"
}
```

- `locationRestrictions: []` means the job is open worldwide; otherwise it lists the allowed countries / regions.
- `salary.extractedFrom` is `source` (structured board data), `field` (board's salary text) or `description`.
- In monitor mode each item also has `firstSeenAt`.
- Fields a board does not provide are `null`.

A per-board report (`status`, `fetched`, `matched`, `output`, `mergedDuplicates`, `requests`, `error`) is saved to the
`SUMMARY` record of the run's key-value store.

### Pricing — pay per job

You pay a small fee **per job in the dataset** (event `job`) — no monthly rental. Duplicates merged across boards are
charged **once**. In monitor mode you pay only for **new** jobs, and the first (baseline) run outputs nothing, so a
schedule every few hours typically costs cents per day. Set a maximum cost per run and the Actor stops cleanly when
it is reached.

### Tips

- **Job alerts**: enable `onlyNewJobs`, create a Schedule (e.g. every 6 hours) and connect an integration
  (Slack, email, Google Sheets, webhook, Zapier, Make).
- **Remotive** asks API users to fetch at most ~4 times a day — don't schedule more often if Remotive is selected.
- **Bigger result sets**: raise `maxJobsPerSource`; Himalayas and Jobicy go up to 1,000 jobs per run.
- **Leaner dataset**: switch off `includeDescription`.

### FAQ

**Which remote job boards are supported?** Remote OK, We Work Remotely, Remotive, Himalayas, Jobicy and Arbeitnow
(remote jobs only). All are read through their official public APIs or RSS feeds — no login, no browser.

**How are duplicates detected?** Each job gets a `dedupKey` from the normalized company name (legal suffixes like
Inc/GmbH removed) and title (gender tags like "(m/w/d)", "Remote", "Sr."/"Senior" normalized). Jobs with the same key
are merged; `sources` lists every board and link, and missing fields (logo, salary, category…) are filled from the
duplicates.

**Can I get only new remote jobs every day?** Yes — enable `onlyNewJobs` and schedule the Actor. The first run saves
a silent baseline; each later run outputs only jobs that appeared since. Use different `monitorId`s for different
searches.

**What happens if a job board is down?** The other boards still run. The failed board is reported in the `SUMMARY`
record and in the run's status message; the run fails only if every selected board fails.

**Can I export to Google Sheets, Excel or CSV?** Yes — use the dataset export (JSON, CSV, Excel, XML, RSS) or Apify
integrations. AI agents can call the Actor via the Apify MCP server.

**Does it scrape job descriptions?** Yes, as plain text (`descriptionText`) when `includeDescription` is on.

### Legal & attribution

This Actor uses only the free public APIs and RSS feeds that these job boards publish for sharing their listings.
Each board asks that re-published jobs **credit the board and link back to the original posting** — every result
therefore includes `sourceName`, `sourceUrl` and `applyUrl` pointing to the original listing (and a `sources` list for
merged duplicates). If you display these jobs publicly, **show the source name and keep the link** (Remote OK asks for a
followed link, without `nofollow`, and does not allow use of its logo without permission). Remotive does not allow its
jobs to be submitted to third-party job sites such as Google Jobs, LinkedIn Jobs, Jooble or Neuvoo; Jobicy asks that
apply buttons point to the original job URL. Please read and respect each board's terms:
[Remote OK](https://remoteok.com/api), [Remotive](https://remotive.com/api-documentation),
[We Work Remotely](https://weworkremotely.com), [Himalayas](https://himalayas.app/api),
[Jobicy](https://jobicy.com/jobs-rss-feed), [Arbeitnow](https://www.arbeitnow.com/blog/job-board-api).
Job descriptions remain the property of the employers and boards. The Actor collects no personal data.

### Related tools

- [Career Sites Jobs Feed (Workday, Greenhouse, Lever, Ashby +7)](https://apify.com/mersetti/ats-jobs-feed) —
  **jobs straight from company career sites**: live listings from any company's ATS, with salary and new/closed job
  monitoring.
- [Substack to Markdown for AI & RAG](https://apify.com/mersetti/substack-to-markdown)
- [Shopify Products & Price Monitor](https://apify.com/mersetti/shopify-products-monitor)

# Actor input Schema

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

Remote job boards to aggregate. Duplicates found on several boards are merged into one result (all sources are listed in its "sources" field).

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

Keep jobs whose title, company or tags contain ANY of these words (case-insensitive, whole words incl. plurals and -ing/-er forms: "engineer" also matches "Engineering"). Empty = all jobs. Himalayas and Jobicy are searched server-side with these keywords.

## `excludeKeywords` (type: `array`):

Drop jobs whose title or company contains any of these words, e.g. intern, sales, crypto.

## `categories` (type: `array`):

Keep jobs whose category or tags contain ANY of these texts (case-insensitive substring), e.g. software, devops, design, marketing, data, customer support. Empty = all categories.

## `postedWithinDays` (type: `integer`):

Only jobs posted in the last N days. 0 = any date.

## `includeDescription` (type: `boolean`):

Adds the plain-text job description (descriptionText). Switch off for a smaller, faster dataset.

## `maxJobsPerSource` (type: `integer`):

Maximum number of matching jobs taken from each job board (before duplicates are merged). The newest jobs come first.

## `onlyNewJobs` (type: `boolean`):

Remembers jobs between runs and outputs only jobs not seen before. Schedule the Actor (e.g. every 6 hours) to get a feed of fresh remote jobs. You pay only for new jobs.

## `monitorId` (type: `string`):

Name of the saved state. Use different IDs for different searches (e.g. "python-jobs", "design-jobs").

## `baselineOnFirstRun` (type: `boolean`):

On the first monitor run only save the current jobs (no output, no cost), so later runs report truly new jobs.

## Actor input object example

```json
{
  "sources": [
    "remoteok",
    "remotive",
    "weworkremotely",
    "himalayas",
    "jobicy",
    "arbeitnow"
  ],
  "keywords": [
    "engineer",
    "developer"
  ],
  "postedWithinDays": 0,
  "includeDescription": true,
  "maxJobsPerSource": 100,
  "onlyNewJobs": false,
  "monitorId": "default",
  "baselineOnFirstRun": true
}
```

# Actor output Schema

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

No description

## `summary` (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 = {
    "sources": [
        "remoteok",
        "remotive",
        "weworkremotely",
        "himalayas",
        "jobicy",
        "arbeitnow"
    ],
    "keywords": [
        "engineer",
        "developer"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("mersetti/remote-jobs-aggregator").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",
        "remotive",
        "weworkremotely",
        "himalayas",
        "jobicy",
        "arbeitnow",
    ],
    "keywords": [
        "engineer",
        "developer",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("mersetti/remote-jobs-aggregator").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 '{
  "sources": [
    "remoteok",
    "remotive",
    "weworkremotely",
    "himalayas",
    "jobicy",
    "arbeitnow"
  ],
  "keywords": [
    "engineer",
    "developer"
  ]
}' |
apify call mersetti/remote-jobs-aggregator --silent --output-dataset

```

## MCP server setup

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

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/5UPmViWHGBWHBzm5T/builds/euH9KOHXUJPXa3CDc/openapi.json
