# Remote Jobs Scraper & API: 4 Boards, One Shape, 35 Fields (`dododata/remote-jobs-search`) Actor

Remote jobs from Himalayas, Remote OK, Jobicy and Arbeitnow in one 35-field shape, de-duplicated across boards. Himalayas timezones included. $1 per 1,000.

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

## Pricing

from $0.70 / 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.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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: 4 Boards, One Shape, 35 Fields

**Four public remote-job boards, one row shape, de-duplicated across all four.** Himalayas, Remote OK, Jobicy and Arbeitnow, read through the APIs their owners publish, in clean JSON: **35 fields per job, 200 jobs from all four boards in about a minute, 0 failures.**

Himalayas alone was listing **104,515 open remote jobs** when this was written, and it is the only one of the four that publishes **which timezones a job may be worked from**. That field survives into every row.

### What you get

```json
{
  "id": "himalayas:https://himalayas.app/companies/mercor/jobs/french-voice-actor-tts-expert-7606534085",
  "source": "himalayas",
  "source_name": "Himalayas",
  "company": "mercor",
  "title": "French Voice Actor - TTS Expert",
  "location": "Belgium",
  "locations": ["Belgium"],
  "workplace_type": "remote",
  "remote": true,
  "employment_type": "Full Time",
  "experience_level": "Mid-level",
  "salary_min": 50,
  "salary_max": 150,
  "salary_currency": "USD",
  "salary_period": "hourly",
  "salary_source": "structured",
  "tags": ["Voice-Acting", "Text-To-Speech", "French-Voice-Over", "..."],
  "timezone_offsets": [1],
  "posted_at": "2026-09-22T04:12:56.000Z",
  "expires_at": "2026-11-21T04:12:55.000Z",
  "description": "About the job Mercor connects elite creative and technical talent with leading AI research labs ...",
  "apply_url": "https://himalayas.app/companies/mercor/jobs/french-voice-actor-tts-expert-7606534085",
  "url": "https://himalayas.app/companies/mercor/jobs/french-voice-actor-tts-expert-7606534085",
  "change_status": "new",
  "first_seen_at": "2026-09-22T08:20:11.252Z",
  "scraped_at": "2026-09-22T08:20:11.722Z"
}
```

### The four boards

| Board | What it is | Size | What it publishes that the others do not |
|---|---|---|---|
| **Himalayas** | the largest of the four, pages with a cursor | **about 100,000** open remote jobs (104,515 when first measured, 99,828 on 24 Sept) | **timezone restrictions**, an expiry date, seniority |
| **Remote OK** | tech-heavy, one request returns its newest 99 | 99 per run | a salary range on some of its jobs |
| **Jobicy** | curated, one request returns up to 100 | 100 per run | an industry tag and a clean region list |
| **Arbeitnow** | Europe-heavy, 250 jobs a page | a few remote per page | German-language listings you will not find on the other three |

In a measured run of **200 rows on Apify**: 143 from Himalayas, 27 from Jobicy, 19 from Arbeitnow, 11 from Remote OK, **200 distinct jobs, 0 failures**. Each board is held to an equal share of your row budget until every board has answered, so you get a mix rather than 200 rows of whichever one is fastest.

**Remote OK sets the pace of a run.** It answers in one request, but that request is slow and sometimes has to be retried: measured on 2026-09-22 it answered in 5 seconds on one run and 84 on another, and the other boards wait for it rather than spending its share. So a 200-row run takes about one to three minutes. If you want the fastest possible run and can do without its 99 tech jobs, leave `remoteok` out of `networks`; the remaining three answer in seconds.

**The same job is often on two of these boards.** Rows are de-duplicated on company and title across all four, so you are billed once for it, not twice.

### Filters, applied before a row is saved

You are charged per row, so every filter runs before anything is written and you never pay for what you filter out.

| Filter | What it does |
|---|---|
| **Keywords** | keep a job if its title **or its tags** contain any of these words |
| **Exclude keywords** | drop it on the title only, so a stray tag does not cost you a job |
| **Region restrictions** | `Europe`, `UK`, `United States`, `LATAM`, `APAC` or a country. Region names cover their countries, so `Europe` finds a Himalayas job listed for Germany. Whole words only: `UK` never matches Ukraine. A job with no published restriction is open to anyone and is kept only if you add `Worldwide` |
| **Only jobs with pay** | keep only jobs where the board published a range. In the measured run, **172 of 400** had one |
| **Minimum salary** | a yearly figure: hourly and monthly pay are annualised first, and only pay in USD, EUR, GBP, CHF, CAD or AUD is compared |
| **Posted within (days)** | a job whose board published no date is kept, not guessed at |
| **Job boards to read** | run one board, or all four |

A filter that matches nothing does not cost you hours. Himalayas lists about 100,000 jobs, so after **50 pages in a row (1,000 jobs) with no match** the run stops paging it and the log says so, rather than reading the whole feed to return nothing. A filter that matches even once every few pages keeps going.

### A daily feed of only the jobs you have not had

Give the run a **Monitor name** and put it on a schedule. Every row then says whether it is `new`, `updated` or already delivered, and carries `first_seen_at`. With **Only changes** on, jobs you have already received are left out and **not billed**.

Measured, two runs of the same monitor minutes apart:

```text
run 1   Monitor "remote-watch": 100 new · 0 updated · 0 already delivered before
run 2   Monitor "remote-watch": 100 new · 0 updated · 100 already delivered before
```

The second run returned **100 different jobs**: it skipped everything it had already delivered and paged deeper to fill the budget with jobs you have not seen.

Each of these feeds is a window on to its own board and never a complete listing, so this Actor **never claims a job was removed**. Absence from a feed proves nothing.

### Pay

`salary_min`, `salary_max`, `salary_currency` and `salary_period` are filled only from the board's own pay fields, and `salary_source` is `structured` when that happened and `null` when it did not. **Nothing on this Actor is read out of the job description**, so no number here is a guess. Ranges come through as the board published them, hourly as well as annual: check `salary_period` before you compare two rows.

### Timezones, which only Himalayas publishes

`timezone_offsets` is the list of UTC offsets a remote job may be worked from, for example `[-8, -7, -6]` for US Pacific to Central, `[1]` for Central Europe, or `[5.5]` for India: half-hour and quarter-hour zones are kept. In the measured run **191 of 400 rows carried one**. It is `null` on Remote OK, Jobicy and Arbeitnow, which do not publish it, rather than filled in with a guess.

If you are matching candidates to jobs by working hours, this is the field: it is the difference between "remote" and "remote, but you have to be awake at 3am".

### Where the data comes from

This Actor reads only the public APIs these four boards publish for the purpose, at the pace their `robots.txt` asks for, and credits each of them:

- **Himalayas** — https://himalayas.app
- **Remote OK** — https://remoteok.com
- **Jobicy** — https://jobicy.com
- **Arbeitnow** — https://www.arbeitnow.com

Every row carries `source`, `source_name` and a `url` pointing at the **original job page on the board it came from**, which is what Remote OK, Jobicy and Arbeitnow each ask of anyone using their feed. `apply_url` points there too, so an application always reaches the board that published it. If you build a product on this dataset, keep those links followed and visible: it is the only thing these boards ask in return for a free API.

No login is used, no page behind one is read, and **no personal data is collected**. Contact details that turn up inside a job description are removed before the row is written.

### Price

**$1 per 1,000 jobs**, plus $0.0002 per run start. Pay per event: you pay for rows you actually receive, and filters and monitor mode run before billing.

| You want | Rows | Cost |
|---|---|---|
| a look at what the boards have | 100 | $0.10 |
| a working dataset | 1,000 | $1.00 |
| a daily monitor on a keyword, for a month, with **Max jobs 50** or **Posted within 1 day** | ~1,500 | ~$1.56 |

On a monitor, set one of those two. With the default of 500 and Only changes on, each run skips what you already have and pages deeper to fill the 500, so it bills 500 progressively older jobs a day (about $15 a month) instead of the handful that are new.

### Also from Dodo Data

- **[Job Search API & Monitor](https://apify.com/dododata/job-search-api)** — search 168,000 jobs from company career pages by words and location, with no URLs and no company list.
- **[Career Site Jobs Scraper](https://apify.com/dododata/career-site-jobs-scraper)** — point it at any company's career page on Greenhouse, Lever, Ashby or Workday and get the same row shape, with pay read out of the description when the employer only states it in prose.

# Actor input Schema

## `networks` (type: `array`):

Leave empty for all four. Himalayas is the biggest (about 100,000 open remote jobs) and the only one that publishes timezone restrictions. Remote OK is tech-heavy and returns its newest 99. Jobicy is curated and returns up to 100. Arbeitnow is Europe-heavy and only a small share of its board is remote.

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

Keep a job if its title or tags contain any of these words. Case and accents are ignored. Leave empty for every job.

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

Drop a job if its title contains any of these words, for example senior, intern, sales.

## `regions` (type: `array`):

Keep a job only if its board says it can be worked from one of these, for example Europe, UK, United States, LATAM, APAC, Germany. Region names cover their countries, because Himalayas lists countries (Germany, Portugal) where the other boards write Europe or EMEA. A job whose board publishes no restriction is open to anyone: add Worldwide to keep those too. Arbeitnow lists cities (Berlin, Hamburg), so only a city name matches there.

## `onlyWithSalary` (type: `boolean`):

Keep only jobs where the board published a salary range. Roughly a third of them do.

## `minSalary` (type: `integer`):

Keep only jobs whose published pay reaches at least this much a year. Hourly and monthly pay is annualised first (x2,080 and x12). Compared in the board's own currency, and only for pay in US dollars, euros, pounds, Swiss francs, Canadian or Australian dollars: a job paid in another currency, or with no published pay, is left out when this is set.

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

Keep only jobs posted in the last N days. A job whose board published no date is kept rather than guessed at.

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

The full description as plain text, with contact details removed. Turn off for a much smaller dataset.

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

Turns on change tracking. Pick any name, for example remote-backend-watch, and use the same name on every run. Each row then says whether it is new, updated or unchanged since the previous run under that name. The first run marks everything new. State is kept in a key-value store on your own Apify account.

## `onlyChanges` (type: `boolean`):

Needs a monitor name. Jobs you already received and that have not changed are left out of the results and not billed.

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

Stop after this many jobs across all boards. You pay per row, so this is also your budget cap.

## `maxConcurrency` (type: `integer`):

Parallel requests. 4 is polite and fast enough for four feeds.

## `useProxy` (type: `boolean`):

Off by default: these four public feeds answered 4.4 times cheaper without it (7 s against 40 s for 150 jobs, 25 Sept 2026). Turn it on only if a run reports being rate-limited or blocked.

## Actor input object example

```json
{
  "networks": [
    "himalayas",
    "remoteok",
    "jobicy",
    "arbeitnow"
  ],
  "keywords": [],
  "excludeKeywords": [],
  "regions": [],
  "onlyWithSalary": false,
  "includeDescription": true,
  "onlyChanges": false,
  "maxItems": 500,
  "maxConcurrency": 4,
  "useProxy": false
}
```

# Actor output Schema

## `results` (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 = {
    "networks": [
        "himalayas",
        "remoteok",
        "jobicy",
        "arbeitnow"
    ],
    "keywords": [],
    "excludeKeywords": [],
    "regions": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("dododata/remote-jobs-search").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 = {
    "networks": [
        "himalayas",
        "remoteok",
        "jobicy",
        "arbeitnow",
    ],
    "keywords": [],
    "excludeKeywords": [],
    "regions": [],
}

# Run the Actor and wait for it to finish
run = client.actor("dododata/remote-jobs-search").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 '{
  "networks": [
    "himalayas",
    "remoteok",
    "jobicy",
    "arbeitnow"
  ],
  "keywords": [],
  "excludeKeywords": [],
  "regions": []
}' |
apify call dododata/remote-jobs-search --silent --output-dataset

```

## MCP server setup

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

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/h25P1SilWCpD0WmdA/builds/CzVicR98SHx7td1x7/openapi.json
