# We Work Remotely Jobs Scraper (WeWorkRemotely RSS) (`ataism/wwr-jobs`) Actor

WeWorkRemotely jobs from the official RSS: full descriptions, filters, only-new daily alerts. For alerts and research, not job boards. $2.50 per 1,000 jobs.

- **URL**: https://apify.com/ataism/wwr-jobs.md
- **Developed by:** [Kaizoku Data](https://apify.com/ataism) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.50 / 1,000 job saveds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## We Work Remotely Jobs Scraper (WeWorkRemotely RSS)

Get remote jobs from [We Work Remotely](https://weworkremotely.com) (WeWorkRemotely) as clean, structured data: **full description included**, company, category, job type, region, countries, skills and dates. The Actor reads WWR's **official public RSS feeds**, which WWR offers for anyone to use, so it needs no browser and no proxies, and doesn't break when the website layout changes.

**Price: $2.50 per 1,000 jobs ($0.0025 per job), full description included.** No separate "detail" charge. A daily alert typically costs about $0.05–0.15 a day.

*For alerts, research and internal tools, with a link back to WWR; not for job boards, aggregators or job search services (see* Terms of use\*).\*

- **Only new jobs:** schedule it daily and each run returns (and charges for) only jobs you haven't seen, remembered for 120 days.
- **Whole-word filters:** keywords and exclude keywords (`go` won't match "Google"; `c++` and `node.js` work), regions and countries, job types, posted within N hours.
- **All 11 category feeds plus the latest-jobs feed**, de-duplicated.
- **Emails and phone numbers removed** from titles and descriptions; every row carries its WWR link and an attribution string.
- **Quick start:** press *Start*. The prefilled input reads the Programming feed and finishes in seconds.

### What you can do with it

- **Daily job alerts.** Schedule it with **Only new jobs** turned on, and each run returns (and charges for) only jobs you haven't seen.
- **Alerts and internal dashboards.** Send new jobs to your own or your team's Slack, Discord, email or internal dashboard. Keep the link back to WWR (see *Terms of use* below). Not for public job boards, aggregators or job search services.
- **Market research.** Track which companies hire remotely, in which categories and regions.

### Input

| Field | What it does |
|---|---|
| **Categories** | `all` (default) reads all 11 category feeds plus the latest-jobs feed and removes duplicates. Or pick any of: programming, full-stack, back-end, front-end, devops, design, product, customer-support, sales-marketing, management-finance, other, latest. |
| **Keywords (any)** | Keep a job if any keyword appears as a whole word in its title, company, category, skills or description (`go` won't match "Google"; `c++` and `node.js` work). |
| **Exclude keywords** | Drop a job if any of these appear (whole words). |
| **Regions / countries** | e.g. `Europe`, `United States`. Whole-word match on the job's region, country list and state. |
| **Always include worldwide jobs** | On by default: "Anywhere in the World" jobs pass the region filter. |
| **Job types** | Full-Time, Contract, Part-Time. |
| **Posted within (hours)** | e.g. `24` for a daily digest. |
| **Only new jobs since my last run** | Remembers the job IDs it already returned to you (for 120 days) and skips them. |
| **Max jobs** | Newest first. |
| **Include description HTML** | Plain text is always included; turn HTML off for smaller datasets. |

#### Example inputs

**1. Daily alert: Python or Go jobs open to Europe** (schedule it daily; with *Only new jobs* each run returns only jobs you haven't seen):

```json
{
  "categories": ["programming", "devops"],
  "keywords": ["python", "golang"],
  "regions": ["Europe"],
  "postedSinceHours": 24,
  "onlyNew": true
}
```

**2. All DevOps jobs open to Europe from the last 24 hours, full-time only** ("Anywhere in the World" jobs are kept too unless you turn off *Always include worldwide jobs*):

```json
{
  "categories": ["devops"],
  "regions": ["Europe"],
  "jobTypes": ["Full-Time"],
  "postedSinceHours": 24
}
```

**3. Product and design jobs without senior or lead roles** (exclude words match whole words anywhere in the job):

```json
{
  "categories": ["product", "design"],
  "excludeKeywords": ["senior", "lead", "principal", "head"],
  "maxItems": 100,
  "includeDescriptionHtml": false
}
```

### Output

One item per job:

```json
{
  "id": "acme-senior-backend-engineer",
  "url": "https://weworkremotely.com/remote-jobs/acme-senior-backend-engineer",
  "title": "Senior Backend Engineer",
  "company": "Acme",
  "companyLogoUrl": "https://wwr-pro.s3.amazonaws.com/logos/…/logo.gif",
  "companyHeadquarters": "Berlin",
  "companyWebsite": "https://acme.dev",
  "category": "Back-End Programming",
  "jobType": "Full-Time",
  "region": "Anywhere in the World",
  "countries": ["Germany", "United Kingdom of Great Britain and Northern Ireland"],
  "state": null,
  "skills": ["Go", "PostgreSQL"],
  "descriptionHtml": "<p>…</p>",
  "descriptionText": "…",
  "publishedAt": "2026-10-17T13:11:19.000Z",
  "expiresAt": "2026-11-16T13:11:19.000Z",
  "feed": "back-end",
  "source": "We Work Remotely",
  "attribution": "Source: We Work Remotely (https://weworkremotely.com/remote-jobs/acme-senior-backend-engineer)",
  "scrapedAt": "2026-10-18T06:00:00.000Z"
}
```

You can export it as JSON, CSV, Excel or XML, or read it through the Apify API.

**Fill rates** in our 2026-10-18 sample of 529 listings: `id`, `url`, `title`, `company`, `category`, `region`, `description` and `publishedAt` 100%; `jobType` and `expiresAt` 95%; `companyHeadquarters` 98%; `companyWebsite` 35%; `companyLogoUrl` 26%; `countries` 21%; `state` 22%; `skills` 17%. The low numbers are fields WWR leaves empty in its feed.

### Pricing

Pay per event:

- **$0.0025 per job** saved to the dataset.
- Apify's standard tiny start fee per run.

| Run | Jobs | Cost |
|---|---|---|
| Daily alert with *Only new jobs* | a few dozen | about $0.05–0.15 a day |
| One full read of all feeds | about 400–450 | about $1.00–1.13 |
| 1,000 jobs | 1,000 | $2.50 |

For example, 1,000 jobs cost **$2.50**. A daily alert with *Only new jobs* typically returns a few dozen jobs, about **$0.05–0.15 a day**. You can cap the cost of any run with Apify's *Max total charge* setting. The Actor stops when it reaches the cap, and jobs already delivered are remembered by *Only new jobs*, so you never pay twice for the same job.

### Use it via API, n8n or Make

For alerts for yourself or your team, research and internal tools (not for job boards, aggregators or job search services; see *Terms of use*). Get an API token in Apify Console → *Settings* → *API & Integrations*, and keep it in an environment variable (`APIFY_TOKEN` below).

**cURL** (starts a run, waits for it and returns the jobs as JSON):

```bash
curl -X POST "https://api.apify.com/v2/acts/ataism~wwr-jobs/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"categories": ["programming"], "keywords": ["python"], "postedSinceHours": 24}'
```

**JavaScript** (`npm install apify-client`):

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('ataism/wwr-jobs').call({
    categories: ['programming'],
    postedSinceHours: 24,
    onlyNew: true,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const job of items) console.log(job.company, job.title, job.url);
```

**Python** (`pip install "apify-client>=3"`):

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("ataism/wwr-jobs").call(run_input={
    "categories": ["programming"],
    "postedSinceHours": 24,
    "onlyNew": True,
})
for job in client.dataset(run.default_dataset_id).iterate_items():
    print(job["company"], job["title"], job["url"])
```

(With apify-client versions before 3.0, `call()` returns a dict: use `run["defaultDatasetId"]`.)

**n8n** (new jobs to your own Slack, sheet or email):

1. Add a *Schedule Trigger* (for example daily at 08:00).
2. Add the **Apify** node, operation **Run an Actor and get dataset**, Actor `ataism/wwr-jobs`, and paste your input JSON with `"onlyNew": true`.
3. Add a *Slack*, *Google Sheets* or email node and post `title`, `company` and `url` (the link back to WWR).

**Make:**

1. Add the **Apify** module **Run an Actor**, choose `ataism/wwr-jobs`, paste the input JSON and set *Run synchronously* to *Yes*.
2. Add the **Apify** module **Get Dataset Items** and map the run's default dataset ID.
3. Send the rows to your own Slack, sheet or email, keeping each job's `url`. Schedule the scenario daily.

### Terms of use: attribution, and no job boards

This Actor reads **only WWR's public RSS feed**. It doesn't scrape the website or use WWR's API. WWR's feed page says: *"Anyone can use the feed, all we ask is that you attribute the links back to We Work Remotely."*

- **Attribution on every row:** each item carries the job's WWR `url` and an `attribution` string. Wherever you show a job, link it to its `url`.
- **Not for republishing on job boards:** don't use this data to run or feed a job board, job aggregator or job search service. WWR's [API terms](https://weworkremotely.com/api-terms-and-guidelines) rule that out for their data. Good uses are research, market analysis, alerts for yourself or your team, and internal tools.

### Privacy

This Actor collects **job listings only**. To avoid passing on personal data, it **removes email addresses and phone numbers** from titles and descriptions; you'll see `[email removed]` or `[phone removed]` instead. Encoded addresses (HTML entities, `%40`) are caught too, and if an address is split across HTML tags, that listing's HTML is replaced by its cleaned plain text. Free-text tricks such as "john (at) example (dot) com" may not be caught. Apply through the job's WWR page.

### Reliability

- Reads official RSS, not HTML, so website redesigns don't break it.
- Retries on network errors and 429/5xx responses with backoff, honours `Retry-After`, and pauses between feeds.
- If a feed returns an error page instead of RSS, it's retried. If it still fails, the run names the failed feeds in its status message and in the `RUN_SUMMARY` record, and the run fails if every feed failed.
- A daily self-test checks the live feeds against a recorded baseline (schema and field fill rates). We fix breakages fast.

### FAQ

**How many jobs are there?** About 400–450 unique live jobs across all categories (2026-10-18). The *latest* feed alone holds roughly the newest 90.

**Is it allowed?** Yes, for alerts, research and internal tools with a link back to WWR. We read only the official RSS feeds WWR publishes ("Anyone can use the feed"), and WWR's robots.txt allows them. Not for running or feeding a job board, aggregator or job search service (see *Terms of use*). We're not affiliated with We Work Remotely.

**Something's wrong?** Open an issue on the Actor's Issues tab. We aim to reply within 2 working days.

# Actor input Schema

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

Which official WWR feeds to read. "all" reads every category feed plus the latest-jobs feed and removes duplicates.

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

Keep a job if ANY keyword appears in its title, company, category, skills or description (case-insensitive). Leave empty to keep all.

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

Drop a job if ANY of these words appears in its title, company, category, skills or description.

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

Keep jobs whose region, country list or state contains any of these (e.g. "Europe", "United States", "Canada").

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

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

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

Keep only these job types. Leave empty for all.

## `postedSinceHours` (type: `integer`):

Keep only jobs published in the last N hours (e.g. 24 for a daily alert).

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

Skip jobs this Actor already returned to you in earlier runs (remembered for 120 days in a key-value store named "wwr-jobs-state" in your account). Ideal for scheduled alerts: you only pay for new jobs.

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

Use different names to keep separate only-new memories for different saved tasks.

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

Stop after this many jobs (newest first). Leave empty for no limit.

## `includeDescriptionHtml` (type: `boolean`):

Plain-text description is always included. Turn off to make the dataset smaller.

## `mode` (type: `string`):

Internal: "selfTest" runs the maintainer health check and saves no jobs.

## Actor input object example

```json
{
  "categories": [
    "programming"
  ],
  "keywords": [],
  "includeWorldwide": true,
  "onlyNew": false,
  "stateKey": "default",
  "includeDescriptionHtml": true,
  "mode": "scrape"
}
```

# Actor output Schema

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

Remote job listings, one item per row (JSON, CSV, Excel or XML).

## `runSummary` (type: `string`):

Counts, skipped inputs and errors 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 = {
    "categories": [
        "programming"
    ],
    "keywords": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("ataism/wwr-jobs").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 = {
    "categories": ["programming"],
    "keywords": [],
}

# Run the Actor and wait for it to finish
run = client.actor("ataism/wwr-jobs").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 '{
  "categories": [
    "programming"
  ],
  "keywords": []
}' |
apify call ataism/wwr-jobs --silent --output-dataset

```

## MCP server setup

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

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/Ge7a02HPcuh6Hlafd/builds/LWsM2vJnShfTOjDwQ/openapi.json
