Remote and work-from-home job listings from We Work Remotely, Remote OK and Python.org Jobs in one clean format, with salary, employment type and categories. Filter by keyword, region, category or date, merge duplicates across boards, or get only jobs new since your last run. Links to each source.
Versions follow MAJOR.MINOR.PATCH (src/version.py); Apify shows MAJOR.MINOR from .actor/actor.json.
Every run logs its version and records it in the RUN_STATS key-value record.
1.2.6 (2026-10-16)
A run that returns nothing (every input bad, private, blocked or unreachable, or an empty input) now ends
SUCCEEDED with an empty dataset and the reason as its status message and in the log, instead of FAILED. Only an
unexpected error still fails the run. RUN_STATS keeps the reason as before.
No change to output fields or price.
1.2.5 (2026-10-03)
A run no longer fails when the store it remembers earlier runs in can't be opened (403: the account already has
a store of that name that this actor didn't create). It falls back to humble-echidna-<name>, and if that is
refused too it runs without memory for that run. Users' existing memory is untouched: the usual name is tried first.
1.2.4 (2026-10-01)
Waits out longer Retry-After answers: when a site or API asks it to wait before retrying, it now waits up
to 90 seconds (was 30), once per site, with at most 3 minutes of such long waits per run; a longer wait, or a
site that asks again after it was waited out, is reported with the reason instead of stalling the run.
A near-empty page that only shows Cloudflare Turnstile's check is reported as a bot check (blocked), like the
other challenge pages.
No change to input, output or price.
1.2.3 (2026-09-28)
2 ready-to-run examples published on Apify Store and linked from the README.
1.2.2 (2026-09-28)
Cheaper on paid Apify plans: $1.80 per 1,000 jobs on Starter, $1.60 on Scale and $1.40 on Business ($2.00 on the Free plan).
1.2.1 (2026-09-26)
Max jobs per run now defaults to 100 for API and agent calls that leave it out (was: no limit, several
hundred jobs). Set it higher, e.g. 1000, for every matching job.
1.2.0 (2026-09-26)
New board: Python.org Jobs (pythonorg, on by default), the Python Software Foundation's job board. Only its
remote postings are returned (the location line or title says remote, anywhere, worldwide or telecommute); on-site
postings are left out and counted in RUN_STATS (queries.pythonorg.notes). One request per run.
Feed: https://www.python.org/jobs/feed/rss/, the RSS feed linked from https://www.python.org/jobs/ (the 20
newest postings; no paging). robots.txt (read 2026-09-26) disallows only /~guido/orlijn/ and /webstats/.
Terms (https://www.python.org/about/legal/ , "Third-Party Content", read 2026-09-26): "third party content
providers grant the PSF and all other users of the web site an irrevocable, worldwide, royalty-free, nonexclusive
license to reproduce, distribute, transmit, display, perform, and publish such content, including in digital
form." Job postings are the employers' content; nothing else from python.org is taken.
The feed carries no dates, so postedAt is null for these jobs (they pass "Posted within (days)" and sort
last), no employment type and no categories: every job is tagged python, categories come from its title.
Other boards were checked and left out on their terms, robots.txt, a required key, or no feed (Working Nomads,
NoDesk, Remote First Jobs, Landing.jobs, EU Remote Jobs, Remote Rocketship, Web3.career, Crypto Jobs List, The
Muse, Jobspresso, Remote Woman and more); 4 Day Week is on hold (its API licence forbids populating a competing job
board). The audit, with the quotes, is in the repo's notes/REMOTE-BOARDS-2026-09-26.md.
1.1.1 (2026-09-25)
Input field descriptions rewritten for AI agents (Apify's MCP server shows agents only the description, not the
form): each now states its default, its allowed range and how it combines with other fields. No change to field
names, types, defaults or behaviour.
maxResults says an API or agent call without it gets every matching job (several hundred with the other defaults).
1.1.0 (2026-09-25)
Clean locations and countries.country was We Work Remotely's raw country field, flag emojis and long ISO
names included ("🇬🇪 Georgia" for Iterable, headquartered in Atlanta; "🇺🇸 United States of America"), and
location joined all three of its fields, so nearly every listing read "Anywhere in the World, ...". Remote OK
jobs never had a country. Now (measured on the default input, 349 jobs, 2026-09-25: 0 flags, 0 "Anywhere in the
World" prefixes, 109 jobs with a country):
We Work Remotely's region ("Anywhere in the World", "USA Only", "North America Only") is the location only
when its state and country fields don't narrow it; "Anywhere in the World" alone becomes Worldwide,
" Only" becomes X. Those two fields are read as where candidates may be, not the company's headquarters
(a Danish company listing "Texas"; a New York one listing "Delaware" and 67 countries), so they are the job's
location: a US state gets ", United States", other places are listed one by one.
Countries come from the flags (each flag spells the ISO code), so names with "and" in them survive, and are
shown by their short names from a table generated once from ISO 3166-1 (pycountry 26.2.16) and kept as data
in src/countries.py (no new dependency).
Georgia: read as the US state when the listing names no country other than Georgia and the US and anything
in it points to the US (the US, a US state, or the description's "Headquarters:" line: Atlanta, ", GA", "USA",
"US"); otherwise the country. All 4 such listings on 2026-09-25 (Iterable, Celonis, Veeam, Xometry) had US
headquarters lines; a Balkan recruiter listing Georgia among 8 countries keeps the country.
Remote OK: repeated and empty parts removed ("Austin, Austin, Texas, United States" to "Austin, Texas,
United States", "Texas, " to "Texas"); country from the last part when it names a country or a US state
("Remote - US", "Remote UK", "Mexico City, Mexico - Remote"); a bare "Georgia" stays unknown.
country is one country or null: several countries, a region or a city with no country give null.
The location filter also finds a country by its short form (USA finds "United States", UK finds "United
Kingdom"). A We Work Remotely job that says "Anywhere in the World" but lists countries now only matches those
countries (it used to match every filter as a worldwide job).
Text garbled upstream is also repaired when it starts with Ä or Å ("IslÄmÄbÄd" to "Islāmābād").
1.0.0 (2026-09-24)
First release.
Remote jobs from We Work Remotely (category RSS feeds) and Remote OK (public API), in the same output format as
Company Career Page Jobs Scraper (ats-jobs), plus source, sourceUrl, tags, categories and
alsoListedOn. Same output schema and dataset view as ats-jobs.
Each board's reuse conditions are kept: every record has its board's name and the original listing URL; no
logos; Remote OK's anti-spam line is passed on unchanged in description. All requests go through the shared
mms_common client: honest identifying User-Agent, robots.txt per RFC 9309 with Crawl-delay (Remote OK asks for
1 s), and the private-network guard.
Filters: keywords (title or tags), exclude keywords (title), locations/regions (worldwide jobs always match),
normalized categories, posted within N days. Listings dated in the future get the run's time.
Duplicates across boards (same company + title) merged into the most complete listing; the others stay
credited in alsoListedOn.
"Only jobs new since my last run", remembered per board and per filter combination in a named store in the
user's account (record keys use Apify's key charset). A job counts as returned only once its dataset push went
through; a board that was only partly read (a failed feed, a listing that didn't parse) forgets nothing.
Salary from the boards' own fields (with plausibility checks) or from the job text; normalized employment type.
Remote OK's double-encoded text (e.g. "Immunixâ¢") is repaired.
Charged per job returned, through Apify's standard apify-default-dataset-item event. "Max jobs per run"
(pre-filled with 50, no default, newest first) and the maximum cost per run are honoured; a max cost below one
job fetches nothing.
Failure isolation: a board that's down, a failing category feed, or a record that doesn't parse only affects
itself. RUN_STATS reports each board (queries), whole failures (queriesFailed) and partial ones
(queriesPartial, with failedFeeds/failedPages and skippedRecords), and the status message names them.
RUN_STATS is written on every exit path, including an unreadable input.