WeWorkRemotely Scraper - Remote Jobs, Salaries & Companies
Pricing
from $1.20 / 1,000 results
WeWorkRemotely Scraper - Remote Jobs, Salaries & Companies
Extract current remote job listings from weworkremotely.com. Use keyword search or WWR feed, category, and remote-jobs URLs. Returns clean job rows with title, company, category, region, tags, salary text, description matches, and filtered structured output.
Pricing
from $1.20 / 1,000 results
Rating
0.0
(0)
Developer
Abot API
Maintained by CommunityActor stats
0
Bookmarked
4
Total users
0
Monthly active users
6 hours ago
Last modified
Categories
Security
Limited permissionsShare
We Work Remotely Scraper: Remote Job Listings, Salaries & Apply Links
We Work Remotely Scraper turns We Work Remotely into a clean, structured jobs feed. Search current remote openings by keyword across one or all supported categories, or point it at a specific We Work Remotely feed or category URL, and get back company, title, category, region, salary hints, technologies, seniority and full job descriptions for every listing. Run it once for a snapshot, or turn on Incremental mode to get only new and changed jobs on a recurring schedule, then export the dataset to JSON, CSV or Excel, or pull it straight into your app through the API.
Why This Scraper?
- Two ways in. Search current listings by keyword across one or all supported categories, or paste a specific We Work Remotely feed or category URL directly.
- Eleven category feeds, one run. Choosing All categories scans all eleven category feeds (programming, design, customer support, sales and marketing, product, devops and sysadmin, management and finance, full-stack, front-end, back-end and all other remote jobs) and deduplicates jobs by listing URL, for broader coverage than the main feed alone.
- Filter before you pay. Keep only listings that match a region, a posting age window, a salary hint, a company website, or a minimum description length.
- Full descriptions on demand. Turn on Include full description fields for cleaned description text and parsed sections (About the company, Responsibilities, Requirements, Benefits); extracted technologies and an inferred seniority level are always computed regardless of this toggle.
- Built for schedules. Incremental mode tracks one search over time and returns only new and changed jobs on later runs, so a daily monitor never re-bills an unchanged posting.
- Resume a run that hit its cap. Continue one earlier run that finished but stopped at Max items before reaching every feed, by its run or dataset id; jobs it already collected are skipped rather than re-scraped and re-billed.
- Optional MCP connector export. Send a compact summary of saved jobs into Notion, Linear, Airtable or Apify after the dataset is written, without changing what stays in the Apify dataset.
Use Cases
- Recruiting and talent sourcing: run a keyword search across every category to build a daily shortlist of remote openings to reach out against.
- Lead generation and market tracking: watch which companies are hiring remotely as a signal for sales or investment research.
- Job board aggregation: mirror We Work Remotely postings, with normalized fields, into your own site, newsletter or app.
- Compensation and skills research: collect salary hints, seniority levels and extracted technologies across categories to study pay and skill trends.
- Job alert automation: schedule a keyword search with Incremental mode and forward only newly posted jobs to Slack, email or a chat bot.
Data You Get
Sample shape: values are illustrative placeholders, not from a live record.
| Field | Example |
|---|---|
jobId | "3f9a1c2b7e0d4a58" |
title | "Senior Software Engineer, Backend (Python)" |
companyName | "Nimbus Robotics" |
category | "Back-End Programming" |
region | "Anywhere in the World" |
jobType | "Full-Time" |
seniorityLevel | "Senior" |
technologies | ["Python", "AWS", "PostgreSQL"] |
skills | ["Python", "Django", "AWS"] |
salaryText | "$140,000 - $170,000" |
salaryMin | 140000 |
salaryMax | 170000 |
postedAt / postedAgeDays | "2026-09-20T14:05:00Z" / 8 |
expiresAt / expiresInDays | "2026-10-20T14:05:00Z" / 21 |
isExpired | false |
applyUrl / applyUrlIsExternal | "https://careers.nimbusrobotics.example/jobs/1234" / true |
companyWebsiteUrl | "https://nimbusrobotics.example" |
canonicalUrl | "https://weworkremotely.com/remote-jobs/nimbus-robotics-senior-backend-engineer-example" |
descriptionText | "We're looking for a senior backend engineer to..." (full text when Include full description fields is on) |
descriptionLength | 3187 |
descriptionSections | {"About the company": "...", "Responsibilities": "..."} (when Include full description fields is on) |
externalLinks / externalLinkCount | ["https://nimbusrobotics.example", "https://careers.nimbusrobotics.example/jobs/1234"] / 2 |
sourceType / searchKeyword | "rss" / "python" |
scrapedAt | "2026-09-29T08:00:00Z" |
changeType / changedFields | "NEW" / [] (Incremental mode only) |
Each row also carries jobSlug, rawTitle, listingDomain, regionSlug, categorySlug, applicationUrl (a duplicate of applyUrl), externalApplyUrl and applyUrlDomain (set only when the apply link points off We Work Remotely), companyWebsiteDomain, companyLogoUrl, headquarters, locationText, isRemote, isWorldwide, employmentType (a duplicate of jobType), country, state, skillsRaw, skillsCount, skillsSource, salaryCurrency, hasSalary, hasCompanyWebsite, sectionHeadings, aboutCompanyText, responsibilitiesText, requirementsText, benefitsText, sourceUrl, rowType (always "job"), and, in Incremental mode only, firstSeenAt and lastSeenAt.
How to Use
- Pick a mode:
keyword(search current listings) orurl(read a specific We Work Remotely feed or category URL). - In keyword mode, enter your search terms and choose one or more categories, or leave it on All categories for the broadest coverage. In URL mode, paste one or more feed or category URLs.
- Add any filters you need (region, posting age, salary, company website, description length) and switch on Include full description fields if you want the full text.
- Set Max items to control run size and cost, then click Start.
Keyword search across all categories:
{"mode": "keyword","searchTerms": ["python"],"categories": ["all"],"includeDetails": true,"maxItems": 20}
Read a specific category feed URL:
{"mode": "url","startUrls": [{ "url": "https://weworkremotely.com/categories/remote-design-jobs.rss" }],"includeDetails": false,"maxItems": 15}
Filtered keyword search, engineering roles with a salary and a company site:
{"mode": "keyword","searchTerms": ["engineer"],"categories": ["programming", "back-end-programming"],"regions": ["Anywhere in the World"],"requireSalary": true,"requireCompanyWebsite": true,"maxItems": 25}
Scheduled monitor, only new and changed design jobs:
{"mode": "keyword","searchTerms": ["designer"],"categories": ["all"],"incrementalMode": true,"stateKey": "designer-watch","emitExpired": true,"maxItems": 100000}
Run it from your code
Python:
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("abotapi/we-work-remotely-jobs-scraper").call(run_input={"mode": "keyword", "searchTerms": ["python"], "categories": ["all"]})for job in client.dataset(run["defaultDatasetId"]).iterate_items():print(job["title"], job["companyName"])
JavaScript:
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });const run = await client.actor('abotapi/we-work-remotely-jobs-scraper').call({ mode: 'keyword', searchTerms: ['python'], categories: ['all'] });const { items } = await client.dataset(run.defaultDatasetId).listItems();
Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.
How Max items shapes a run
Max items caps the number of matching rows saved; it does not create more jobs than the selected categories or URLs currently have listed. All categories walks up to eleven category feeds one at a time, and the run stops as soon as it has saved Max items rows, so later feeds in the walk may never be fetched. There is no unlimited setting: Max items must be between 1 and 100000. An Incremental run that needs a complete scan (to allow EXPIRED detection, see below) should set Max items high enough that no requested feed is left unfetched.
Sorting, categories and alternative input names
- Sort by (
sort): site feed order (default), newest first, or company A-Z. It is applied after the filters and before Max items, so "newest 10" really are the 10 newest matches. With incremental mode on, the sort only orders the rows returned in that run. - Categories now cover the Front-End, Back-End and Full-Stack Programming feeds on their own (each reads its own feed) and "All other remote jobs".
- Alternative input names are accepted:
search_urls(runs in URL list mode),category(oneremote-*-jobscategory, keyword mode then needs no search terms) andresults_wanted(same as Max items). - New output fields:
categories,salaryInterval(year, month or hour when the posting says so, otherwise empty),guidandsource.
Resume and recurring updates
Resume from a previous run (resumeFromRunId) continues ONE specific earlier run that finished but stopped at its Max items cap before reaching every feed: paste its run or dataset id and jobs it already collected (matched by jobId) are skipped, so you only pay for the remainder. Since jobs are only pushed once every feed has been walked, a run that crashed or was aborted mid-way has nothing saved to resume from. An id this account cannot read fails the run immediately instead of silently re-scraping everything.
Incremental mode (incrementalMode) is for running the same search again and again on a schedule. Each job is classified NEW, UPDATED (with changedFields), or UNCHANGED (suppressed and not billed unless emitUnchanged is on). EXPIRED is produced only after a run that fetched and fully scanned every requested feed, never after a run that Max items capped mid-walk and never after a run that used Resume from a previous run. REAPPEARED only ever fires for a job that a prior complete, non-resumed scan (with emitExpired on) already marked EXPIRED; a search whose baseline has no EXPIRED job yet will never produce a REAPPEARED row. stateKey names or shares the stored state; leave it empty and the actor derives one from your mode, search terms or URLs, and every filter, so two different setups never mix state. A job is compared on every output field except scrapedAt, postedAgeDays, expiresInDays and isExpired, which change with the clock and are always excluded (they stay in the output). ignoreFieldsForChanges adds more fields to leave out of the comparison and changedFields; changing that list never produces a wave of UPDATED rows, because the stored snapshot is re-compared with the current list. Two one-time effects apply to existing Incremental setups. First, All categories now also reads the full-stack, front-end, back-end and all-other feeds, so jobs that only those feeds list appear once as NEW (they were never visible before); jobs you already track are not touched. Second, the front-end, back-end and full-stack categories now read their own feeds instead of the shared programming feed, and the two sets of jobs do not overlap. A saved state for a run that selects any of those three categories is therefore not reused: that run starts a fresh baseline (everything NEW once) instead of marking every previously tracked job EXPIRED. Runs that select none of those three categories keep their saved state. New output fields (guid, source, categories, salaryInterval) never turn an already tracked, unchanged job into UPDATED. A keyword run with no search term and no category (for example a default run with only Max items set) searches the example term python so it still returns jobs; that example run never saves Incremental mode state and never marks anything EXPIRED, so a later run with your own terms starts clean. With Incremental mode off, output is exactly as before.
Send results into your apps (MCP connectors)
Optionally pipe the scraped jobs into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step after the scrape: the Apify dataset is never changed.
What gets written to the connector: a condensed, human-readable summary of each job, not the full JSON. Each job becomes one entry with a title and its key fields flattened to plain text. The complete record always stays in the Apify dataset.
- Authorize a connector once under Apify → Settings → Integrations (Notion, Linear, Airtable, or Apify).
- Select it in the "Pipe results into your apps" input field. (If the picker is empty, you haven't authorized a connector yet.)
- For Notion, also set
notionParentPageUrlto the page where job pages should be created.
The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.
Input Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
mode | string | keyword | keyword (search current listings) or url (read specific feed/category URLs). |
startUrls | array | 5 example URLs: the main feed, two category .rss feeds, one category page without .rss and one category HTML page (same as the prefill) | Feed, category, category RSS, search or job posting URLs to read in url mode. |
searchTerms | array | no default (prefill: ["python"]) | Terms matched against title, company, category, region, tags and description text (keyword mode). Left empty with no category chosen, the run searches the example term python instead of stopping. |
categories | array | ["all"] | Restrict keyword search to selected categories; leave on All categories to scan all eleven category feeds. |
regions | array | no default (empty; no region filter) | Keep listings whose region contains one of these values, such as Anywhere in the World or Europe. |
includeDetails | boolean | true | Include full description HTML/text, parsed sections, and section-derived fields in each row. |
maxItems | integer | 20 | Maximum matching job rows to save (1 to 100000); a cap, not a guarantee. |
sort | string | feed order | Order of saved rows: site feed order, newest first, or company A-Z; applied after filters and before Max items. |
results_wanted | integer | no default | Alias of maxItems. |
search_urls | array | no default | Alias that runs URL list mode with the given URLs. |
category | string | no default | One remote-*-jobs category; keyword mode then needs no search terms. |
postedWithinDays | integer | no default (empty; no age filter) | Keep only jobs posted within this many days. |
requireSalary | boolean | false | Keep only rows where a salary hint was found in the listing text. |
requireCompanyWebsite | boolean | false | Keep only rows with a company website URL. |
requireApplyUrl | boolean | false | Keep only rows with an apply URL. Every row already carries one, since it falls back to the We Work Remotely listing link when no separate apply link is found, so this filter currently changes nothing. |
minDescriptionLength | integer | no default (empty; no length filter) | Keep only rows whose full cleaned description text has at least this many characters, measured on the complete text even when includeDetails is off. |
resumeFromRunId | string | no default (empty) | Continue ONE specific interrupted run by its run or dataset id; already-collected jobs are skipped. |
incrementalMode | boolean | false | Turn on for recurring monitoring: later runs return only new and changed jobs. |
ignoreFieldsForChanges | array | empty | Incremental mode only: field names or dotted paths whose changes are not counted as an update and are left out of changedFields. The fields stay in the output. |
stateKey | string | no default (empty; auto-derived from mode/search/filters) | Name or share an Incremental mode monitoring campaign. With Incremental mode on and your own State key, keyword mode needs a search term or a category: with neither, the run stops with an input message and collects nothing. |
emitUnchanged | boolean | false | Also return (and bill) jobs that have not changed since the last run, marked UNCHANGED. |
emitExpired | boolean | false | Also return (and bill) jobs that were tracked before and are no longer found, marked EXPIRED. Only produced after a complete, non-resumed scan. |
proxy | object | no default (prefill: Apify Proxy on, no groups) | Connection settings; uses Apify Proxy with the standard datacenter pool by default. |
mcpConnectors | array | no default (empty; skip export) | Optional: send a summary of each saved job to apps you authorized under Integrations. |
notionParentPageUrl | string | no default (empty) | Notion connector only: page under which job pages are created. |
maxNotifyListings | integer | 50 | Cap on how many jobs are written to each selected connector per run. |
Output Example
Sample shape: values are illustrative placeholders, not from a live record.
{"rowType": "job","jobId": "3f9a1c2b7e0d4a58","jobSlug": "nimbus-robotics-senior-backend-engineer-example","title": "Senior Software Engineer, Backend (Python)","rawTitle": "Nimbus Robotics: Senior Software Engineer, Backend (Python)","companyName": "Nimbus Robotics","url": "https://weworkremotely.com/remote-jobs/nimbus-robotics-senior-backend-engineer-example","canonicalUrl": "https://weworkremotely.com/remote-jobs/nimbus-robotics-senior-backend-engineer-example","applyUrl": "https://careers.nimbusrobotics.example/jobs/1234","applyUrlIsExternal": true,"companyWebsiteUrl": "https://nimbusrobotics.example","region": "Anywhere in the World","category": "Back-End Programming","jobType": "Full-Time","skills": ["Python", "Django", "AWS"],"technologies": ["Python", "AWS", "PostgreSQL"],"seniorityLevel": "Senior","salaryText": "$140,000 - $170,000","salaryMin": 140000,"salaryMax": 170000,"salaryCurrency": "USD","postedAt": "2026-09-20T14:05:00Z","postedAgeDays": 8,"expiresAt": "2026-10-20T14:05:00Z","expiresInDays": 21,"isExpired": false,"descriptionText": "We're looking for a senior backend engineer to own our core payments API...","descriptionLength": 3187,"descriptionSections": { "About the company": "Nimbus Robotics builds...", "Responsibilities": "Own our core payments API..." },"externalLinks": ["https://nimbusrobotics.example", "https://careers.nimbusrobotics.example/jobs/1234"],"sourceType": "rss","searchKeyword": "python","scrapedAt": "2026-09-29T08:00:00Z"}
Plan Requirement
This actor reads the public We Work Remotely RSS feeds, and those feeds load through the standard Apify datacenter proxy. A paid plan is not required for the datacenter route and no residential proxy is needed. The website's HTML pages (home, listing, search and job detail pages) sit behind a bot-check page that the actor does not try to get past, so it never fetches detail HTML pages: everything in a row, including the full description, comes from the feed data. A proxy country you choose in the proxy settings is applied to whichever proxy group you use. If the default connection is unavailable, the actor retries over a backup connection and logs: "Served over the backup connection (the default connection was unavailable)."
Billing. Pay-per-event pricing with three events; the Pricing tab on the actor page shows the current rates.
| Event | Unit | Cadence |
|---|---|---|
apify-actor-start | One run start | Once per run |
apify-default-dataset-item | One job row saved to the dataset | Once per row saved |
detail-enrichment | One saved job row with Include full description fields on | Charged once after the rows are saved, with a count equal to the number of rows saved in that run; no extra requests are made |
Rows that Incremental mode suppresses as unchanged are not saved and not billed (unless Emit unchanged is on). Use Max items to cap the size and cost of a run.
FAQ
How much does it cost?
You pay a small one-time fee when a run starts, a per-row fee for every job returned, and an extra per-row fee only when Include full description fields is switched on. The Pricing tab shows the current rates. Use Max items to cap the size, and cost, of any run.
Is it legal to scrape We Work Remotely?
This actor collects only publicly available job listing data. You are responsible for how you use it: follow We Work Remotely's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution.
Can I get only new or changed jobs on a schedule?
Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. Each run then returns only new and updated jobs by default; jobs that have not changed are suppressed and not billed unless you turn on Emit unchanged, and expired jobs only appear once you also turn on Emit expired, with reappeared jobs only possible after that on a later run.
Which URLs work in URL mode?
Run in URL mode with the Start URLs field untouched and the actor reads five example URLs (the main RSS feed, the design and devops category feeds, the programming category page without .rss, and the full-stack category page). Replace them with your own URLs to narrow the run.
| URL shape | Example | Result |
|---|---|---|
| Main RSS feed | https://weworkremotely.com/remote-jobs.rss | All current listings |
| Category RSS feed | https://weworkremotely.com/categories/remote-design-jobs.rss | That category |
| Category page, no .rss | https://weworkremotely.com/categories/remote-programming-jobs | Same as the category feed |
| Other category slugs (devops, full-stack, front-end, back-end) | https://weworkremotely.com/categories/remote-devops-sysadmin-jobs.rss | That category |
Category page with ?page=2 | https://weworkremotely.com/categories/remote-programming-jobs?page=2 | The site has no real pagination, so this is the same feed and is deduplicated |
| Search page | https://weworkremotely.com/remote-jobs/search?term=python | The term becomes a keyword filter over every feed (all category feeds and the main feed); a warning is logged if no listed job matches it |
| All jobs page | https://weworkremotely.com/remote-jobs/all or /remote-jobs/all.rss | Read as the main RSS feed, because the site serves that page only behind a bot-check page |
| All other remote jobs | https://weworkremotely.com/categories/all-other-remote-jobs.rss | That category (the remote-all-other-jobs spelling is read as the same category) |
| Job posting page | https://weworkremotely.com/remote-jobs/<job-slug> | Matched by its slug across every feed (all category feeds and the main feed), since the detail page itself is not fetched. If the job is no longer listed in any feed, nothing is returned for it and the log carries a warning naming that URL |
Any other URL is skipped with a warning in the log. The run only stops if none of the URLs is usable.
Why did my run fail instead of returning an empty dataset?
When no requested feed can be loaded at all, the run fails with one of four messages instead of ending with an empty dataset:
- "The proxy connection was refused for this account, so no page could be read. Your Apify plan does not include proxy access; a paid plan is required: https://apify.com/pricing. If you entered your own proxy URLs, check that their credentials are valid."
- "No results: residential proxies are unavailable on this run (Apify free plans do not include them), and the backup connection could not load the requested We Work Remotely pages. Upgrade at https://apify.com/pricing or supply your own proxy URLs, then try again."
- "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search. Please try again later."
- "None of the given pages could be read as a We Work Remotely feed: the site refused every one of them (an error status or a bot-check page). This is not a connection problem. Use the main feed, a category feed or a category page URL instead."
Each feed has a 25 second deadline and at most three attempts (with a short pause between them, plus one extra attempt after the connection switches to the backup), and a bot-check page (HTML instead of a feed) counts as a refused URL, not as an empty result. Resume from a previous run also fails cleanly if it points at an id this account cannot read, or if it is combined with Incremental mode on a search that already has saved state. If your search or filters simply match no current listings, the run completes normally with an empty dataset.
What do the output views show?
The dataset has three views: Overview (the main job columns), Changes (change type and changed fields, for Incremental mode) and Detail (descriptions, sections and links). The same rows are available through the API and every export format.
Can I use it with AI agents or MCP?
Yes. Call it from any Apify integration or MCP client, and use the connector field to push a summary of each saved job into Notion, Linear or Airtable.
🔗 Want more jobs data?
Pair this actor with these related scrapers from the same team:
| 💼 RemoteOK Jobs Scraper Scrape RemoteOK jobs by keyword or job URL. Extract title, company, location, remote... | 💼 Jora Jobs Scraper Scrape Jora.com job listings across supported countries by keyword, filters, or URL... |
| 💼 Jobs.cz Scrape Czech job listings from Jobs.cz. Search builder (keyword, location, field, salary... | 💼 Wellfound Jobs Scraper Scrape startup jobs from Wellfound.com by keyword, location, role, or remote status... |
| 💼 Hiring.Cafe Scraper Scrape Hiring.Cafe jobs from $1/1K results. Search with advanced filters or URLs and... | 💼 Workday Jobs Scraper Scrape job postings from any Workday-hosted careers site on Myworkdayjobs.com. Paste... |
💬 Support
- 🐞 Found a bug or a missing field? Open a ticket on the Issues tab. We usually reply within hours.
- 🛠️ Need another site, extra fields or a private build? Email contact@abotapi.com.
- ⭐ Enjoying it? A quick review on the actor page helps other users find it.