# Remote Jobs Aggregator & Scraper: Remote OK, We Work Remotely (`jtpalms/remote-jobs-aggregator`) Actor

Remote job listings from Remote OK, Jobicy and We Work Remotely in one clean dataset. Filter by keyword, location, category, job type, salary and date, with duplicates across boards merged. Source link and attribution on every row. Pay only per job returned.

- **URL**: https://apify.com/jtpalms/remote-jobs-aggregator.md
- **Developed by:** [JT Palms](https://apify.com/jtpalms) (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 $1.20 / 1,000 job listings

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 Aggregator: Remote OK, Jobicy and We Work Remotely in one feed

Get the latest remote job listings from three job boards in one clean dataset. The actor reads each board's official public API or RSS feed, maps every job to the same fields, filters by keyword, location, category, job type, salary and date, and merges the same job when it appears on more than one board. Every row keeps its source name, a link to the original listing and the board's attribution line.

**USD 1.50 per 1,000 jobs.** You pay only for the job rows in your results. Jobs removed by your filters or merged as duplicates are free.

### What people use it for

- **Remote job boards and newsletters.** Fill a niche job board or a weekly jobs email with fresh, filtered listings, each one linking back to the original post.
- **Job alerts.** Schedule a daily run with your keywords and locations and send new jobs to Slack, email or Google Sheets through Make, Zapier or n8n.
- **Recruiting market research.** See who is hiring remotely, for which roles, open to which countries, and how that changes week to week.
- **Salary benchmarking.** About half the listings state a salary. Each one comes as `salaryMin`, `salaryMax`, `salaryCurrency` and `salaryPeriod`, so you can compare roles without reading every post.

### Sample output

One row per job. This is a real row from a test run, with the description shortened. The same job was also on We Work Remotely, so it was merged and that listing is in `alsoListedOn`.

```json
{
  "id": "ae008cdac8f188b0",
  "source": "Jobicy",
  "sourceId": "151810",
  "title": "Financial Analyst",
  "company": "Salesloft",
  "companyUrl": null,
  "companyLogo": "https://jobicy.com/data/server-nyc0409/galaxy/mercury/2022/03/8f0ff4d3ba38b945acfa9bbe11e39be1.jpeg",
  "url": "https://jobicy.com/jobs/151810-financial-analyst-2",
  "sourceUrl": "https://jobicy.com/jobs/151810-financial-analyst-2",
  "attribution": "Source: Jobicy (https://jobicy.com). Keep Jobicy as the original source and link to the Jobicy job URL (sourceUrl).",
  "location": "USA",
  "remoteRegion": "americas",
  "remoteCountries": ["United States"],
  "employmentType": "full_time",
  "seniority": null,
  "category": "Finance & Accounting",
  "tags": [],
  "salaryMin": 67000,
  "salaryMax": 77500,
  "salaryCurrency": "USD",
  "salaryPeriod": "year",
  "salarySource": "listing",
  "postedAt": "2026-09-27T07:20:31.000Z",
  "descriptionText": "Job Title: Financial Analyst\n\nLocation: United State, REMOTE\n\nClari + Salesloft are building the next era of enterprise revenue...",
  "duplicateOf": null,
  "alsoListedOn": [
    { "source": "We Work Remotely", "sourceUrl": "https://weworkremotely.com/remote-jobs/salesloft-financial-analyst" }
  ],
  "scrapedAt": "2026-09-28T18:20:11.966Z"
}
```

A We Work Remotely row from the same run, where the salary was read from the description:

```json
{
  "source": "We Work Remotely",
  "title": "CPS Solution Architect",
  "company": "Collibra",
  "url": "https://weworkremotely.com/remote-jobs/collibra-cps-solution-architect-1",
  "location": "United States of America",
  "remoteRegion": "americas",
  "remoteCountries": ["United States"],
  "employmentType": "full_time",
  "category": "DevOps and Sysadmin",
  "salaryMin": 96000,
  "salaryMax": 120000,
  "salaryCurrency": "USD",
  "salaryPeriod": "year",
  "salarySource": "description",
  "postedAt": "2026-09-28T07:31:13.000Z"
}
```

Export as JSON, CSV or Excel, or read the results through the Apify API.

### Sources

| Job board | What one run reads | Attribution the board asks for |
|---|---|---|
| [Remote OK](https://remoteok.com) | Public API, the latest ~100 jobs | Link back to the Remote OK listing and mention Remote OK as the source. Do not use the Remote OK logo. |
| [Jobicy](https://jobicy.com) | Public API, the latest 200 jobs (about 2 days) | Keep Jobicy as the original source and link to the Jobicy job URL. |
| [We Work Remotely](https://weworkremotely.com) | Public RSS feeds (all jobs plus each category), ~200 jobs | Attribute the links back to We Work Remotely. |

A run with no filters and no date limit returns about 500 unique jobs.

**Attribution.** Every row has `source`, `sourceUrl` (the listing on that board) and `attribution` (the board's credit line). If you publish these jobs on a site, board, newsletter or app, show the source name and link to `sourceUrl`. That is the condition each board sets for reusing its listings. Do not present the jobs as your own postings or remove the credit.

**Boards not included.** Remotive, Himalayas and Arbeitnow also publish job APIs, but their terms rule out this kind of use: Remotive and Himalayas ask that their jobs are not submitted to third-party websites (Remotive sells a paid API for commercial use), and Arbeitnow's terms allow personal, non-commercial use only. LinkedIn, Indeed and Glassdoor have no public feed for this and are not scraped.

### How to use it

1. Choose the **Job boards** (all three by default).
2. Add filters. All are optional:
   - **Keywords**: any of these words in the title, tags, category or description (whole words, so `go` does not match "good").
   - **Exclude keywords**: drop jobs with these words in the title, company, category or tags.
   - **Categories**: text to look for in the category or tags, like `design` or `customer support`.
   - **Locations**: text to look for in the location, like `Canada`, `USA`, `Europe`, `LATAM` or `Worldwide`. `USA`, `US` and `United States` all match each other. Turn on **Also keep worldwide jobs** to add jobs open to anyone.
   - **Posted within (days)**: 30 by default, 0 for no limit.
   - **Employment types**, **Minimum salary**, **Only jobs with a salary**.
3. Click **Start**, then download the results or connect them to your tools.

Example input:

```json
{
  "keywords": ["python", "django"],
  "excludeKeywords": ["senior", "lead"],
  "locations": ["Canada"],
  "matchWorldwide": true,
  "postedWithinDays": 7,
  "employmentTypes": ["full_time", "contract"],
  "maxResults": 200
}
```

#### Daily job alerts

1. Save your filters as a task and add a schedule that runs once a day.
2. Set **Posted within (days)** to `2` so nothing is missed between runs.
3. Keep **Merge duplicates** on. It merges the same job across boards within one run.
4. To send only jobs you have not seen before, keep the `id` of every job you process. The `id` is stable across runs (a hash of the board and the board's own job ID), so you can skip ids you already have in your sheet or database.

Each board is read at most once an hour per Apify account. Runs closer together than that reuse the copy from the last hour (see Limits), so a schedule more frequent than hourly adds cost without adding jobs.

### Output fields

| Field | Description |
|---|---|
| `id` | Stable ID: hash of board + the board's job ID. |
| `source`, `sourceId` | Board name and the board's own job ID. |
| `title`, `company` | Job title and employer. |
| `companyUrl`, `companyLogo` | Employer website and logo, when the board gives them. |
| `url` | The job listing to send applicants to (the board's listing page). |
| `sourceUrl` | The listing on the source board. Link here when you publish the job. |
| `attribution` | The credit line the board asks for. |
| `location` | Location text as the board states it. |
| `remoteRegion` | `worldwide`, `americas`, `europe`, `emea`, `apac`, `multiple` or empty. Best effort from the location. |
| `remoteCountries` | Countries named in the location, as English names. |
| `employmentType` | `full_time`, `part_time`, `contract`, `internship`, `other` or empty. |
| `seniority` | `intern`, `junior`, `mid`, `senior`, `lead`, `director`, `executive` or empty. From the title, or the board's level field. |
| `category`, `tags` | The board's category and tags or skills. |
| `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod` | Pay, when stated. Period is `year`, `month`, `week`, `day` or `hour`. |
| `salarySource` | `listing` (the board's salary field) or `description` (read from the job text). |
| `postedAt` | Posting date, ISO 8601. |
| `descriptionText` | Description as plain text, up to 3,000 characters. Turn off with **Include description**. |
| `duplicateOf` | With **Merge duplicates** off: the `id` of the main copy of this job. |
| `alsoListedOn` | Other boards where the same job appears, with their links. |
| `scrapedAt` | When the data was fetched from the board. |

A run summary (jobs per board, how many each filter removed, duplicates merged, jobs billed) is saved as `SUMMARY` in the run's key-value store.

### Pricing

| What | Price |
|---|---|
| Job row in your results | USD 0.0015 (USD 1.50 per 1,000) |
| Jobs removed by filters, merged duplicates | Free |

A run that returns all ~500 jobs costs about USD 0.75. A daily alert with a few keywords usually returns 10 to 50 jobs, so 2 to 8 cents a day. Set a maximum cost per run in the run options and the actor stops cleanly when it reaches it. Jobs saved before that are kept.

### Limits

- **The actor identifies itself.** Every request carries the user agent `Mozilla/5.0 (compatible; remote-jobs-aggregator/1.0; +https://apify.com/JTPalms/remote-jobs-aggregator)`, with plain request headers. It never pretends to be a browser. Some sites block bots; a blocked request is reported as an error, not hidden.
- **Coverage.** The actor returns what the three boards publish in their feeds right now: about 500 unique jobs. It is not a historical archive, and Jobicy's feed only covers its latest 200 jobs.
- **Hourly refresh.** Each board is fetched at most once per 60 minutes per Apify account. Jobicy asks integrations not to poll more often than hourly, and the feeds change slowly anyway. The actor keeps the last copy in a key-value store named `remote-jobs-aggregator-cache` in your account and reuses it for runs within the hour; `scrapedAt` shows when each job was fetched. If a board is down, a copy up to 24 hours old is used, and the run log says so.
- **Salary.** Remote OK and Jobicy have salary fields. For other jobs the actor reads the salary from the description only when it is clearly stated (a currency sign or code, a pay word or a period like "per hour", and a realistic amount). Those rows have `salarySource: "description"`. A bare `$` is read as USD unless the job names a single country that uses its own dollar (Canada, Australia, New Zealand, Singapore). **Minimum salary** compares amounts in the job's own currency without conversion.
- **Location and region** are best effort from the board's location text. Remote OK often leaves the location empty, so many Remote OK jobs have no region.
- **Employment type.** Remote OK does not state it. The actor marks part-time, contract and internship when the title or tags say so, and leaves the rest empty. Filtering by employment type drops jobs with no type.
- **Duplicates** are matched on company plus title, posted within 14 days of each other. The same job with a differently worded title on two boards stays as two rows.
- **Descriptions** are plain text, cut at 3,000 characters. The full post is at `sourceUrl`.

### Related actors

- [Remote OK Jobs Scraper (RemoteOK): Remote Job Listings](https://apify.com/JTPalms/remoteok-jobs): The latest Remote OK job listings from its official public API, normalized with salary, tags, location and region.
- [We Work Remotely Jobs Scraper (WeWorkRemotely): Remote Jobs](https://apify.com/JTPalms/weworkremotely-jobs): All current We Work Remotely job listings from its official RSS feeds, normalized with salary, region, category and skills.
- [Career Site Jobs: ATS Job Postings API for Greenhouse, Lever](https://apify.com/JTPalms/ats-jobs-index): Job postings from 17,000+ company career sites, read from the official job-board APIs of Greenhouse, Ashby, Lever, Workable, Teamtailor...

### FAQ

**Is it legal?** The actor reads only the official public APIs and RSS feeds that these boards publish so others can reuse their listings, it follows each board's stated request limits, and it keeps each board's attribution on every row. It does not log in, get around any protection or read pages the boards do not offer for reuse. It collects job listings only, with no candidate profiles. Descriptions are returned as each board publishes them, so a posting can include a recruiter's contact details if the employer put them there. If you republish the jobs, keep the source name and the link to `sourceUrl`, as each board asks. Boards whose terms do not allow this use are left out.

**Why does a second run within the hour return the same jobs?** The boards are read at most once an hour, as described under Limits. New jobs appear after the hour is up.

**Does `url` go to the employer's own application page?** It goes to the listing on the job board, which has the apply button. The boards ask that their listing is what you link to.

**Can I get more than ~500 jobs?** Not from these feeds. They only publish the latest jobs. Scheduled daily runs, stored by `id`, build up a larger history over time.

**Can you add another job board?** Open an issue with the board's name. It will be added only if the board publishes a feed or API whose terms allow reuse.

**Is this an official Remote OK, Jobicy or We Work Remotely product?** No. It is an independent tool that reads their public APIs and RSS feeds. It is not affiliated with or endorsed by Remote OK, Jobicy or We Work Remotely.

# Actor input Schema

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

Which job boards to pull from. All three are used by default.

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

Keep jobs that mention any of these words in the title, tags, category or description. Whole words, not case sensitive. Examples: python, react, customer support. Leave empty for all jobs.

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

Drop jobs with any of these words in the title, company, category or tags (the description is not checked). Examples: senior, crypto, sales.

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

Keep jobs whose category or tags contain any of these texts. Examples: design, marketing, devops, customer support.

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

Keep jobs whose location contains any of these texts. Country names and codes work (USA, US, United States all match), as do regions like Europe, LATAM, APAC and Worldwide.

## `matchWorldwide` (type: `boolean`):

When you filter by location, also keep jobs open to candidates anywhere in the world.

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

Only jobs posted in the last N days. Use 0 for no limit.

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

Keep only these types. Leave empty for all. Jobs whose source does not state a type are dropped when this is set.

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

Drop jobs whose stated salary is below this, converted to a yearly figure in the job's own currency (hourly x 2,080, monthly x 12). Jobs that do not state a salary are kept unless you also turn on "Only jobs with a salary".

## `requireSalary` (type: `boolean`):

Keep only jobs that state a salary.

## `maxResults` (type: `integer`):

Stop after this many jobs. Newest jobs come first.

## `dedupe` (type: `boolean`):

The same job (same company and title, posted within 14 days) listed on several boards is returned once: the most complete copy, with the other boards in alsoListedOn. Turn off to get every copy, with duplicateOf pointing at the main one.

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

Add the job description as plain text (up to 3,000 characters).

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

Optional. Only needed if a job board blocks requests from data centers.

## Actor input object example

```json
{
  "sources": [
    "remoteok",
    "jobicy",
    "weworkremotely"
  ],
  "keywords": [
    "python"
  ],
  "matchWorldwide": false,
  "postedWithinDays": 30,
  "requireSalary": false,
  "maxResults": 1000,
  "dedupe": true,
  "includeDescription": true
}
```

# Actor output Schema

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

All output rows in the default dataset (JSON, CSV, Excel via the format parameter).

## `summary` (type: `string`):

Counts and per-input status for this run.

# 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 = {
    "keywords": [
        "python"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("jtpalms/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 = { "keywords": ["python"] }

# Run the Actor and wait for it to finish
run = client.actor("jtpalms/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 '{
  "keywords": [
    "python"
  ]
}' |
apify call jtpalms/remote-jobs-aggregator --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,jtpalms/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/5NZdluWaJOyldJaMO/builds/CKiDBglYU3Bwgmhb8/openapi.json
