# Changelog of Remote Jobs Scraper: We Work Remotely, RemoteOK, Python.org (`humble-echidna/remote-jobs`) Actor

- **URL**: https://apify.com/humble-echidna/remote-jobs/changelog.md
- **Full Actor documentation**: https://apify.com/humble-echidna/remote-jobs.md

## Changelog

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`,
    "<X> 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.
