# Remote Jobs API: RemoteOK, We Work Remotely, Remotive (`actose/remote-jobs-feeds-aggregator`) Actor

Remote jobs from RemoteOK, We Work Remotely, Remotive and Jobicy in one clean JSON: title, company, salary min/max, location, job type, category, date, apply URL. Cross-board dedup, new-jobs-only alerts. No API key, no login. MCP-ready for AI agents. Pay per result. $0.95 per 1,000 jobs.

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

## Pricing

from $0.81 / 1,000 remote job delivereds

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 API: RemoteOK, We Work Remotely, Remotive & Jobicy in one feed

**$0.95 per 1,000 jobs. Pay only for jobs delivered. Errors, blocked boards and empty results are free. No API key, no login, no monthly rental.**

This Actor merges the public remote job feeds of **Remote OK**, **We Work Remotely**, **Remotive** and **Jobicy** into **one clean, deduplicated JSON schema**. You can filter the merged list by category, keywords, location, minimum salary, contract type and posting date. Turn on **"Only new jobs"** to get daily alerts that contain only jobs you haven't received yet.

It reads only official public feeds (JSON APIs and RSS), with no browser and no HTML scraping. That makes it fast (usually under 30 seconds) and much more reliable than page scrapers that break whenever a job board changes its design.

### What you get for each job

| Field | Example |
|---|---|
| `title`, `company`, `companyLogo` | "Senior Python Engineer", "Acme" |
| `url`, `applyUrl` | Original job page on the board |
| `location`, `isWorldwide` | "Europe, UK", `false` |
| `jobType`, `seniority` | "full-time", "Senior" |
| `categories` (unified across boards), `sourceCategory`, `tags` | `["developer"]`, "Back-End Programming" |
| `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod` | 120000, 150000, "USD", "year" |
| `salaryMinYearly`, `salaryMaxYearly` | Hourly or monthly pay converted to a yearly figure |
| `postedAt` | ISO 8601 UTC date |
| `description` | Plain text, **Markdown (LLM/RAG-ready)**, HTML, or none |
| `source`, `sourceName`, `alsoListedOn`, `sourceCount` | Where the job was found, plus duplicate listings on other boards |
| `dedupKey`, `id`, `attribution`, `scrapedAt` | Stable keys for your database |

### How much does it cost?

Pricing is pay-per-event: **$0.00095 per job delivered** ($0.95 per 1,000 jobs).

- 50 Python developer jobs = **$0.05**
- 1,000 jobs = **$0.95**
- A daily alert that returns about 20 new jobs costs about **$0.02 a day**, or roughly $0.60 a month.
- A run that finds no job, or a board that is down, costs **$0** in results.

Set a **maximum charge per run** in Apify. The Actor stops cleanly when it reaches that limit and never delivers unpaid "preview" items.

### Input example

```json
{
  "sources": ["remoteok", "remotive", "weworkremotely", "jobicy"],
  "categories": ["developer"],
  "keywords": ["python"],
  "excludeKeywords": ["intern"],
  "locations": ["Europe"],
  "includeWorldwide": true,
  "minSalary": 0,
  "maxAgeDays": 30,
  "maxResults": 50,
  "onlyNew": false,
  "descriptionFormat": "markdown"
}
```

| Input | What it does |
|---|---|
| `sources` | Which boards to merge (all four by default) |
| `categories` | One unified list: `developer`, `devops`, `data`, `design`, `product`, `marketing`, `sales`, `customer-support`, `finance`, `hr`, `writing`, `qa`, `other` |
| `keywords` / `excludeKeywords` | Whole-word match, case- and accent-insensitive. A job is kept if it matches **any** keyword |
| `searchInDescription` | Also search keywords in the job description (default: on) |
| `locations` / `includeWorldwide` | For example `["USA", "Canada"]`. "Anywhere" jobs are kept unless you switch that off |
| `minSalary` / `onlyWithSalary` | Yearly salary filter (hourly and monthly pay are converted) |
| `jobTypes` | `full-time`, `part-time`, `contract`, `freelance`, `internship`, `temporary` |
| `maxAgeDays` | Keep jobs posted in the last N days |
| `onlyNew` / `stateKey` | Return only jobs not delivered by previous runs with the same filters (or the same alert name) |
| `deduplicate` | Merge the same job (same company and title) posted on several boards (default: on) |
| `descriptionFormat` / `maxDescriptionLength` | `text`, `markdown`, `html` or `none`, with optional truncation |

### Output example

```json
{
  "id": "weworkremotely-acme-senior-python-engineer",
  "source": "weworkremotely",
  "sourceName": "We Work Remotely",
  "sourceJobId": "acme-senior-python-engineer",
  "title": "Senior Python Engineer",
  "company": "Acme",
  "companyLogo": "https://...",
  "url": "https://weworkremotely.com/remote-jobs/acme-senior-python-engineer",
  "applyUrl": "https://weworkremotely.com/remote-jobs/acme-senior-python-engineer",
  "location": "Anywhere in the World",
  "isWorldwide": true,
  "jobType": "full-time",
  "seniority": null,
  "categories": ["developer"],
  "sourceCategory": "Back-End Programming",
  "tags": [],
  "salaryMin": 120000,
  "salaryMax": 150000,
  "salaryCurrency": "USD",
  "salaryPeriod": "year",
  "salaryMinYearly": 120000,
  "salaryMaxYearly": 150000,
  "salaryText": "$120,000 - $150,000",
  "postedAt": "2026-10-24T07:31:05Z",
  "description": "## About the role\n\nWe are looking for ...",
  "descriptionFormat": "markdown",
  "dedupKey": "acme|senior python engineer",
  "sourceCount": 2,
  "alsoListedOn": [{"source": "remoteok", "sourceName": "Remote OK", "url": "https://remoteok.com/remote-jobs/..."}],
  "attribution": "Job via We Work Remotely (https://weworkremotely.com)",
  "scrapedAt": "2026-10-26T08:00:00Z"
}
```

You can download the dataset as JSON, CSV, Excel, XML or HTML. The **RUN_SUMMARY** record in the key-value store shows how many jobs were downloaded, matched, deduplicated and delivered for each board.

### Use cases

#### 1. Daily remote job alert for a recruiting agency (Slack or email)

A tech recruiter who places remote Python and Django developers schedules the Actor every morning at 7:00. Each run returns only the jobs published since the last run, usually 10 to 40 per day, for about $0.01 to $0.04.

Input:

```json
{"categories": ["developer"], "keywords": ["python", "django", "fastapi"], "excludeKeywords": ["intern"], "maxAgeDays": 3, "onlyNew": true, "stateKey": "python-daily", "maxResults": 200, "descriptionFormat": "none"}
```

Output (excerpt):

```json
[{"title": "Senior Backend Engineer (Python)", "company": "Acme", "location": "Europe", "salaryMin": 90000, "salaryMax": 120000, "salaryCurrency": "EUR", "url": "https://jobicy.com/jobs/..."}]
```

Next step: a Make or n8n scenario posts each new job to the `#remote-python` Slack channel. Each company becomes a prospect for the recruiter's outreach.

#### 2. Salary benchmark for an HR / compensation analyst

A compensation analyst wants published pay ranges for remote DevOps roles. A weekly run collects only jobs with a salary of $100k or more.

Input:

```json
{"categories": ["devops"], "onlyWithSalary": true, "minSalary": 100000, "maxAgeDays": 30, "maxResults": 500, "descriptionFormat": "none"}
```

Output (excerpt):

```json
[{"title": "Site Reliability Engineer", "company": "Globex", "salaryMinYearly": 140000, "salaryMaxYearly": 175000, "salaryCurrency": "USD", "location": "USA", "sourceName": "Jobicy"}]
```

Next step: the dataset goes into Google Sheets, and a pivot table shows median ranges by seniority and location. 500 jobs cost $0.48.

#### 3. Remote job board or newsletter for a niche audience

The editor of a "remote jobs for designers in Europe" newsletter needs fresh design jobs every week, without the same job appearing three times.

Input:

```json
{"categories": ["design"], "locations": ["Europe", "EMEA", "UK"], "includeWorldwide": true, "maxAgeDays": 7, "onlyNew": true, "stateKey": "design-eu-weekly", "descriptionFormat": "markdown", "maxDescriptionLength": 600}
```

Output (excerpt):

```json
[{"title": "Product Designer", "company": "Initech", "location": "Anywhere in the World", "sourceCount": 2, "alsoListedOn": [{"sourceName": "Remote OK"}], "description": "## About us\n..."}]
```

Next step: an LLM writes a two-line pitch for each job from the Markdown description, and the newsletter tool sends the issue. Each job links to its original board (`url` and `attribution`).

#### 4. Job-search AI agent

A career-coach assistant (Claude, ChatGPT or Cursor through MCP) calls the Actor with the candidate's skills and preferences and gets back structured jobs to rank. Twenty jobs cost $0.02.

### Call it from code

**Python**

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("<USERNAME>/remote-jobs-feeds-aggregator").call(run_input={
    "categories": ["developer"], "keywords": ["python"], "maxResults": 50,
})
for job in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(job["postedAt"], job["title"], job["company"], job["url"])
```

**JavaScript**

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('<USERNAME>/remote-jobs-feeds-aggregator').call({
    categories: ['developer'], keywords: ['python'], maxResults: 50,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map((j) => `${j.title} @ ${j.company}`));
```

### Integrations (Make, n8n, Zapier, Google Sheets)

- **Schedules**: in Apify Console go to Schedules, choose a daily cron (`0 7 * * *`) and use an input with `"onlyNew": true`.
- **n8n**: Schedule Trigger, then the Apify node ("Run Actor and get dataset items"), then Slack, Gmail or Google Sheets.
- **Make / Zapier**: use the Apify app trigger "Actor run finished", then "Get dataset items", then add a row in Google Sheets or send an email.
- **Google Sheets**: add the dataset export URL with `format=csv` to `=IMPORTDATA(...)`, or use the Apify integration.
- **Webhooks and API**: any HTTP tool can start a run and read `.../datasets/{id}/items?format=json`.

### Use with AI agents (MCP)

The Actor works with the Apify MCP server (`https://mcp.apify.com`), so Claude, Cursor, ChatGPT and other MCP clients can call it as a tool. For example, you can ask: *"Find remote Python jobs posted this week that pay over $100k and are open to Europe."* The input fields are plain, documented JSON, and `descriptionFormat: "markdown"` returns compact descriptions an LLM can read directly.

### FAQ

**Why not query each board myself?** Each board has its own format, its own categories and its own salary format, and many jobs appear on several boards. This Actor gives you one schema, unified categories, parsed salaries and cross-board deduplication.

**How does deduplication work?** Jobs with the same normalized company and title (ignoring "Remote", "(m/f/d)" and "Inc/LLC") are merged. The newest listing is kept, the other links go into `alsoListedOn`, and missing salary or location data is taken from the duplicates.

**How does "Only new jobs" work?** The Actor remembers the jobs it already delivered in a named key-value store in your account. That memory is kept per filter set, or per `stateKey`, for 90 days. The first run returns everything that matches. Later runs return only new jobs.

**Is the salary in USD?** Salaries stay in the currency the job gives (`salaryCurrency`). `minSalary` compares yearly amounts without currency conversion.

**Do you use proxies or a browser?** No. The Actor makes a few plain HTTP calls to public feeds. If a board refuses a request, it retries once through a datacenter proxy. Residential proxies are never used.

**Is it legal?** The Actor only reads feeds that the boards publish for public reuse. Every job includes its original `url` and an `attribution` line. Keep both when you display jobs, as the boards ask.

### Limits (honest)

- **Remotive's free public API only serves its latest jobs** (about 10, published with a 24-hour delay) and asks for at most about 4 calls a day. The Actor caches Remotive's response for 6 hours, so most jobs come from Remote OK, We Work Remotely and Jobicy.
- The Actor covers the jobs **currently listed** in each feed: a few hundred per board. That means recent jobs, not a full history.
- We Work Remotely's feed has no salary field. Salaries are taken from the description when a clear range is written (for example "$120,000 - $150,000").
- Remote OK has no category field, so categories come from the job title. Remote OK's tags are very broad, so use keywords for precision.
- Deduplication is by company and title. The same job with a very different title on two boards may still appear twice.

# Actor input Schema

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

Which public feeds to merge. Leave all selected for the widest coverage.

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

Keep only jobs in these categories (one unified list for all boards). Leave empty for all categories.

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

Keep jobs that contain at least one of these words in the title, company, tags (and description if enabled below). Whole-word, case- and accent-insensitive. Example: python, react, kubernetes.

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

Drop jobs whose title, company or tags contain any of these words. Example: senior, intern, crypto.

## `searchInDescription` (type: `boolean`):

On: keywords are also searched in the full job description (more results). Off: title, company and tags only (more precise).

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

Keep jobs whose location contains one of these words (e.g. Europe, USA, Canada, LATAM, EMEA, Germany). Leave empty for any location.

## `includeWorldwide` (type: `boolean`):

When a location filter is set, still keep jobs open worldwide.

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

Keep only jobs with a published salary whose yearly maximum is at least this amount (in the job's own currency; hourly and monthly pay are converted to yearly). 0 = no salary filter.

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

Drop jobs without salary information.

## `jobTypes` (type: `array`):

Keep only these contract types. Jobs whose board does not publish a type are kept. Leave empty for all.

## `maxAgeDays` (type: `integer`):

Drop jobs older than this. 0 = no date filter.

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

Maximum jobs returned (and charged) in this run, newest first.

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

The same job posted on several boards (same company and title) is returned once, with the other links in 'alsoListedOn'.

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

For scheduled runs: return only jobs not delivered by a previous run with the same filters (or the same state key). Perfect for daily alerts to email, Slack or Google Sheets.

## `stateKey` (type: `string`):

Optional name of the 'onlyNew' memory, e.g. 'python-eu-daily'. Leave empty to derive it automatically from your filters.

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

Plain text, Markdown (best for LLMs and RAG), the original HTML, or no description (smallest output).

## `maxDescriptionLength` (type: `integer`):

Cut long descriptions to this many characters. 0 = full description.

## `useProxyFallback` (type: `boolean`):

If a board refuses the direct request, retry once through the Apify datacenter proxy. Residential proxies are never used.

## Actor input object example

```json
{
  "sources": [
    "remoteok",
    "remotive",
    "weworkremotely",
    "jobicy"
  ],
  "categories": [
    "developer"
  ],
  "keywords": [
    "python"
  ],
  "searchInDescription": true,
  "includeWorldwide": true,
  "minSalary": 0,
  "onlyWithSalary": false,
  "maxAgeDays": 30,
  "maxResults": 50,
  "deduplicate": true,
  "onlyNew": false,
  "descriptionFormat": "text",
  "maxDescriptionLength": 0,
  "useProxyFallback": true
}
```

# Actor output Schema

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

One item per job, newest first.

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

Jobs downloaded, matching, deduplicated and delivered, per source.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "sources": [
        "remoteok",
        "remotive",
        "weworkremotely",
        "jobicy"
    ],
    "categories": [
        "developer"
    ],
    "keywords": [
        "python"
    ],
    "minSalary": 0,
    "maxAgeDays": 30,
    "maxResults": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("actose/remote-jobs-feeds-aggregator").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "sources": [
        "remoteok",
        "remotive",
        "weworkremotely",
        "jobicy",
    ],
    "categories": ["developer"],
    "keywords": ["python"],
    "minSalary": 0,
    "maxAgeDays": 30,
    "maxResults": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("actose/remote-jobs-feeds-aggregator").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "sources": [
    "remoteok",
    "remotive",
    "weworkremotely",
    "jobicy"
  ],
  "categories": [
    "developer"
  ],
  "keywords": [
    "python"
  ],
  "minSalary": 0,
  "maxAgeDays": 30,
  "maxResults": 50
}' |
apify call actose/remote-jobs-feeds-aggregator --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,actose/remote-jobs-feeds-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/0YkYbnX5D4gInqt9a/builds/he5O3k50ewQlZTtGU/openapi.json
