WeWorkRemotely Scraper - Remote Jobs, Salaries & Companies avatar

WeWorkRemotely Scraper - Remote Jobs, Salaries & Companies

Pricing

from $1.20 / 1,000 results

Go to Apify Store
WeWorkRemotely Scraper - Remote Jobs, Salaries & Companies

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

Abot API

Maintained by Community

Actor stats

0

Bookmarked

4

Total users

0

Monthly active users

6 hours ago

Last modified

Security

Limited permissions

Share

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.

FieldExample
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"
salaryMin140000
salaryMax170000
postedAt / postedAgeDays"2026-09-20T14:05:00Z" / 8
expiresAt / expiresInDays"2026-10-20T14:05:00Z" / 21
isExpiredfalse
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)
descriptionLength3187
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

  1. Pick a mode: keyword (search current listings) or url (read a specific We Work Remotely feed or category URL).
  2. 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.
  3. 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.
  4. 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 ApifyClient
client = 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 (one remote-*-jobs category, keyword mode then needs no search terms) and results_wanted (same as Max items).
  • New output fields: categories, salaryInterval (year, month or hour when the posting says so, otherwise empty), guid and source.

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.

  1. Authorize a connector once under Apify → Settings → Integrations (Notion, Linear, Airtable, or Apify).
  2. Select it in the "Pipe results into your apps" input field. (If the picker is empty, you haven't authorized a connector yet.)
  3. For Notion, also set notionParentPageUrl to 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

ParameterTypeDefaultDescription
modestringkeywordkeyword (search current listings) or url (read specific feed/category URLs).
startUrlsarray5 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.
searchTermsarrayno 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.
categoriesarray["all"]Restrict keyword search to selected categories; leave on All categories to scan all eleven category feeds.
regionsarrayno default (empty; no region filter)Keep listings whose region contains one of these values, such as Anywhere in the World or Europe.
includeDetailsbooleantrueInclude full description HTML/text, parsed sections, and section-derived fields in each row.
maxItemsinteger20Maximum matching job rows to save (1 to 100000); a cap, not a guarantee.
sortstringfeed orderOrder of saved rows: site feed order, newest first, or company A-Z; applied after filters and before Max items.
results_wantedintegerno defaultAlias of maxItems.
search_urlsarrayno defaultAlias that runs URL list mode with the given URLs.
categorystringno defaultOne remote-*-jobs category; keyword mode then needs no search terms.
postedWithinDaysintegerno default (empty; no age filter)Keep only jobs posted within this many days.
requireSalarybooleanfalseKeep only rows where a salary hint was found in the listing text.
requireCompanyWebsitebooleanfalseKeep only rows with a company website URL.
requireApplyUrlbooleanfalseKeep 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.
minDescriptionLengthintegerno 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.
resumeFromRunIdstringno default (empty)Continue ONE specific interrupted run by its run or dataset id; already-collected jobs are skipped.
incrementalModebooleanfalseTurn on for recurring monitoring: later runs return only new and changed jobs.
ignoreFieldsForChangesarrayemptyIncremental 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.
stateKeystringno 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.
emitUnchangedbooleanfalseAlso return (and bill) jobs that have not changed since the last run, marked UNCHANGED.
emitExpiredbooleanfalseAlso return (and bill) jobs that were tracked before and are no longer found, marked EXPIRED. Only produced after a complete, non-resumed scan.
proxyobjectno default (prefill: Apify Proxy on, no groups)Connection settings; uses Apify Proxy with the standard datacenter pool by default.
mcpConnectorsarrayno default (empty; skip export)Optional: send a summary of each saved job to apps you authorized under Integrations.
notionParentPageUrlstringno default (empty)Notion connector only: page under which job pages are created.
maxNotifyListingsinteger50Cap 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.

EventUnitCadence
apify-actor-startOne run startOnce per run
apify-default-dataset-itemOne job row saved to the datasetOnce per row saved
detail-enrichmentOne saved job row with Include full description fields onCharged 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.

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 shapeExampleResult
Main RSS feedhttps://weworkremotely.com/remote-jobs.rssAll current listings
Category RSS feedhttps://weworkremotely.com/categories/remote-design-jobs.rssThat category
Category page, no .rsshttps://weworkremotely.com/categories/remote-programming-jobsSame as the category feed
Other category slugs (devops, full-stack, front-end, back-end)https://weworkremotely.com/categories/remote-devops-sysadmin-jobs.rssThat category
Category page with ?page=2https://weworkremotely.com/categories/remote-programming-jobs?page=2The site has no real pagination, so this is the same feed and is deduplicated
Search pagehttps://weworkremotely.com/remote-jobs/search?term=pythonThe 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 pagehttps://weworkremotely.com/remote-jobs/all or /remote-jobs/all.rssRead as the main RSS feed, because the site serves that page only behind a bot-check page
All other remote jobshttps://weworkremotely.com/categories/all-other-remote-jobs.rssThat category (the remote-all-other-jobs spelling is read as the same category)
Job posting pagehttps://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...

👉 Browse all abotapi scrapers

💬 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.