# 🌍 Remote Jobs Scraper — Work From Home Jobs, Salary USD (`iettiu/remote-jobs-scraper`) Actor

Scrape remote jobs from six public boards — WeWorkRemotely, RemoteOK, Remotive, Jobicy, Himalayas, Arbeitnow — plus 582 company career pages on Greenhouse and Ashby that no other remote-jobs scraper reads. One schema, every salary in USD per year, equity flagged. No API keys.

- **URL**: https://apify.com/iettiu/remote-jobs-scraper.md
- **Developed by:** [Dương Thiệu Trần](https://apify.com/iettiu) (community)
- **Categories:** Jobs, Agents, MCP servers
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: 5.00 out of 5 stars

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

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

## 🌍 Remote Jobs Scraper API — 6 Job Boards + 582 Company Career Pages

Scrape remote jobs from six public boards — **WeWorkRemotely, RemoteOK, Remotive, Jobicy,
Himalayas and Arbeitnow** — **plus 582 company career pages** read straight from the hiring systems
those companies post into. One unified schema, every salary converted to **USD per year**.

Looking for a **WeWorkRemotely scraper**, a **RemoteOK API** or a **Remotive job feed**? This
returns all three, plus three more boards, plus the remote roles at Stripe, Datadog, Perplexity,
Cerebras, Anduril and 577 other companies — in one normalised dataset.

Every posting is a **work from home job**. The six boards are remote-only by construction, and
onsite roles from the company career pages are dropped before you ever see them.

No API keys. No browser. No proxies.

***

### Remote job boards and company career pages this scraper covers

| Board | What it is | Jobs available |
|---|---|---|
| **WeWorkRemotely** | The largest curated remote job board | ~715 |
| **RemoteOK** | Remote-first tech and startup roles | ~100 |
| **Remotive** | Hand-screened remote roles | ~18 |
| **Himalayas** | The deepest catalogue of the six | 24,000+ |
| **Jobicy** | Remote roles with structured salary data | ~100 |
| **Arbeitnow** | European and German-market remote roles | ~60 remote |
| **Greenhouse** | 237 company career pages, first-hand from the employer | ~14,900 |
| **Ashby** | 368 company career pages, with structured pay and equity | ~7,000 |

The last two are **applicant tracking systems** — the software a company posts its own jobs into,
which then generates its careers page. That makes those rows first-hand rather than syndicated,
and it is where the salary and equity data comes from. Pass `atsBoards` to narrow them to specific
companies: a recruiter tracking 40 targets wants those 40, not all 582.

### Why scrape 588 sources in one run

Job boards barely overlap. Measured across a live sample, **cross-board duplication is 0.5%** —
which means a single board shows you a small slice of the market and you have no way of knowing
what you're missing.

| | One board | This Actor |
|---|---|---|
| Remote jobs reachable | ~100 | **~4,000** |
| Schemas to handle | 1 | **1** (eight normalised into one) |
| Salary formats to parse | 1 | **1** (`USD/year`, converted from any currency and pay period) |
| Sources covered | 1 | **6 boards + 582 company career pages** |
| Equity offers flagged | no | **yes**, where the employer publishes it |

### Remote job salaries, normalised to USD per year

Every board publishes pay differently, and most of it is unusable as-is:

```
RemoteOK    salary_min: 0            ← zero, not null, when there is no salary
Himalayas   salaryPeriod: "annual"   ← plus a separate currency field
Remotive    salary: "OTE $25k - $35k"  ← free text, with prefixes
Jobicy      salaryMin + salaryCurrency + salaryPeriod
Arbeitnow   (no salary field at all — parsed from the description)
```

This Actor turns all of it into two comparable integers:

```json
"salary_min_usd_year": 120000,
"salary_max_usd_year": 160000,
"has_salary": true
```

Hourly, daily, weekly and monthly rates are annualised. Currencies are converted at daily rates.
**When a currency or figure cannot be resolved with confidence, the field is left empty rather
than guessed** — a wrong salary is worse than no salary.

About **37% of jobs publish pay**, so `hasSalaryOnly` gives you the subset that is actually usable
for compensation benchmarking.

### Remote job data fields you get

```json
{
  "id": "remotive:2086540",
  "source": "remotive",
  "title": "Senior Backend Engineer",
  "company": "Acme Inc",
  "company_logo": "https://…",
  "location_raw": "Worldwide",
  "location_type": "remote",
  "location_regions": ["worldwide"],
  "employment_type": "full_time",
  "seniority": "senior",
  "salary_raw": "OTE $25k - $35k",
  "salary_min": 25000,
  "salary_max": 35000,
  "salary_currency": "USD",
  "salary_period": "year",
  "salary_min_usd_year": 25000,
  "salary_max_usd_year": 35000,
  "has_salary": true,
  "tags": ["python", "backend"],
  "description_text": "…",
  "apply_url": "https://…",
  "posted_at": "2026-08-08T21:48:06Z",
  "scraped_at": "2026-08-13T02:14:00Z"
}
```

`seniority` and `employment_type` are inferred from the job title where the board doesn't state
them, so the filters work uniformly across all eight sources.

### Equity offers, not just base salary

Ashby lets an employer publish compensation as a field rather than burying it in prose, and that
field frequently names equity:

```
$257K – $335K • Offers Equity
$189K – $240K • Offers Equity • Offers Commission
€224K – €248K • Offers Equity
```

Rows sourced from Ashby carry an `offers equity` tag when the employer says so. It is read with a
regex from the employer's own field — never inferred by a language model — so a number here is a
number the company published, not a guess.

**Measured honestly:** across 70 Ashby boards and 1,214 jobs, 40% carried structured pay and 30%
named equity. Coverage varies enormously by employer — the median board sits far below the
average, because a handful of large pay-transparency-compliant companies carry the aggregate. On
Greenhouse, pay lives in the job description and is text-parsed instead, reaching 48-59% depending
on the boards sampled.

### Filter remote jobs by salary, seniority and location

| Input | What it does |
|---|---|
| `sources` | Pick which boards to query |
| `search` | Matches job title or description |
| `location` | `"Germany"`, `"United States"`, `"Europe"` — or `"worldwide"` for every job open to applicants anywhere |
| `excludeKeywords` | Drop jobs matching these words, e.g. `"sales, crypto"` |
| `worldwideOnly` | Only jobs with no country restriction |
| `employmentType` | Full time · Part time · Contract · Internship |
| `seniority` | Intern · Junior · Mid · Senior · Lead · Executive |
| `hasSalaryOnly` | Keep only jobs that publish pay |
| `minSalaryUsd` | Minimum salary, compared **after** currency and period conversion |
| `postedWithinDays` | Freshness cutoff, default 30 days |
| `maxItems` | Hard cap, so a run can't produce an unexpected bill |
| `maxJobsPerPlatform` | Cap each board's contribution, e.g. 10 from each |
| `debugMode` | Log every URL fetched and each filtering step |

### Daily remote jobs digest — get only the new jobs

Turn on **`onlyNew`** and schedule the Actor. The first run returns everything that matches your
filters; every run after that returns only the jobs that appeared since — usually a handful, not
the same sixty rows again.

Because you are billed per job returned, a daily digest costs a fraction of a full run. A first
run of 60 jobs followed by six days of ~5 new jobs each is 90 results for the week, not 420.

| Run | With `onlyNew` off | With `onlyNew` on |
|---|---|---|
| Monday | 60 jobs | 60 jobs |
| Tuesday | the same 60 | the 4 posted overnight |
| Wednesday | the same 60 | the 7 posted since |

History is kept on your own account for 60 days. If storage is ever unavailable the run returns
everything rather than silently returning nothing — an empty digest and a quiet day on the job
boards look identical, and only one of them is a bug.

#### Send it to Slack every morning

1. **Schedule** → new schedule, `0 8 * * *`, pointing at this Actor with `onlyNew: true`.
2. In n8n, Make or Zapier, add an **Apify → Actor run finished** trigger.
3. Map `title`, `company`, `salary_min_usd_year`, `apply_url` into a Slack message.

Anything with a `salary_min_usd_year` above your threshold is a one-line filter in the same flow —
the field is already normalised to USD per year, so no currency handling is needed downstream.

### Remote Jobs Scraper pricing

Tiered by your Apify plan — **bigger plans pay less per job**:

| Your Apify plan | Per job returned |
|---|---|
| Free | $0.006 |
| Bronze | $0.005 |
| Silver | $0.004 |
| **Gold / Platinum / Diamond** | **$0.003** |

Actor start: $0.00005 — effectively free, so scheduling this costs you nothing to try.

| Run | Jobs | Free plan | Gold+ |
|---|---|---|---|
| Default | 60 | **$0.36** | **$0.18** |
| Medium | 200 | $1.20 | $0.60 |
| Everything | 1,000 | $6.00 | $3.00 |

**You are charged for jobs returned, not jobs fetched.** A narrow search reads thousands of
postings across all eight sources and bills you only for the ones that match — and a search that
matches nothing costs you nothing.

### What people build with remote job data

- **Salary benchmarking** — pay from 588 sources, directly comparable in one currency and period
- **Job board aggregation** — feed your own site or newsletter
- **Recruitment market research** — what roles are open, where, at what level
- **Lead generation** — companies actively hiring are companies actively spending

### Remote jobs scraper FAQ

#### Which job boards does this remote jobs scraper cover?

Six public boards — WeWorkRemotely, RemoteOK, Remotive, Jobicy, Himalayas, Arbeitnow — plus 582
company career pages on Greenhouse and Ashby. All are read in a single run and returned in one
schema, so you never reconcile eight different field names yourself.

#### Do all remote jobs include salary data?

No — not every board publishes pay, and the ones that do don't publish it on every posting. Set
`hasSalaryOnly` to keep only postings with a resolvable salary. WeWorkRemotely has no salary field
at all, so those figures are parsed out of the posting text.

#### How are salaries in EUR or GBP handled?

Every figure is converted to **USD per year**, so a posting quoted in EUR per month and one quoted
in GBP per year sort against each other correctly. The original values stay in `salary_min`,
`salary_max`, `salary_currency` and `salary_period` if you'd rather do your own conversion.
Anything that can't be resolved with confidence is left empty rather than guessed.

#### Can I get only worldwide remote jobs?

Yes — set `worldwideOnly`. Note that boards are unreliable here: a posting can be filed under
"Anywhere in the World" while its own text says US-only. Where the two disagree, the more specific
field wins, so `is_worldwide` is stricter than the board's own label.

#### Is there a job postings API for remote jobs?

Yes. Run this Actor over the Apify API, schedule it, or wire it into n8n, Make or Zapier — the
output is the same normalised JSON either way.

#### How fresh are the postings?

`postedWithinDays` defaults to 30. Each row carries `posted_at`, `days_since_posted` and an
`is_stale` flag so you can apply your own cutoff downstream.

#### Can I use this to find work from home jobs?

That is all it returns. The six boards are remote-only, and onsite roles from the company career
pages are dropped before output, so the whole dataset is work
from home jobs by construction — you never filter office roles out. Use `worldwideOnly` if you
also need postings open to candidates in any country, rather than remote-but-US-only.

#### What are Greenhouse and Ashby doing in a remote jobs scraper?

They are applicant tracking systems — the software a company posts its own jobs into, which then
builds its careers page. Reading them directly means a posting reaches you first-hand rather than
after a board syndicates it, and it reaches you from companies that never post to a public remote
board at all. Onsite roles are filtered out before output, so the dataset stays remote-only.

#### Can I scrape only specific companies?

Yes — pass `atsBoards` with the company slugs you care about, e.g. `stripe`, `figma`, `openai`.
Leave it empty to use the built-in registry of 582 boards.

#### How much does it cost to scrape 1,000 remote jobs?

You're charged per job returned, not per source read — a single run reads every source and bills
only for the rows it delivers. See the pricing table above.

### Notes and limitations

- Job postings are **business data**, not personal data.
- All eight sources are public APIs or feeds used within their intended purpose. The Greenhouse
  and Ashby endpoints are the documented public job-board APIs; `robots.txt` permits them.
- One source failing degrades coverage; it never fails the run. The log names what was skipped.
- Every board is read until it has contributed its share or genuinely run out, and the log says
  which: `jobicy: 0 matching of 200 fetched (exhausted)` means that board really has no matches —
  not that the scraper stopped early.

# Actor input Schema

## `search` (type: `string`):

Keeps jobs containing ALL of these words, in any order, across title, company, tags and description. "senior python" matches "Python Senior Developer". Leave empty for all jobs.

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

Hard cap on how many jobs are returned, so a run cannot produce an unexpected bill. The default of 60 returns about 10 from each board — enough to judge coverage and quality for well under a dollar. Raise it once you know what you want: the unfiltered corpus is about 500 jobs, and roughly 1,350 when filters are set.

## `hasSalaryOnly` (type: `boolean`):

Roughly 20–30% of postings publish pay. Enable this to keep only those — the subset usable for salary benchmarking.

## `worldwideOnly` (type: `boolean`):

Keep only jobs open to applicants anywhere, with no country or region restriction.

## `location` (type: `string`):

Filter by where the job accepts applicants from, e.g. "United States", "Germany", "Europe". Typing "worldwide", "anywhere" or "global" returns every job open to applicants anywhere — the same result as the Worldwide only checkbox, because most unrestricted jobs publish no location at all rather than the word.

## `seniority` (type: `string`):

Filter by experience level, inferred from the job title where the board does not state it.

## `employmentType` (type: `string`):

Filter by contract type. Jobs whose board does not publish this field are kept — WeWorkRemotely and most of RemoteOK never state it, so excluding them would silently remove about 43% of all jobs. Check the employment\_type field on each result: null means the board did not say.

## `minSalaryUsd` (type: `integer`):

Keeps jobs whose maximum salary is at least this much, after converting every currency and pay period to USD per year. Jobs without a published salary are excluded when this is set.

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

Only jobs newer than this. Set to 0 to disable.

## `excludeKeywords` (type: `string`):

Drop jobs containing any of these words. Space or comma separated, e.g. "sales recruiter crypto".

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

Which sources to read. The first six are public remote-job boards. Greenhouse and Ashby are applicant tracking systems - the hiring software companies post into directly - covering 582 company career pages, which is roughly 95x the volume of the six boards and where the salary and equity data comes from. They also carry onsite roles, so pair them with the location filters if you only want remote.

## `atsBoards` (type: `array`):

Limit the ATS sources to these company board slugs, e.g. stripe, openai, figma. Leave empty to use the built-in registry of 582 boards. A recruiter tracking 40 target companies wants exactly those, not all 582.

## `maxJobsPerPlatform` (type: `integer`):

Cap how many jobs each board may contribute, so one large board cannot dominate the results. 0 means no per-board cap. Applied after filtering, so narrowing a search still searches every board in full.

## `onlyNew` (type: `boolean`):

Return only jobs this Actor has not returned to you on a previous run. Turn this on when you schedule the Actor: the first run gives you everything, and every run after that gives only what appeared since. You are billed per job returned, so a daily digest costs a fraction of a full run. History is kept per account for 60 days; if it is ever unavailable the run returns everything rather than silently returning nothing.

## `debugMode` (type: `boolean`):

Log every URL fetched and the row count at each filtering step. Useful when a search returns fewer jobs than expected.

## Actor input object example

```json
{
  "search": "python",
  "maxItems": 60,
  "hasSalaryOnly": false,
  "worldwideOnly": false,
  "location": "",
  "seniority": "any",
  "employmentType": "any",
  "minSalaryUsd": 0,
  "postedWithinDays": 30,
  "excludeKeywords": "",
  "sources": [
    "remoteok",
    "jobicy",
    "himalayas",
    "remotive",
    "arbeitnow",
    "weworkremotely",
    "greenhouse",
    "ashby"
  ],
  "atsBoards": [],
  "maxJobsPerPlatform": 0,
  "onlyNew": false,
  "debugMode": false
}
```

# Actor output Schema

## `jobs` (type: `string`):

One row per job posting. Unified schema across all six boards, with salary\_min\_usd\_year and salary\_max\_usd\_year comparable across currencies and pay periods.

## `jobsCsv` (type: `string`):

The same job rows as a CSV download, for spreadsheets and BI tools.

## `salariedJobs` (type: `string`):

Only the rows where the board published pay. Roughly 30% of postings — this is the subset usable for compensation benchmarking.

# 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 = {
    "search": "python",
    "maxItems": 60,
    "sources": [
        "remoteok",
        "jobicy",
        "himalayas",
        "remotive",
        "arbeitnow",
        "weworkremotely",
        "greenhouse",
        "ashby"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("iettiu/remote-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 = {
    "search": "python",
    "maxItems": 60,
    "sources": [
        "remoteok",
        "jobicy",
        "himalayas",
        "remotive",
        "arbeitnow",
        "weworkremotely",
        "greenhouse",
        "ashby",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("iettiu/remote-jobs-scraper").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 '{
  "search": "python",
  "maxItems": 60,
  "sources": [
    "remoteok",
    "jobicy",
    "himalayas",
    "remotive",
    "arbeitnow",
    "weworkremotely",
    "greenhouse",
    "ashby"
  ]
}' |
apify call iettiu/remote-jobs-scraper --silent --output-dataset

```

## MCP server setup

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

```

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/X7zcewkCUr93h8BVa/builds/XSFVEDM40gWlUFUX9/openapi.json
