# We Work Remotely Jobs Scraper (`nice_dev/weworkremotely-jobs-scraper`) Actor

Scrape every remote job on We Work Remotely: title, company, salary band and real salary, region and countries, skills, full description, badges. Filters, monitoring of new and closed jobs. Export to JSON, CSV or Excel.

- **URL**: https://apify.com/nice_dev/weworkremotely-jobs-scraper.md
- **Developed by:** [Nice Dev](https://apify.com/nice_dev) (community)
- **Categories:** Jobs, MCP servers, Automation
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.76 / 1,000 jobs

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

### 💼 What is We Work Remotely Jobs Scraper?

**We Work Remotely Jobs Scraper** extracts **every remote job from [We Work Remotely](https://weworkremotely.com)**: **title, company, salary band and the real salary written in the job, region and countries (ISO codes), skills, full description, badges, apply link** and, as options, the **company profile** and the **e-mail addresses published on the company's own website**. It works as a **We Work Remotely API alternative**: run it on a schedule, call it from your code, or plug it into Make, Zapier or n8n.

Pick one or more **categories** (or leave them empty for the whole site), add a **keyword** (`python`) or paste any We Work Remotely URL, click **Start**, and download the jobs in JSON, CSV or Excel. No login, nothing to set up, and it is **fast: about 100 jobs in 20 seconds**.&#x20;

### 📋 What data can you extract from We Work Remotely?

One item per job, 66 fields:

| Category | What you get |
| --- | --- |
| 💼 **Job** | title, link, job type (full-time, contract), category, seniority (junior … director), skills — `Staff Software Engineer`, `Full-Time` |
| 🏢 **Company** | name, logo, headquarters, Top 100 remote company or not, We Work Remotely company page |
| 🌍 **Where you can work from** | region as written (`USA Only`, `Europe`…), countries and their ISO codes, state, worldwide or not, time zones asked |
| 💵 **Salary** | the site's yearly band and the real amount written in the job: minimum, maximum, currency, per year / month / hour — `$162,400—$290,000 USD` |
| ⭐ **Badges** | featured, boosted, hot, pinned, new |
| 🕒 **Dates** | posting date, expiry date, age in days, days left — what the date filters read |
| 📝 **Description** | full text and HTML, visa sponsorship, relocation help |
| 📨 **How to apply** *(job page)* | apply link and type (employer's page, e-mail, We Work Remotely account), the apply e-mail when the employer gives one, exact list of countries accepted |
| 🏷️ **Company profile** *(option)* | website, address, number of open jobs, About text (when the company wrote one), remote and diversity policy links, benefits, hiring process |
| 📇 **Company contacts** *(option)* | e-mail addresses, phone numbers and social links published on the company's own website |
| 🔔 **Monitoring** | active or closed (taken down since the last run), date closed, first seen, which of your keywords matched |

Every field, with an example, is listed in the **Output** section below.

The apply link and type, the exact countries accepted, the company website, address and number of jobs posted come from the job page: they are filled when **Extract job page details** is on (`extractDetails`). The company profile needs **Add the company profile**, the contacts **Email from the company website**. Without them the run reads only the job lists, and is much faster.

### ✅ Why use We Work Remotely Jobs Scraper?

- 🌍 **The whole site in one run**: every category, about 400 jobs, or just the ones you pick — about 100 jobs in 20 seconds, the whole site with every job page in about 11 minutes.&#x20;
- 💵 **The real salary, not only the band**: when the job states an amount (`$140K-$180K`, `€83,000 annually`), you get its minimum, maximum, currency and period, and where it was read. A job that lists a range per place (« San Francisco … all other locations, including remote … ») gives the range for remote workers.
- 🗺️ **Countries you can filter on**: ISO codes, worldwide or not, the exact list of countries the employer accepts, the time zones the job asks for.
- 🎯 **Filters the site does not have**: salary minimum / maximum, seniority, skills, title words, companies to keep or drop, featured / Top 100 / hot jobs, posting dates.
- 🔔 **Monitoring built in**: tick **Only new jobs**, schedule the Actor, and each run returns (and charges) only the jobs it has never delivered — and, if you want, the jobs taken down since.
- 📇 **Company contacts**: the e-mail addresses, phone numbers and social links published on each company's own website, once per company.
- 🔌 API, scheduling, monitoring, integrations (Make, Zapier, n8n, Google Sheets…), proxy rotation and JSON/CSV/Excel export via the Apify platform.

### 🚀 How to scrape We Work Remotely

1. Create a free Apify account.
2. Open **We Work Remotely Jobs Scraper**, pick some **Categories** (empty = all) and, if you want, type a **Search keyword** (e.g. `python`).
3. Or paste your own We Work Remotely URLs into **Start URLs**: category pages, search pages, company pages or single job pages.
4. Set **Max jobs** (100 by default, 0 = no limit), the filters you need, tick **Extract job page details** for the apply link and the exact countries, then click **Start**.
5. Download the dataset in JSON, CSV, Excel or via API.

### 💰 How much does it cost to scrape We Work Remotely?

This Actor uses **pay per event** pricing: you pay for the jobs you get and for the options you tick, never for a blank row. Platform usage (compute, proxy) is included in the price.

| Event | When it is charged | Free | Bronze | Silver | Gold |
| --- | --- | --- | --- | --- | --- |
| Job | each job saved from the job lists (**Extract job page details** off) | **$0.79** | $0.78 | $0.77 | $0.76 |
| Job with details | each job saved with its job page read (**Extract job page details** on, or a job URL pasted), instead of a Job | **$0.88** | $0.87 | $0.86 | $0.85 |
| Company profile | each company profile added (**Add the company profile**), once per company per run | **$0.89** | $0.88 | $0.87 | $0.86 |
| Apply e-mail | each application e-mail address delivered (**Add the apply e-mail**) | **$0.77** | $0.76 | $0.75 | $0.74 |
| Company e-mail | each company whose own website gives at least one e-mail address (**Email from the company website**), once per run; phone numbers and social links found alone are free | **$0.88** | $0.86 | $0.81 | $0.77 |
| Closed job | each job reported as taken down (**Report jobs taken down**), instead of a job | **$0.36** | $0.35 | $0.34 | $0.33 |
| Filter check | each job read from the job lists and dropped by one of your filters: filtering never costs more than taking everything | **$0.044** | $0.034 | $0.024 | $0.014 |
| Job page filter check | each job page read (**Extract job page details**) and dropped by a filter that needs that page | **$0.17** | $0.16 | $0.15 | $0.14 |

Prices are per 1,000 events. Plus **$0.01 per run start** ($1 per 100 runs). Every option is off by default: you only pay for what you tick.

- The whole site, about 400 remote jobs, ≈ **$0.33** (≈ **$0.37** with every job page read).
- A daily monitor of 30 new jobs (`onlyNew`) ≈ **$0.034** a day.
- The $5 of free credits every Apify account starts with already cover about **6,000 jobs**.

A job your filters drop is never saved nor charged as a job (only its small filter check), and you never pay twice for the same job (`onlyNew`).

### ⚙️ Input

The jobs of two categories that mention Python:

```json
{
    "categories": ["remote-full-stack-programming-jobs", "remote-back-end-programming-jobs"],
    "query": "python",
    "maxItems": 100
}
```

Several keywords and places, a salary floor, only recent jobs, with their job page:

```json
{
    "searchQueries": ["react", "typescript"],
    "locations": ["Europe", "Germany"],
    "minSalary": 80000,
    "postedAfter": "7 days",
    "extractDetails": true,
    "maxItemsPerQuery": 50,
    "maxItems": 300
}
```

Daily monitoring of the design jobs: the new ones, and the ones taken down since the last run:

```json
{
    "categories": ["remote-design-jobs", "remote-product-design-jobs", "remote-ux-ui-design-jobs"],
    "onlyNew": true,
    "includeClosedJobs": true,
    "stateKey": "design-daily",
    "maxItems": 0
}
```

Or with your own URLs, plus the company profile and its contacts:

```json
{
    "startUrls": [
        { "url": "https://weworkremotely.com/remote-jobs/search?term=python" },
        { "url": "https://weworkremotely.com/company/zapier" }
    ],
    "extractDetails": true,
    "includeCompanyProfile": true,
    "enrichEmails": true
}
```

| Field | Notes |
| --- | --- |
| `categories` | Job categories to read, e.g. `remote-design-jobs`, `remote-customer-support-jobs`. Empty = every category (the whole site). Ignored when `startUrls` is set. |
| `startUrls` | Category pages (`https://weworkremotely.com/categories/remote-design-jobs`), search pages (`https://weworkremotely.com/remote-jobs/search?term=python`), the full-time / contract pages, company pages (`https://weworkremotely.com/company/zapier`) or single job pages. Keywords, filters, caps and monitoring still apply. |
| `query`, `searchQueries` | Keep only the jobs whose title, company, skills or description contain one of these words or phrases (`python`, `customer success`), case and accents ignored. `matchedKeywords` tells which one matched. |
| `location`, `locations`, `includeWorldwide` | Keep only the jobs open to one of these places (`Germany`, `Europe`, `United States`), matched against the region, the countries and the headquarters. Jobs open to anyone are kept too, unless `includeWorldwide` is unticked. A job limited to a wider area without a list of countries (`Europe`, `EMEA`) is kept unless `extractDetails` reads its exact countries. |
| `worldwideOnly` | Keep only the jobs open to applicants anywhere in the world. |
| `maxItems` | Stop after this many jobs for the whole run (`0` = no limit). |
| `maxItemsPerQuery` | Cap for EACH category or start URL, so that the first one cannot use up the whole budget (`0` = no cap). |
| `sortBy` | Which jobs `maxItems` and the per-category cap keep: `newest`, `oldest` or `salary` (highest first, in US dollars: the band of the site or an amount in USD; a salary in another currency without a band is ranked with the jobs without a salary, last). The order is made inside each category or start URL, not across them; with `extractDetails` the rows are saved as the job pages arrive. |
| `extractDetails` | Open each job page for the apply link, the exact countries accepted, the employer's salary, the company website, address and number of jobs posted (charged as a job with details). |
| `includeCompanyProfile` | Add each company's We Work Remotely profile, once per company per run. |
| `includeApplyEmail` | With `extractDetails`: add the application e-mail address when the employer gives one. |
| `enrichEmails` | Visit each company's own website (3 pages at most, once per company per run) for its e-mail addresses, phone numbers and social links. Needs `extractDetails` or `includeCompanyProfile`. |
| `descriptionMaxLength` | Cut the plain-text description to this many characters (`0` = the full text, up to 32,000 characters); the HTML stays whole. |
| `jobTypes` | `Full-Time`, `Contract`. Empty = both. |
| `salaryRanges` | The site's yearly bands, e.g. `$100,000 or more USD`. Empty = all. |
| `minSalary`, `maxSalary`, `includeNoSalary`, `onlyWithSalary` | Yearly salary in US dollars (`80000`): the band of the site or the amount written in the job. Jobs without a salary follow `includeNoSalary`; `onlyWithSalary` keeps only the jobs that show one. |
| `seniority` | Levels read from the title: `junior`, `senior`, `lead`, `manager`… Jobs whose title names no level are dropped when this list is set. |
| `titleKeywords`, `skills` | Keep the jobs whose title (`engineer`, `designer`) or skills (`React`, `SEO`) contain one of these words. |
| `includeCompanies`, `excludeCompanies` | Companies to keep or drop, by name or company page slug (`zapier`), case ignored. |
| `onlyFeatured`, `onlyTop100`, `onlyHot` | Keep only the featured jobs, the jobs of the Top 100 remote companies, or the hot jobs. |
| `postedAfter`, `postedBefore` | Posting date range: `2026-09-01`, or a period before now (`7 days`, `2 weeks`, `1 month`, `24 hours`). |
| `excludeKeywords` | Drop the jobs whose title contains one of these words (`senior`, `intern`), case and accents ignored. |
| `stateKey`, `onlyNew`, `resetState`, `includeClosedJobs` | Monitoring: `onlyNew` returns only the jobs never delivered under this memory key (`design-daily`); `includeClosedJobs` also reports, once, the jobs taken down since; `resetState` forgets the memory. |
| Advanced | `proxyConfiguration` (Apify proxy by default, included in the price; the residential proxy is not available), `maxConcurrency`, `maxRequestsPerMinute`, `minRequestIntervalMs`, `maxRequestRetries`, `debugLog`. |

### 📦 Output

A real job from a test run, shortened: the description is cut, and some empty or option fields are left out (the full list is below).

```json
{
    "id": "samsara-staff-software-engineer",
    "url": "https://weworkremotely.com/remote-jobs/samsara-staff-software-engineer",
    "title": "Staff Software Engineer",
    "status": "active",
    "companyName": "Samsara",
    "companySlug": "samsara",
    "companyUrl": "https://weworkremotely.com/company/samsara",
    "companyHeadquarters": "Remote",
    "isTop100Company": false,
    "category": "Full-Stack Programming",
    "categories": ["Full-Stack Programming"],
    "jobType": "Full-Time",
    "region": null,
    "countries": ["United States of America"],
    "countryCodes": ["US"],
    "state": "California",
    "isWorldwide": false,
    "applicantCountries": ["US"],
    "skills": [],
    "salaryRange": "$100,000 or more USD",
    "salaryMin": 162400,
    "salaryMax": 290000,
    "salaryCurrency": "USD",
    "salaryPeriod": "year",
    "salaryText": "$162,400—$290,000 USD",
    "salarySource": "description",
    "seniority": "lead",
    "isFeatured": true,
    "isHot": true,
    "isPinned": true,
    "publishedAt": "2026-09-16T13:57:14.000Z",
    "firstPublishedAt": "2026-08-17T13:57:14.000Z",
    "expiresAt": "2026-10-16T13:57:14.000Z",
    "ageDays": 29,
    "description": "About the role:\n\nSamsara (NYSE: IOT) sits at the center of hardware, software, AI, and the physical world. […]",
    "relocationOffered": false,
    "timezoneRestrictions": [],
    "matchedKeywords": ["engineer"],
    "applyUrl": "https://weworkremotely.com/job-seekers/account/register?alert=Create+an+account+to+view+full+job+details.",
    "applyType": "account",
    "applyEmail": null,
    "directApply": false,
    "companyWebsite": "https://www.samsara.com/",
    "companyDomain": "samsara.com",
    "companyJobsPosted": 6,
    "companyEmails": [],
    "companyPhones": [],
    "companySocialLinks": [],
    "searchUrl": "https://weworkremotely.com/categories/remote-full-stack-programming-jobs",
    "scrapedAt": "2026-10-16T08:20:56.239Z"
}
```

You can download the dataset in various formats such as JSON, HTML, CSV or Excel.

#### All 66 fields

| Fields | What you get |
| --- | --- |
| `id`, `url`, `title`, `status` | **Job**: job slug, job page, title, `active` or `closed` |
| `companyName`, `companySlug`, `companyUrl`, `companyLogoUrl`, `companyHeadquarters`, `isTop100Company` | **Company**: `Samsara`, `samsara`, its We Work Remotely page, logo, `Remote`, `false` |
| `category`, `categories`, `jobType`, `seniority`, `skills` | `Full-Stack Programming` (every category of the job in `categories`), `Full-Time` or `Contract`, `lead`, `["Java", "PostgreSQL", "Spring"]` |
| `region`, `countries`, `countryCodes`, `state`, `isWorldwide`, `applicantCountries`, `timezoneRestrictions` | **Where you can work from**: `USA Only`, `["United States of America"]`, `["US"]`, `California`, `false`, exact ISO list from the job page, `["CET"]` |
| `salaryRange`, `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod`, `salaryText`, `salarySource` | **Salary**: `$100,000 or more USD`, `162400`, `290000`, `USD`, `year`, the sentence as written, `description` / `jobPage` / `band` |
| `isFeatured`, `isBoosted`, `isHot`, `isPinned`, `isNew` | **Badges** shown by the site |
| `publishedAt`, `firstPublishedAt`, `expiresAt`, `ageDays`, `daysUntilExpiry` | **Dates**: ISO 8601 UTC, then whole days (what the date filters read). `firstPublishedAt` is the date the job was first posted; `publishedAt` is its latest (re)posting, as the site shows it (the site renews a job every 30 days) |
| `description`, `descriptionHtml`, `visaSponsorship`, `relocationOffered` | full text, full HTML, `true` / `false` / `null` when the job says nothing |
| `applyUrl`, `applyType`, `applyEmail`, `directApply` | **How to apply** (job page): link, `external` / `email` / `account` / `login`, e-mail (option), `true` / `false` |
| `companyWebsite`, `companyDomain`, `companyAddress`, `companyJobsPosted` | `https://www.samsara.com/`, `samsara.com`, address, `6` (all the jobs the company ever posted, from the job page) |
| `companyOpenJobs`, `companyDescription`, `companyRemotePolicyUrl`, `companyDeiPolicyUrl`, `companyBenefits`, `companyHiringProcess` | **Company profile** (option): the jobs the company has open now, then its own texts and links (`null` when it wrote none) |
| `companyEmails`, `companyEmailSource`, `companyPhones`, `companySocialLinks` | **Company contacts** (option): e-mails, the page where the first one was found, phone numbers, social links |
| `closedAt`, `firstSeenAt`, `matchedKeywords`, `searchUrl`, `scrapedAt` | **Monitoring**: date closed, first delivery, `["engineer"]`, the page the job was read from, ISO timestamp |

### 💡 Tips

#### How to get more results

Leave **Categories** empty and set `maxItems` to `0`: the run returns every job on the site (about 400). The site shows only open jobs: schedule the Actor daily with `onlyNew` to keep a history.

#### How to reduce costs

Leave `extractDetails` off unless you need the apply link, the exact countries accepted or the company website: a job read from the lists costs less than a job with details. The filters (a job dropped costs only the filter check), `maxItems`, `maxItemsPerQuery` and `onlyNew` for recurring runs (you never pay twice for the same job) do the rest.

#### Monitoring: new jobs and jobs taken down

Tick **Only new jobs** (`onlyNew`) and schedule the Actor. The first run returns everything; each later run skips the jobs already delivered: they are not saved, not charged, and their page is not even opened. The memory lives in a named key-value store of your account (`weworkremotely-jobs-scraper-seen`, up to 150,000 jobs per key) and is only updated with jobs that really reached the dataset, so a failed run never hides anything. Give each schedule its own `stateKey` (two schedules sharing a key would hide each other's jobs), and tick `resetState` once to start over.

Tick **Report jobs taken down** (`includeClosedJobs`) as well, and each run also saves, once, every job a previous run delivered that is no longer on its category page, with `status` = `closed` and `closedAt`. It works with categories (not with search or company pages), and only for the jobs delivered by runs where the box was ticked.

#### Filter by posting date

`postedAfter` and `postedBefore` take a date (`2026-09-01`, the whole day is included) or a period before now (`7 days`, `2 weeks`, `1 month`; via the API also `24 hours` or a full ISO date-time). The filter reads `publishedAt`; a job without a posting date is dropped as soon as a date bound is set. Filtered-out jobs are not saved and do not count in `maxItems` (each job dropped costs the filter check, see Pricing); the run summary tells how many were filtered.

### 🔌 Integrations and API

Call the Actor via the Apify API, the JavaScript or Python clients, or connect it with integrations and webhooks (Make, Zapier, n8n, Google Sheets, Slack, Airtable…). The dataset can be fetched as JSON or CSV from any tool.

### 🤖 Use with AI agents (MCP)

AI agents (Claude, ChatGPT, Cursor…) can find and run this Actor through the [Apify MCP server](https://mcp.apify.com), billed to their Apify account like any run. It returns one item per We Work Remotely job. Actor id: `nice_dev/weworkremotely-jobs-scraper`; MCP server with this Actor only: `https://mcp.apify.com/?tools=fetch-actor-details,nice_dev/weworkremotely-jobs-scraper`.

Smallest input, for a cheap first call:

```json
{
    "categories": ["remote-design-jobs"],
    "maxItems": 10
}
```

Key output fields: `url`, `title`, `companyName`, `region`, `countryCodes`, `salaryMin`, `salaryMax`, `salaryCurrency`, `publishedAt` and `applyUrl` (with `extractDetails`).

Cost: $0.79 per 1,000 jobs ($0.76 on Gold) plus $0.01 per run; job pages and the other options are priced in the pricing section above. Cap each call with `maxItems` and, through the API, with the run option `maxTotalChargeUsd`.

### ❓ FAQ

#### Is it legal to scrape We Work Remotely?

The Actor only reads what We Work Remotely shows publicly to any anonymous visitor. It logs in to nothing and creates no account. Job posts are business data; some of them hold personal data (a recruiter's application e-mail, for instance), which is protected by GDPR: do not store it without a legitimate reason. You are responsible for using the data in compliance with We Work Remotely's Terms of Use and applicable law. This Actor is not affiliated with We Work Remotely.

#### Where do the e-mail addresses come from?

With `enrichEmails` on, the Actor reads the pages each company publishes on its own website (home page, contact page, legal notice) and returns the business contact addresses shown there — what any visitor sees. It does not log in, guess addresses, read contact forms or bypass any protection. Some of these addresses are personal data under GDPR (e.g. `firstname.lastname@company.com`): you are responsible for having a lawful basis before using them, for informing the people concerned and for honouring opt-outs. Rules for B2B e-mail prospecting differ by country (opt-out in some, prior consent in others).

#### And the phone numbers?

The Actor also returns the phone numbers the company publishes on those same pages (`tel:` links, the company's structured data, numbers written next to a "phone" label or in international form). It never guesses a number, never dials, sends no message and reads nothing that is not shown to any visitor. A number can be personal data (a sole trader's mobile): the same responsibilities apply, and rules on phone prospecting (opt-out registers such as Bloctel, prior consent in some countries) differ by country.

#### Does it need a login or a proxy?

No login. The proxy is included in the price: leave the default setting (the residential proxy is not available). A request the site turns away is retried at once on a new proxy session, up to 10 times on top of the retries (without a proxy, after a pause of 5 seconds, doubled at each retry up to 150 seconds).

#### Is the data safe to open in Excel or to show on a web page?

Titles and descriptions are the employers' own words, copied as they are, and a phone number starts with `+`. A text can begin with `-`, `+`, `=` or `@`: Excel and Google Sheets may read such a cell of a CSV file as a formula or as a number. The Actor leaves the text as it is, so that the JSON and the API give the real value: when you open a CSV, import these columns as text. `descriptionHtml` is the employer's HTML, not cleaned by the Actor: on a web page, sanitize it, and escape every other field like any text written by a stranger.

#### Known limitations

- **Apply link of a locked job**: for many jobs, We Work Remotely shows the employer's apply link only to signed-in job seekers. The Actor uses no account, so `applyUrl` is then the site's sign-up or sign-in page, with `applyType` = `account` or `login`; the employer's own link is given when the job shows it publicly (`external`, `email`).
- **Salary filters in US dollars only**: `minSalary` and `maxSalary` compare amounts in USD. A job whose salary is written in another currency is judged on the site's yearly band, with no exchange-rate conversion; without a band, it counts as a job without salary (`includeNoSalary`). Its own amount and currency are still delivered.
- **Location filter without the job page**: a job limited to a wider area whose listing names no country (`Europe`, `EMEA`, `North America`) is kept for any place you type, since the listing cannot tell whether your country is in it. Tick `extractDetails`: the job page gives the exact countries accepted, and the filter uses them.
- **Search, job type and company pages** pasted into `startUrls` without `extractDetails`: the posting date is known to the day only, and the keyword, skill and date filters judge the job on what its listing card shows. Tick `extractDetails` for the full job.
- **Jobs taken down** are reported only from category pages read in full: never for a category the run could not read, and never by a run that stopped early (`maxItems` reached, timeout, maximum cost) — those jobs stay in memory and the next complete run reports them.
- **Company contacts**: with `extractDetails` alone, a job read before its company's website is known can come without contacts; tick `includeCompanyProfile` too and every job of a company gets them.
- `onlyNew` remembers job ids, not their content: a job whose salary changed is not returned again.
- Two runs sharing the same `stateKey` at the same time may both return the same new job.

**A run that reaches its timeout** stops itself about 45 seconds before it: no new page is asked, what it read is saved and, with `onlyNew`, remembered, and the run ends *Succeeded* with "Stopped before the run's timeout". Resurrect it to go on from there, or give the next run a longer timeout (Run options). The whole site, about 400 jobs, takes about 11 minutes even with every job page: the default timeout is far more than enough.&#x20;

**A run the platform stops without warning** (out of memory)

- Resurrect it: it goes on from where it stood at most a minute before the stop. What it had read since is read again, and the jobs already saved are skipped: none is delivered or charged twice, and `maxItems` still counts them.
- With `onlyNew`, the memory is saved after each write (at most once every 2 seconds): resurrect the stopped run and the jobs it had saved meanwhile join the memory; leave it stopped for good, and the next run may return the jobs of its last 2 seconds once more.

#### Something doesn't work?

The last line of the log counts the jobs saved, filtered out and no longer on We Work Remotely (removed while the run was reading them), and the requests that failed after every retry. Those requests and the removed jobs are listed, with the reason, in the `FAILED_REQUESTS` record of the run's key-value store. A run that saved nothing and had failed requests fails, and its last message gives the cause (a company page that does not exist says so, a search with no results too).

If We Work Remotely changes its pages, you are told instead of paying for blank rows. A category page that counts jobs but gives none the Actor can read is an error (listed in `FAILED_REQUESTS`), never a quiet "No jobs found". If the first 20 jobs read all lack the same field — title, company, posting date, description, category or expiry date (the last three are not checked on the cards of search, job type and company pages, which do not show them), or with `extractDetails` the apply link or the company's job count — the run saves nothing more, stops and fails, and its last message names the missing field: at most those first jobs are charged. A job that `postedAfter` / `postedBefore` drops because it has no date at all counts among those 20. A run of fewer jobs (a scheduled `onlyNew` run that finds a few new ones, a small **Max jobs**) is checked at its end, from 5 jobs: its jobs are already saved, and the run fails with the missing field instead of ending green.

### 🛟 Support

Open an issue in the **Issues** tab with a link to your run: the run log and the `FAILED_REQUESTS` record of the key-value store show exactly which URLs failed and why.

# Actor input Schema

## `startUrls` (type: `array`):

We Work Remotely URLs: category pages (`https://weworkremotely.com/categories/remote-design-jobs`) or their RSS feed (`…/remote-design-jobs.rss`), search pages (`https://weworkremotely.com/remote-jobs/search?term=python`), the full-time / contract pages, company pages (`https://weworkremotely.com/company/zapier`) or single job pages (`https://weworkremotely.com/remote-jobs/...`). When this list is not empty, **Categories** is ignored; keywords, filters, caps and monitoring still apply. Max 1 000 URLs.

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

Job categories to read. Empty = all categories (the whole site, a few hundred jobs, read in about 25 requests).

## `query` (type: `string`):

Keep only the jobs whose title, company, skills or description contain this word or phrase (case and accents ignored), e.g. `python` or `customer success`. Empty = every job.

## `searchQueries` (type: `array`):

Several keywords in one run: a job matching ANY of them is kept (once, even if it matches several). Added to **Search keyword**.

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

Keep only the jobs open to this place: matched against the job's region (`USA Only`, `Europe`, `EMEA`…), its countries and the company headquarters, e.g. `Germany`, `Europe`, `United States`. Jobs open to anyone (`Anywhere in the World`) are kept too unless you untick **Include worldwide jobs**. A job limited to a wider area whose countries the listing does not give (`Europe`, `EMEA`) is kept for any place, unless **Extract job page details** reads its exact countries. Empty = everywhere.

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

Several places in one run: a job open to ANY of them is kept. Added to **Location**.

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

With a location filter, also keep the jobs open to anyone in the world. Untick to keep only the jobs that name your place.

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

Keep only the jobs open to applicants anywhere in the world (`Anywhere in the World`, no country restriction). Drops every job limited to a region or a list of countries.

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

Maximum number of jobs to save for the whole run (after deduplication and filters). 0 = no limit.

## `maxItemsPerQuery` (type: `integer`):

Cap for EACH category or start URL, so that the first one cannot use up the whole **Max jobs** budget. 0 = no per-category cap.

## `sortBy` (type: `string`):

Order of the jobs in the dataset, and so which ones **Max jobs** keeps: newest first, oldest first, or highest salary first (ranked in US dollars: the band shown on the site or an amount in USD; a salary in another currency without a band is ranked with the jobs without any salary, last). The order is made inside each category or start URL, not across them; with **Extract job page details** the rows are saved as the pages arrive, so the order inside the dataset is not fixed (the jobs kept are).

## `extractDetails` (type: `boolean`):

Open each job page (1 extra request per job) for: the exact list of countries allowed, the salary the employer entered, the apply link, the company website and address, the number of jobs the company posted, the deadline. Charged as a job with details (see Pricing).

## `includeCompanyProfile` (type: `boolean`):

Open each company's We Work Remotely profile once per run (1 extra request per company) for its website, headquarters, Top 100 status, number of open jobs, description, remote / DEI policy links, benefits and hiring process. Charged per company profile added (see Pricing).

## `includeApplyEmail` (type: `boolean`):

With **Extract job page details**: when the employer gives an application e-mail address on the job page (a few jobs only), decode it and add it to `applyEmail`. Charged per e-mail found (see Pricing).

## `enrichEmails` (type: `boolean`):

Visit the company's own website (home, contact and legal pages, 3 pages max, once per company per run) to find its email addresses, phone numbers and social links. Needs **Extract job page details** or **Add the company profile** (the website comes from them). Charged as a separate event, once per company where an email is found (see Pricing); the phone numbers and social links found on the same pages come with it, and a company where no email is found costs nothing.

## `descriptionMaxLength` (type: `integer`):

Cut the plain-text `description` to this many characters (ending with `…`) to keep the dataset light; `descriptionHtml` is left whole. 0 = the full text (up to 32,000 characters).

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

Keep only these types of job. Empty = all.

## `salaryRanges` (type: `array`):

Keep only the jobs in these yearly salary bands, as shown on the site. Empty = all.

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

Keep only the jobs whose yearly salary can reach this amount in US dollars: the top of the band shown on the site, or the amount written in the description when there is one. Jobs without any salary are kept only if **Include jobs without salary** is ticked. 0 = no minimum.

## `includeNoSalary` (type: `boolean`):

With a salary filter, also keep the jobs that show no salary at all. A job paid in another currency than US dollars and shown without a salary band of the site counts as a job without salary here.

## `maxSalary` (type: `integer`):

Keep only the jobs whose yearly salary starts at or below this amount in US dollars (bottom of the band shown on the site, or the amount written in the description). Jobs without any salary follow **Include jobs without salary**. 0 = no maximum.

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

Keep only the jobs that show a salary: a band on the site, the employer's figure or an amount written in the description.

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

Keep only the jobs of these levels, read from the job title (see the `seniority` output field). Jobs whose title names no level are dropped when this list is not empty. Empty = all.

## `titleKeywords` (type: `array`):

Keep only the jobs whose TITLE contains one of these words (case and accents ignored), e.g. `engineer`, `designer`. Unlike **Search keyword**, the description is not searched.

## `skills` (type: `array`):

Keep only the jobs that list one of these skills (matched against the job's skills, then its title and description), e.g. `React`, `SEO`, `Salesforce`.

## `includeCompanies` (type: `array`):

Keep only the jobs of these companies (name or We Work Remotely company page slug, case ignored).

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

Drop the jobs of these companies (name or company page slug, case ignored), e.g. recruiting agencies.

## `onlyFeatured` (type: `boolean`):

Keep only the jobs the employer paid to feature.

## `onlyTop100` (type: `boolean`):

Keep only the jobs of the companies We Work Remotely lists in its Top 100 remote companies.

## `onlyHot` (type: `boolean`):

Keep only the jobs the site marks as `Hot job`.

## `postedAfter` (type: `string`):

Only jobs posted on or after this date (the posting date of the job feed): `2026-09-01`, or a period before now such as `7 days`, `2 weeks`, `1 month` (API: `24 hours` and full ISO date-times work too). Jobs without a posting date are dropped.

## `postedBefore` (type: `string`):

Only jobs posted on or before this date (the whole day is included), or older than a period such as `30 days`.

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

Drop the jobs whose title contains one of these words (case and accents ignored), e.g. `senior`, `intern`.

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

Skip the jobs that a previous run (same **Memory key**) already delivered: they are not saved and not charged, and their page is not even opened. First run = everything is new.

## `includeClosedJobs` (type: `boolean`):

With **Only new jobs**: also save, once, each job delivered by a previous run (same **Memory key**, with this box ticked) that is no longer on its category page, with `status` = `closed` and the date it disappeared. Each one is charged as a closed job, not as a job. Nothing is reported by a run that stops early, or for a category the run could not read. Works with categories, not with search or company pages.

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

Name of the memory used by **Only new jobs**. Give each schedule / task its own key (e.g. `design-europe`) so that they do not share their memory. Letters, digits, `-` and `_`.

## `resetState` (type: `boolean`):

Forget everything remembered under this **Memory key** before the run: this run returns (and charges) every job again. Untick it afterwards.

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

Apify Proxy or your own proxies. Keep the default: it is included in the price. The residential Apify proxy is not available in this Actor.

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

Maximum number of requests processed in parallel.

## `maxRequestsPerMinute` (type: `integer`):

Most requests to the site in any 60 seconds, all of them counted: category pages, feeds, job pages and company profiles. A budget, not an even pace: up to this many can leave at once when the minute starts (spread them with the minimum delay below). Lower it if the site answers HTTP 429 / 403 in the log.

## `minRequestIntervalMs` (type: `integer`):

Smallest gap between two requests, in milliseconds. Unlike the per-minute rate, this spreads the requests evenly instead of letting them go out in a burst. 0 = no gap.

## `maxRequestRetries` (type: `integer`):

Retries per request before it is marked as failed. Behind a proxy, a request the site turns away is also retried on a new proxy session up to 10 times without using up these retries.

## `debugLog` (type: `boolean`):

Include debug messages in the run log.

## Actor input object example

```json
{
  "startUrls": [],
  "categories": [],
  "searchQueries": [],
  "locations": [],
  "includeWorldwide": true,
  "worldwideOnly": false,
  "maxItems": 50,
  "maxItemsPerQuery": 0,
  "sortBy": "newest",
  "extractDetails": false,
  "includeCompanyProfile": false,
  "includeApplyEmail": false,
  "enrichEmails": false,
  "descriptionMaxLength": 0,
  "jobTypes": [],
  "salaryRanges": [],
  "minSalary": 0,
  "includeNoSalary": true,
  "maxSalary": 0,
  "onlyWithSalary": false,
  "seniority": [],
  "titleKeywords": [],
  "skills": [],
  "includeCompanies": [],
  "excludeCompanies": [],
  "onlyFeatured": false,
  "onlyTop100": false,
  "onlyHot": false,
  "excludeKeywords": [],
  "onlyNew": false,
  "includeClosedJobs": false,
  "stateKey": "default",
  "resetState": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "maxConcurrency": 5,
  "maxRequestsPerMinute": 240,
  "minRequestIntervalMs": 0,
  "maxRequestRetries": 5,
  "debugLog": false
}
```

# Actor output Schema

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

No description

# 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 = {
    "maxItems": 50,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("nice_dev/weworkremotely-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 = {
    "maxItems": 50,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("nice_dev/weworkremotely-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 '{
  "maxItems": 50,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call nice_dev/weworkremotely-jobs-scraper --silent --output-dataset

```

## MCP server setup

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