# Remote Jobs API — RemoteOK, WWR, Himalayas & 4 More (`insight.solutions/remote-jobs-api`) Actor

Seven remote job boards in one normalized feed: RemoteOK, Remotive, We Work Remotely, Himalayas, Jobicy, Working Nomads and Arbeitnow. Deduplicated across boards with an alsoOn list, full descriptions at no surcharge, filters applied before the charge, monitor mode for new jobs only. No API key.

- **URL**: https://apify.com/insight.solutions/remote-jobs-api.md
- **Developed by:** [Insight Solutions](https://apify.com/insight.solutions) (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

from $0.30 / 1,000 job returneds

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?

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

## Remote Jobs API — RemoteOK, WWR, Himalayas & 4 More

Seven public remote job boards in **one schema**, deduplicated across them:
**RemoteOK**, **Remotive**, **We Work Remotely**, **Himalayas**, **Jobicy**,
**Working Nomads** and **Arbeitnow**. Same columns, same run, same query.

Read straight from each board's own public feed. No API key, no login, no
browser, no aggregator in the middle.

> **You are charged for the jobs you get, not the jobs we read.** Every filter —
> keyword, location, salary, tag, timezone, date — is applied **before** the
> charge event. A run that reads 500 postings and returns 20 bills you for 20.

### At a glance

**Input** — this is the Store prefill; paste it and run:

```json
{
  "boards": ["remoteok", "remotive", "himalayas"],
  "keywords": ["developer", "engineer"],
  "maxJobs": 25,
  "remoteOnly": true,
  "monitor": false
}
```

**Output** — one row per unique job posting; the fields you will use most are
`title`, `company`, `applyUrl`, `board` and `postedAt` (full list under *Output
reference*). Anything that could not be fetched comes back as a free diagnostic
row (`ok: false`, `errorType`, `error`) instead of a charge.

**Price** — **$0.50 per 1,000 jobs** on the FREE tier (+ $0.001 per run);
filtered-out jobs, cross-board duplicates, full descriptions, summary rows,
diagnostics and a monitor's baseline run are all free. Pay-per-event, no API
key, no browser, limited permissions — works over the Apify MCP server
(`mcp.apify.com`) and with agentic (x402) payments.

**From code** —
`client.actor("insight.solutions/remote-jobs-api").call(run_input={…})` with
`apify-client`, or `POST
https://api.apify.com/v2/acts/insight.solutions~remote-jobs-api/run-sync-get-dataset-items`.

***

### What you get

One row per unique posting. Same columns whichever board it came from:

```json
{
  "rowType": "job",
  "jobId": "himalayas:biocatch/sales-account-manager-iberia",
  "board": "himalayas",
  "boardJobId": "biocatch/sales-account-manager-iberia",
  "url": "https://himalayas.app/companies/biocatch/jobs/sales-account-manager-iberia",
  "applyUrl": "https://himalayas.app/companies/biocatch/jobs/sales-account-manager-iberia",
  "company": "BioCatch",
  "companyLogo": "https://cdn-images.himalayas.app/wb53l4a8sm9f3f8mqkjnyapvsoga",
  "companyUrl": "https://himalayas.app/companies/biocatch",
  "title": "Sales Account Manager Iberia",
  "category": "Sales",
  "categories": ["Sales"],
  "tags": ["Sales And Account Management", "B2B Sales", "Account Manager"],
  "employmentType": "FullTime",
  "seniority": "manager",
  "locationRaw": "Spain",
  "countries": ["Spain"],
  "remoteRegions": null,
  "timezones": [0, 1],
  "isRemote": true,
  "workplaceType": "remote",
  "salaryMin": null,
  "salaryMax": null,
  "postedAt": "2026-09-15T19:29:00.000Z",
  "expiresAt": "2026-11-14T19:28:59.000Z",
  "alsoOn": null,
  "alsoOnBoards": null,
  "source": "himalayas.app"
}
```

…and the same row for a posting that turned up on three boards at once, with the
other two folded into it:

```json
{
  "rowType": "job",
  "jobId": "himalayas:proxify/senior-data-engineer-azure",
  "board": "himalayas",
  "company": "Proxify AB",
  "title": "Senior Data Engineer (Azure)",
  "salaryMin": 70200,
  "salaryMax": 117000,
  "salaryCurrency": "USD",
  "salaryPeriod": "YEAR",
  "alsoOn": [
    {
      "board": "weworkremotely",
      "url": "https://weworkremotely.com/remote-jobs/proxify-ab-senior-data-engineer-azure",
      "applyUrl": "https://weworkremotely.com/remote-jobs/proxify-ab-senior-data-engineer-azure",
      "boardJobId": "proxify-ab-senior-data-engineer-azure",
      "postedAt": "2026-09-15T12:04:11.000Z"
    },
    {
      "board": "workingnomads",
      "url": "https://www.workingnomads.com/job/go/1864102/",
      "applyUrl": "https://www.workingnomads.com/job/go/1864102/",
      "boardJobId": "1864102",
      "postedAt": "2026-09-15T09:31:00-04:00"
    }
  ],
  "alsoOnBoards": ["weworkremotely", "workingnomads"]
}
```

**That is one row and one charge, not three.** The richest copy wins, its empty
columns are filled in from the others, tags and countries are merged, and every
place the job was found is on the row so you can still link to whichever board
you like.

***

### Quick start

Leave everything on its defaults and run it. You get the newest jobs from all
seven boards, deduplicated, remote only.

Some things people actually ask:

| You want | Input |
|---|---|
| Remote Python jobs, anywhere | `{ "keywords": ["python"], "tags": ["python"] }` |
| Senior engineering roles paying over $120k | `{ "keywords": ["engineer"], "seniority": ["senior", "staff", "principal"], "salaryMin": 120000 }` |
| Jobs open to Europe, posted this week | `{ "locations": ["Europe"], "postedSince": "7d" }` |
| Jobs an employer will hire across UTC−5 | `{ "timezones": [-5] }` *(Himalayas is the only board that publishes this)* |
| Only jobs with a published salary | `{ "salaryOnly": true }` |
| Full descriptions for an LLM pipeline | `{ "includeDescription": true, "descriptionFormat": "markdown" }` |
| Everything one company is hiring for | `{ "companies": ["Proxify"] }` |
| A daily alert of new jobs only | `{ "monitor": true }` on a schedule |
| The deep board on its own | `{ "boards": ["himalayas"], "himalayasMaxPages": 50, "maxJobs": 0 }` |

***

### What each board actually gives you

Measured 2026-09-15. These are not marketing numbers — they are what one run
returned, and the Actor will not pretend otherwise.

| Board | What one run returns | Freshness | Structured salary |
|---|---|---|---|
| **RemoteOK** | **newest ~100** (the public API's cap; there is no paging) | newest first | bare numbers on ~12% of rows, labelled USD/year by board convention |
| **Remotive** | **the whole public feed — 15 jobs today**, `job-count` = `total-job-count` | **delayed 24 hours by Remotive's own terms** | free text only, 9 of 15 rows |
| **We Work Remotely** | **88 items — the newest ~10 per category**, not the newest 88 on the board | newest first per category | none |
| **Himalayas** | **104,000+, paged 20 at a time** | live | yes — min, max, currency and period on ~55% of rows |
| **Jobicy** | up to 200 per request | updated hourly | yes, ~50% of rows |
| **Working Nomads** | 50 | newest first | none |
| **Arbeitnow** | 250 per page, **of which ~4% are flagged remote** | updated hourly | none |

Himalayas is the board with depth. Remotive and We Work Remotely are small
today and are in the default list because they carry jobs the others do not, not
because they carry volume. Arbeitnow is a European aggregator: with `remoteOnly`
on it contributes about ten rows per page, and the 240 it does not contribute
are read and discarded **for free** — the `board-summary` row shows you exactly
that.

***

### Input

| Field | Type | Default | What it does |
|---|---|---|---|
| `boards` | multi-select | all seven | Which boards to read |
| `keywords` | array | none | Title, tags and category must contain one of these |
| `excludeKeywords` | array | none | Drop jobs containing one of these, description included |
| `keywordsMatchDescription` | boolean | `false` | Extend `keywords` to the posting body |
| `categories` | array | none | Match the board's own category names. Also picks which We Work Remotely category feed is fetched |
| `tags` | array | none | Match the board's tags. The first entry is sent to RemoteOK as `?tag=` |
| `locations` | array | none | Free text over the location line, countries and remote regions. The first entry is sent to Jobicy as `geo` when it maps |
| `timezones` | array of ints | none | UTC offsets the employer will hire across. **Himalayas only** |
| `employmentTypes` | multi-select | none | `FullTime`, `PartTime`, `Contract`, `Temporary`, `Internship`, `Volunteer`, `Other` |
| `seniority` | multi-select | none | `intern` … `executive` |
| `companies` / `excludeCompanies` | array | none | Include or exclude employers by name |
| `salaryMin` | integer | `0` | USD-per-year floor, compared to the top of the published range |
| `salaryOnly` | boolean | `false` | Drop jobs with no published pay |
| `postedSince` | string | none | ISO date or a relative window: `48h`, `7d`, `4w` |
| `remoteOnly` | boolean | `true` | Only bites on Arbeitnow; the other six are remote-only by construction |
| `maxJobs` | integer | `100` | **Per board**, counted in *matching* rows. `0` = no cap |
| `maxJobsTotal` | integer | `0` | Cap for the whole run, applied after the dedupe |
| `includeDescription` | boolean | `false` | Add the full posting body. **No surcharge** |
| `descriptionFormat` | select | `text` | `text`, `markdown` or sanitized `html` |
| `deduplicate` | boolean | `true` | Collapse the same posting across boards into one charged row |
| `monitor` | boolean | `false` | Return only postings never returned before |
| `stateStoreName` | string | `remote-jobs-api-state` | The named key-value store the monitor remembers in |
| `firstRunBehavior` | select | `baseline-only` | What the first monitor run does |
| `stateRetentionDays` | integer | `45` | How long the monitor remembers a posting |
| `arbeitnowMaxPages` | integer | `2` | Arbeitnow pages to read (2 MB each, ~10 remote rows each) |
| `himalayasMaxPages` | integer | `10` | Himalayas pages to read (20 rows each) |
| `maxConcurrency` | integer | `4` | Boards in flight |
| `maxRunSecs` | integer | `240` | Wall-clock budget for the run |
| `requestTimeoutSecs` | integer | `20` | Per-request timeout |
| `proxyConfiguration` | object | Apify datacenter | Every board answered through it, and directly |

***

### Output reference

Four row types, all in one dataset with the same columns:

| `rowType` | What it is | Charged |
|---|---|---|
| `job` | One unique posting that passed every filter | **yes** |
| `board-summary` | One per board: what it read, what survived, what you paid for | no |
| `run-summary` | The same for the whole run, plus `duplicatesCollapsed` | no |
| `diagnostic` | One thing that could not be read, and why | no |

**Identity and links:** `jobId`, `board`, `boardJobId`, `url`, `applyUrl`,
`alsoOn`, `alsoOnBoards`.
**Employer:** `company`, `companyLogo`, `companyUrl`, `companyDomain`.
**Role:** `title`, `category`, `categories`, `tags`, `employmentType`,
`seniority`, `excerpt`, `descriptionText`, `descriptionHtml`.
**Where:** `locationRaw`, `locations`, `countries`, `remoteRegions`,
`timezones`, `isRemote`, `workplaceType`.
**Money:** `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod`,
`salaryRaw`.
**Time:** `postedAt`, `expiresAt`, `isNew`, `firstSeenAt`.

#### The board summary is your receipt

Every run writes one free row per board:

```json
{
  "rowType": "board-summary",
  "board": "arbeitnow",
  "pagesFetched": 2,
  "jobsFetched": 500,
  "jobsAfterFilters": 20,
  "jobsDeduped": 18,
  "jobsNew": 18,
  "jobsCharged": 18,
  "boardNotice": "This is a free public API for jobs, please do not abuse…"
}
```

Five hundred postings read, twenty matched, eighteen billed. `boardNotice`
carries that board's own terms, in its own words, so the attribution obligation
travels with the data.

#### Salary

Salary is reported **only where the board publishes it**, and never inferred
from description text. Three of the seven publish structured pay; Remotive
publishes free text, which is parsed into numbers where it can be and kept
verbatim in `salaryRaw` either way. RemoteOK publishes bare numbers with no
currency and no period — it quotes annual USD on its own board, so that is what
is reported, and this sentence is the disclosure that it is a board convention
rather than something the feed stated. `salaryMin` compares against the top of a
range and converts an hourly or monthly rate for the comparison only; the
columns always hold exactly what the board published, in the currency it
published.

***

### What you are never charged for

- Every job your filters excluded. The filters run **before** the charge, and an
  excluded job does not even count toward `maxJobs`.
- Every duplicate collapsed across boards. One posting on three boards is one
  row and one charge.
- Every `board-summary` and `run-summary` row.
- Every diagnostic: `blocked`, `rate-limited`, `not-found`, `no-results`,
  `timeout`, `unavailable`, `parse-error`, `invalid-input`, `locked`,
  `budget-reached`.
- Every job already in the monitor's state.
- **Descriptions.** All seven feeds embed the full posting in their list
  response, so there is no second request to bill for and there is no
  description, salary or "detail" surcharge — not now and not later.
- **A run that returns no job at all.** It finishes **FAILED**, says why, and
  bills nothing, start fee included.
- **A monitor's baseline run**, and a monitor on a day with nothing new. See
  below.

***

### Pricing

| Event | FREE | BRONZE | SILVER | GOLD |
|---|---|---|---|---|
| Run started | $0.001 | $0.001 | $0.001 | $0.001 |
| **Job returned** | $0.0005 | $0.0005 | $0.0004 | $0.0003 |

That is **$0.50 per 1,000 jobs**, descriptions included. There is one paid event
and there will never be a second.

| Run | Rows | Cost |
|---|---|---|
| The prefill — 3 boards, `keywords: ["developer","engineer"]`, `maxJobs: 25` | ~40 jobs | $0.001 + $0.020 = **$0.021** |
| All seven boards, no keyword filter, `maxJobs: 0` | ~600 unique jobs | $0.001 + $0.300 = **$0.301** |
| Daily monitor after the baseline, seven boards | ~40 new jobs | $0.001 + $0.020 = **$0.021** |
| Daily monitor, `keywords: ["rust"]`, on a quiet day | 0 jobs | **$0.00** — SUCCEEDED, free |

The run honours `ACTOR_MAX_TOTAL_CHARGE_USD`: when the budget runs out it stops
delivering rather than handing over rows it cannot bill for, finishes SUCCEEDED
and says so in the run status.

***

### Running it every day: monitor mode

Set `monitor: true` and schedule it. Every run after the first returns only
postings this Actor has never returned before.

**The first run is a baseline.** It reads every board, records what is out
there, returns **no job rows and charges for none of them**, and finishes
**SUCCEEDED** with a status message saying so. That is deliberate and it is the
one documented exception to "a run with no job rows fails": a schedule's first
day is not a failure, and charging for a list of everything that already existed
would be charging for the wrong thing. Set `firstRunBehavior: "emit-all"` if you
would rather have the backfill and pay for it.

A later run on a day when nothing new was posted also finishes **SUCCEEDED** and
costs **nothing at all** — not even the start fee. A scheduled job should not
look broken because hiring was quiet.

`isNew` and `firstSeenAt` are on every row in both modes, so a non-monitor run
is still useful for your own diffing.

#### Where the state lives

An Actor's **default** key-value store is created fresh every run, so it cannot
carry yesterday's state. This Actor keeps state in a **named** store —
`remote-jobs-api-state` by default — which works within its **limited
permissions**: such an Actor may create storage, write to it, and re-open
storages it created in previous runs. Nothing else in your account is touched,
and limited permissions are a requirement for x402 agentic payments.

**The one tradeoff:** a limited-permissions Actor cannot read a store *you*
created by hand. If `stateStoreName` points at one you made in Console, the
Actor may not be able to open it — and it then logs a warning, treats the run as
a first run, and returns **no** job rows, so a permissions problem never becomes
a wave of jobs re-announced as new and re-charged. Let the Actor create its own
store and this never comes up.

One record holds every job the monitor has seen: a dedupe key, a date and a
short board list, about 70 bytes each. Entries older than `stateRetentionDays`
are pruned on every write and the record is capped at 50,000 jobs, oldest first.
Two overlapping scheduled runs would each diff against the same state and
double-report every job, so the second one takes one look at the run lock,
writes a free `locked` diagnostic and stops without fetching anything.

***

### Proxy

The default is Apify's **datacenter** proxy, and it is the right default: every
one of the seven boards answered HTTP 200 through it, and HTTP 200 with no proxy
at all, in the capture probe. There is no residential leg anywhere in this
Actor. Switch `proxyConfiguration` to residential only if a board starts
refusing your runs; a board that refuses one exit IP is retried once from a new
one and then reported as a free `blocked` diagnostic rather than hammered.

Boards are read in parallel, never more than one request at a time against any
one of them, with a pause between successive pages.

***

### Use it from an AI agent, or from code

One JSON object in, one flat array out — the shape agent runtimes want. The
Actor runs with **limited permissions**, uses **pay-per-event** pricing and
never enters Standby, so it works over the Apify MCP server and with x402
agentic payments. The **Integrations** tab pushes results to Slack, a webhook,
Zapier, Make, Google Sheets, Snowflake or BigQuery.

```bash
curl -X POST "https://api.apify.com/v2/acts/insight.solutions~remote-jobs-api/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"boards":["himalayas","remoteok"],"keywords":["rust"],"salaryMin":120000,"maxJobs":50}'
```

```python
## pip install apify-client
from apify_client import ApifyClient

client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("insight.solutions/remote-jobs-api").call(run_input={
    "keywords": ["machine learning", "ml engineer"],
    "locations": ["Worldwide", "Europe"],
    "salaryOnly": True,
    "postedSince": "7d",
    "includeDescription": True,
    "descriptionFormat": "markdown",
    "maxJobs": 100,
})

for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    if row.get("ok") and row["rowType"] == "job":
        print(row["title"], row["company"], row["board"],
              row["salaryMin"], row["salaryMax"], row["applyUrl"], sep=" | ")
```

Set `monitor: true` when you want an alert rather than a backfill: the run then
returns only postings it has not returned before.

***

### FAQ

**Do I need an API key for any of these boards?**
No. All seven publish a public feed, and this Actor reads nothing else. No
login, no cookie that identifies anyone, no scraping behind a paywall.

**Why is Remotive only fifteen jobs?**
Because that is what Remotive's public API returns today — `job-count` and
`total-job-count` both say fifteen. It is not a page of a larger feed. Treat it
as a bonus board.

**Why does Arbeitnow return so little?**
Because it is a European job aggregator, not a remote board: roughly 4% of its
postings are flagged remote. Those 4% are real remote jobs the other six boards
do not have. The rest are read and dropped for free, and the `board-summary` row
shows you the ratio.

**How does the cross-board dedupe decide two postings are the same job?**
Company plus title, both normalised — legal suffixes dropped (`OpenBase LLC` and
`OpenBase` are one employer), bracketed qualifiers dropped (`(m/w/d)`,
`(Remote)`, `(IR-471)`), and a leading or trailing "remote" removed. It is
applied **across boards only.** Two postings on the *same* board that share a
company and a title are two requisitions — companies really do post "Senior
Software Engineer" twice for two teams — and collapsing those would lose a real
job.

**Why does `timezones` only return Himalayas jobs?**
Because Himalayas is the only one of the seven that publishes timezone
restrictions. A posting that says nothing about timezones is not "open to every
timezone", so it cannot satisfy a timezone filter.

**Why is `salaryMin` null on so many rows?**
Because most boards do not publish pay. Nothing here reads a number out of a
description and calls it a salary. Use `salaryOnly: true` if you only want rows
that have one.

**Can I get the full job description?**
Yes, `includeDescription: true`, in text, Markdown or sanitized HTML, at no
extra cost. Recruiter names, email addresses and personal profile links are
removed from it.

**What happens if a board changes its format?**
The feeds are public APIs, and their shape can change without notice. A board
that answers in a shape this Actor does not recognise produces a free
`parse-error` diagnostic row naming the URL, the other boards still deliver, and
the run only finishes successfully if it returned at least one real job.

***

### Limitations

- The feeds are what the boards publish. Two of them are small today —
  Remotive 15 jobs, We Work Remotely 88 — and that is everything their public
  APIs have, not a page of something larger.
- **RemoteOK has no paging.** Its API serves the newest ~100 and stops. A
  `?tag=` query returns a different hundred, which is the only way to go deeper.
- **We Work Remotely's all-jobs feed is the newest ~10 per category**, so it is
  not in date order across the feed. Set `categories` to read a category feed
  instead.
- **Arbeitnow** is a European ATS aggregator where roughly 4% of postings are
  remote, and its pages are ~2 MB each. That is why `arbeitnowMaxPages` defaults
  to 2.
- **Working Nomads** returns a flat 50 with no paging parameter that we have
  found.
- Salary is only present where the board publishes it, and is **never** inferred
  from description text.
- Seniority is derived from the job title, except on Himalayas and Jobicy, which
  publish their own band.
- This Actor deliberately does **no employer-email enrichment** — contact lines
  are stripped from `descriptionText` — and does **no scraping around any
  board's feed cap**. Where a board publishes only its newest N, that is what
  you get, and the table above says which.
- The upstream formats may change without notice. See the FAQ.

***

### Attribution — please honour it

Every board here publishes its feed on terms. They are short, they are
reasonable, and they are reproduced below in each board's own words. **Every row
carries the board it came from and that board's own job URL, so linking back is
a copy of one field.** You are responsible for honouring each board's terms in
whatever you build.

- **Remote OK** — *"Please link back (with follow, and without nofollow!) to the
  URL on Remote OK and mention Remote OK as a source, so we get traffic back
  from your site. If you do not we'll have to suspend API access."* Also: do not
  use the Remote OK logo — it is a registered trademark — but the name is fine.
- **Remotive** — *"Please link back to the URL found on Remotive AND mention
  Remotive as a source in order to Remotive to get traffic from your listing."*
  Do **not** resubmit Remotive jobs to third-party boards (their notice names
  Jooble, Neuvoo, Google Jobs and LinkedIn Jobs). Using their listings to
  collect signups or email addresses breaches their terms. Their jobs are
  delayed 24 hours, and they ask for at most about four requests a day — this
  Actor makes exactly one per run.
- **We Work Remotely** — credit We Work Remotely and link to the job's own URL.
  Their `robots.txt` permits the RSS paths this Actor reads.
- **Himalayas** — link back to the original job URL and name Himalayas as the
  source.
- **Jobicy** — *"Please ensure Jobicy is clearly credited with a direct link to
  the source, and all application buttons redirect to the original job URL
  provided in this feed."* Their fair-use rule is at most one request per hour;
  this Actor makes one per run.
- **Working Nomads** — credit Working Nomads and link to the job URL. Their
  `robots.txt` disallows nothing.
- **Arbeitnow** — *"This is a free public API for jobs, please do not abuse. I
  would appreciate linking back to the site. By using the API, you agree to the
  terms of service present on Arbeitnow.com"*

Each board's notice is also written into the dataset, on that board's
`board-summary` row, in the `boardNotice` column.

***

### Our other Actors

Every Insight Solutions Actor is pay-per-result with no browser, no login and no API key, and every one of them returns free diagnostic rows instead of billing for failures. Prices are per 1,000 results.

**Video, audio & social**

- [YouTube Transcript API](https://apify.com/insight.solutions/youtube-transcript-api) — captions as timed segments, text, SRT or VTT, with language fallback and translation.
- [YouTube Comments API](https://apify.com/insight.solutions/youtube-comments-api) — comments and replies with likes, pinned and hearted flags, newest or top sort.
- [YouTube Channel API](https://apify.com/insight.solutions/youtube-channel-api) — a channel's videos, Shorts and live streams, plus YouTube search.
- [Podcast Search, Episodes & Charts API](https://apify.com/insight.solutions/podcast-api) — Apple Podcasts search, charts and full episode feeds.
- [Bluesky Scraper](https://apify.com/insight.solutions/bluesky-scraper) — profiles, posts, followers and follows from the public AT Protocol API.
- [Telegram Channel Scraper](https://apify.com/insight.solutions/telegram-channel-scraper) — posts, views and channel stats from public Telegram channels.
- [Substack Scraper](https://apify.com/insight.solutions/substack-scraper) — posts with full free text, comments and publication profiles.
- [Hacker News API](https://apify.com/insight.solutions/hacker-news-api) — stories, comments, users, front page and a structured "Who is hiring?" parser from the official HN APIs.

**News, documents & the web**

- [Google News Search, Topics & Real Article URLs](https://apify.com/insight.solutions/google-news-api) — news search and topic feeds with the publisher's real URL decoded.
- [Website to Markdown — Content Extractor for LLMs & RAG](https://apify.com/insight.solutions/website-content-extractor) — any site as clean Markdown, text and heading-aware chunks.
- [Internet Archive API](https://apify.com/insight.solutions/internet-archive-api) — archive.org search, item metadata, files and reviews.
- [Wayback Machine Toolkit](https://apify.com/insight.solutions/wayback-toolkit) — archived URL inventories, snapshots and text diffs between dates.
- [Website Technology Detector](https://apify.com/insight.solutions/website-tech-detector) — the tech stack behind any site, with the evidence for each detection.
- [Domain Intelligence API](https://apify.com/insight.solutions/domain-intelligence-api) — DNS, RDAP registration, TLS certificate and HTTP facts in one row per domain.
- [SEO Page Audit](https://apify.com/insight.solutions/seo-page-audit) — sitemap crawl with on-page checks, structured data and broken-link reports.
- [Keyword Suggestions API](https://apify.com/insight.solutions/keyword-suggestions-api) — Google, YouTube, Bing, Amazon and eBay autocomplete with alphabet and question expansions.
- [Website Contact Extractor](https://apify.com/insight.solutions/website-contact-extractor) — emails, phone numbers and social profiles from any list of websites.
- [Web Search Results API](https://apify.com/insight.solutions/web-search-api) — Bing and DuckDuckGo organic results with snippets, no key, no browser.
- [Company Enrichment API](https://apify.com/insight.solutions/company-enrichment-api) — a domain in, a company profile out: firmographics, contacts, tech stack, DNS and hiring signal.

**Business, finance & jobs**

- [Congress & Insider Trades API](https://apify.com/insight.solutions/congress-insider-trades-api) — STOCK Act periodic transaction reports and SEC Form 4 insider trades in one schema.
- [Federal Contracts, Grants & Lobbying API](https://apify.com/insight.solutions/federal-contracts-grants-api) — SAM.gov opportunities, USAspending awards, Grants.gov notices and Senate lobbying filings in one schema.
- [SEC EDGAR API](https://apify.com/insight.solutions/sec-edgar-api) — filings, XBRL financials and full-text search by ticker or CIK.
- [Clinical Trials & FDA API](https://apify.com/insight.solutions/clinical-trials-fda-api) — ClinicalTrials.gov studies plus openFDA recalls, labels, approvals, 510(k)s and adverse-event reports.
- [Product & Vehicle Recalls API](https://apify.com/insight.solutions/product-recalls-api) — CPSC, NHTSA, FDA and USDA recalls, vehicle complaints and ratings, plus a VIN decoder.
- [Y Combinator Companies, Batches & Founders](https://apify.com/insight.solutions/yc-companies-directory) — the YC directory with founders and social links, filterable by batch, industry and hiring status.
- [Career Site Jobs API](https://apify.com/insight.solutions/ats-jobs-api) — jobs straight from Greenhouse, Lever, Ashby, Workable and 10+ other ATS career sites.
- [New Job Postings Monitor](https://apify.com/insight.solutions/job-postings-monitor) — new, closed and changed postings on the career sites you watch.
- [Shopify Products API](https://apify.com/insight.solutions/shopify-products-api) — any Shopify store's catalogue, variants, prices and stock signals.
- [Shopify Store Monitor](https://apify.com/insight.solutions/shopify-store-monitor) — price drops, sales, restocks, sell-outs and new products on any Shopify store, one row per change.

**Apps & games**

- [App Store & Google Play Reviews API](https://apify.com/insight.solutions/app-reviews-api) — reviews from both stores with ratings, versions and developer replies.
- [App Store Top Charts & App Search API](https://apify.com/insight.solutions/app-charts-api) — Apple top charts by country and genre, plus app search and details.
- [App Store Keyword Rank Tracker](https://apify.com/insight.solutions/app-store-keyword-rank-tracker) — where any app ranks for any keyword on the App Store and Google Play, with rank changes and ASO suggestions.
- [Steam Reviews API](https://apify.com/insight.solutions/steam-reviews-api) — Steam reviews with playtime, helpfulness and game details.
- [Steam Game Data API](https://apify.com/insight.solutions/steam-store-stats-api) — prices, tags, review scores, live player counts and top charts.

# Actor input Schema

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

Which remote job boards to read. All seven produce the same columns, so a run over all of them is one table. Note the two small ones: Remotive's public feed is about 15 jobs, and We Work Remotely's is the newest ~10 per category. Arbeitnow is a European aggregator where roughly 4% of postings are remote — it still contributes, and the rows you do not get are free.

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

Only jobs whose title, tags or category contain one of these (case-insensitive substring, any of them matches). Applied before you are charged, so jobs that do not match cost you nothing.

## `monitor` (type: `boolean`):

Return only postings this Actor has never returned before. Put it on a schedule and every run after the first is a short list of genuinely new jobs. The first run records a baseline and charges for no rows at all.

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

Stop each board after this many **matching** jobs. Jobs your filters excluded do not count toward it, so a narrow keyword keeps paging rather than stopping at the first page read. `0` means every job the filters allow.

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

Keep only postings flagged remote. Six of the seven boards are remote-only by construction, so this only bites on Arbeitnow — where it drops roughly 96% of what is read, for free.

## `deduplicate` (type: `boolean`):

Collapse the same posting found on more than one board into one row, with the others listed in `alsoOn`. You are charged once for it instead of three times. Two postings from the *same* board that share a company and a title are never collapsed — those are two requisitions.

## `keywordsMatchDescription` (type: `boolean`):

Extend `keywords` to the full posting text and the board's excerpt. Off by default: matching a body mention of "Python" would return jobs that are not Python jobs.

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

Drop jobs containing any of these. Searched over the title, tags, category *and* the description — a word you want to avoid is worth avoiding wherever it appears.

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

Only jobs in these categories, matched against each board's own category names and tags, e.g. `["Software Development"]`, `["Design"]`, `["Customer Support"]`. On We Work Remotely this also picks which category feed is fetched, which is the only way to see more than ten of anything there.

## `tags` (type: `array`):

Only jobs carrying one of these tags, e.g. `["python"]`, `["react"]`. The first entry is also sent to RemoteOK as its `?tag=` parameter, which returns a genuinely different set of jobs from the untagged feed.

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

Only jobs open to these places. Free text, matched against the board's own location line, the countries it names and its remote regions — `["USA"]`, `["Worldwide"]`, `["Europe"]`, `["Germany"]`. The first entry is also sent to Jobicy as its `geo` filter when it maps onto one.

## `timezones` (type: `array`):

Only jobs the employer will hire across one of these UTC offsets, e.g. `[-5, -6]` for US Eastern and Central. **Himalayas is the only board that publishes this**, so setting it restricts the run to Himalayas rows in practice.

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

Only these contract types. A board that publishes no employment-type field (Working Nomads) can never match this filter.

## `seniority` (type: `array`):

Only these bands. Himalayas and Jobicy publish their own; everywhere else it is derived from the job title, and a title that says nothing has no band and cannot match.

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

Only jobs at these employers (case-insensitive substring on the company name).

## `excludeCompanies` (type: `array`):

Drop jobs at these employers. Useful for filtering out staffing agencies that post the same role across every board.

## `salaryMin` (type: `integer`):

Only jobs whose published pay clears this, compared against the top of the range. Hourly and monthly rates are converted for the comparison only — the salary columns always hold exactly what the board published. A job with no published salary cannot clear a floor and is dropped when you set one.

## `salaryOnly` (type: `boolean`):

Drop every job the board published no pay information for. Three of the seven boards publish structured salary; Remotive publishes free text. The other three publish none, so this filter silences them.

## `postedSince` (type: `string`):

Only jobs posted since this point: an ISO date (`2026-09-01`) or a relative window (`48h`, `7d`, `4w`). A job with no posting date cannot be shown to be inside the window and is dropped.

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

Add the full posting body to every row. **There is no surcharge**: every one of the seven feeds embeds the description in its list response, so this costs no extra request and no extra charge. It is off by default only because it makes the dataset several times larger.

## `descriptionFormat` (type: `string`):

How to render the posting body. `text` and `markdown` fill `descriptionText`; `html` fills `descriptionHtml` with sanitized markup. Recruiter names, mailboxes and profile links are removed in every format.

## `maxJobsTotal` (type: `integer`):

Cap the whole run after the cross-board dedupe. `0` means no cap. Applied to the freshest rows first.

## `firstRunBehavior` (type: `string`):

`baseline-only` records what is out there and charges for nothing — the first run of a schedule costs the start fee and stops. `emit-all` returns and charges for everything it finds, like a normal bulk run, and then monitors from there.

## `stateRetentionDays` (type: `integer`):

How long the monitor remembers a posting. A job that falls out of the window and is still listed will be reported as new again. 45 days covers the life of almost every remote posting.

## `arbeitnowMaxPages` (type: `integer`):

*(Advanced)* How many 250-row Arbeitnow pages to read. Each page is about 2 MB and yields roughly ten remote jobs, which is why the default is low.

## `himalayasMaxPages` (type: `integer`):

*(Advanced)* How many 20-row Himalayas pages to read. Himalayas is the deep board — it holds 100,000+ postings — so this is the lever for a big run.

## `stateStoreName` (type: `string`):

The named key-value store holding what the monitor has already seen. One per watchlist: two schedules with different filters want two stores. Letters, digits and hyphens, 3–63 characters.

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

*(Advanced)* How many boards to read at once. Never more than one request at a time against any one board.

## `maxRunSecs` (type: `integer`):

*(Advanced)* Wall-clock budget for the whole run. A board not finished in time gets a free `timeout` diagnostic and the rows already read are kept.

## `requestTimeoutSecs` (type: `integer`):

*(Advanced)* How long one request to a board may take before it is given up on. The default of 20 seconds clears every board measured here; raise it only if a slow board keeps ending in a free `timeout` diagnostic.

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

Every board here answered through Apify's datacenter proxy and with no proxy at all, so the cheap default is the right one. Switch to residential only if a board starts refusing your runs.

## Actor input object example

```json
{
  "boards": [
    "remoteok",
    "remotive",
    "himalayas"
  ],
  "keywords": [
    "developer",
    "engineer"
  ],
  "monitor": false,
  "maxJobs": 25,
  "remoteOnly": true,
  "deduplicate": true,
  "keywordsMatchDescription": false,
  "excludeKeywords": [],
  "categories": [],
  "tags": [],
  "locations": [],
  "timezones": [],
  "employmentTypes": [],
  "seniority": [],
  "companies": [],
  "excludeCompanies": [],
  "salaryMin": 0,
  "salaryOnly": false,
  "postedSince": "",
  "includeDescription": false,
  "descriptionFormat": "text",
  "maxJobsTotal": 0,
  "firstRunBehavior": "baseline-only",
  "stateRetentionDays": 45,
  "arbeitnowMaxPages": 2,
  "himalayasMaxPages": 10,
  "stateStoreName": "remote-jobs-api-state",
  "maxConcurrency": 4,
  "maxRunSecs": 240,
  "requestTimeoutSecs": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

One row per unique job posting across RemoteOK, Remotive, We Work Remotely, Himalayas, Jobicy, Working Nomads and Arbeitnow, plus free board and run summary rows. Delivered as JSON items in the default dataset.

# 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 = {
    "boards": [
        "remoteok",
        "remotive",
        "himalayas"
    ],
    "keywords": [
        "developer",
        "engineer"
    ],
    "monitor": false,
    "maxJobs": 25,
    "remoteOnly": true,
    "deduplicate": true,
    "includeDescription": false,
    "maxRunSecs": 240,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("insight.solutions/remote-jobs-api").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 = {
    "boards": [
        "remoteok",
        "remotive",
        "himalayas",
    ],
    "keywords": [
        "developer",
        "engineer",
    ],
    "monitor": False,
    "maxJobs": 25,
    "remoteOnly": True,
    "deduplicate": True,
    "includeDescription": False,
    "maxRunSecs": 240,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("insight.solutions/remote-jobs-api").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 '{
  "boards": [
    "remoteok",
    "remotive",
    "himalayas"
  ],
  "keywords": [
    "developer",
    "engineer"
  ],
  "monitor": false,
  "maxJobs": 25,
  "remoteOnly": true,
  "deduplicate": true,
  "includeDescription": false,
  "maxRunSecs": 240,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call insight.solutions/remote-jobs-api --silent --output-dataset

```

## MCP server setup

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

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/g99Op192QLmA5AbNz/builds/ZCKbnHxLZAqbh8J8i/openapi.json
