# Changelog of Multi-ATS Jobs Feed: Greenhouse, Lever & Ashby Jobs API (`hardwoodlogic/multi-ats-jobs-feed`) Actor

- **URL**: https://apify.com/hardwoodlogic/multi-ats-jobs-feed/changelog.md
- **Full Actor documentation**: https://apify.com/hardwoodlogic/multi-ats-jobs-feed.md

## Changelog

All notable changes to the **Multi-ATS Jobs Feed** Actor. Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/);
versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html). The Apify build version (`.actor/actor.json`)
tracks `MAJOR.MINOR`.

### \[Unreleased]

#### Added

- README section on the persistent `multi-ats-jobs-feed-state` store: posting ids only, kept in the Apify account, and how to delete or reset it.
- Startup logs `Source commit: <sha>` from `BUILD_INFO.json` when the run is a packaged upload build. A source checkout logs that it is not packaged.

#### Changed

- The Docker image installs production dependencies with `npm ci` from the monorepo lockfile. The Dockerfile copies every workspace `package.json` so that install matches the lockfile.

#### Security

- Careers-page fetches (and every other request through the shared HTTP client) reject non-http(s) URLs, embedded credentials, and destinations that resolve to loopback, private, link-local, shared, benchmarking, multicast or reserved addresses, including after a redirect. IPv6 `::1`, `fc00::/7`, `fe80::/10`, IPv4-mapped and IPv4-translated forms, NAT64 and 6to4 are included. Decimal, octal and hexadecimal IP literals are rejected. The client connects only to an address it checked, and tries the next checked address when one is unreachable. A missing socket address fails closed. Response bodies are capped at 32 MB. The request timeout covers DNS. Invalid JSON errors do not quote the body.
- When every board fails with "Blocked non-public address", the run error says so and names fake-IP VPNs and proxies (`198.18.0.0/15`).
- A slug-only board uses the preset display name when that board is in a preset (`Jam City`, `thatgamecompany`). Otherwise it uses the name the API returns, then a readable form of the slug.
- A URL that contains an email in a description snippet is replaced with one `[link removed]` placeholder.
- A public IPv6 address is not blocked only because a hextet is `ffff`. Mapped and translated forms are still judged by the embedded IPv4 address. `2606:4700:ffff::1` and `2001:4860:4860::ffff` are allowed.

### \[0.2.2] - 2026-10-27

#### Added

- Actor output schema (`.actor/output_schema.json`), referenced from `actor.json` as `output`, so Apify Console can publish the Actor. It links the default dataset (overview table) and the `OUTPUT` run summary.
- A test that every field on a normalized fixture posting is listed in `.actor/dataset_schema.json`, and that the schema does not declare a field the normalizers do not push. Nullable fields stay nullable.

### \[0.2.1] - 2026-10-27

#### Fixed

- Removed the input-schema `sources` default. Apify merges schema defaults into every run, so `{"boards":[{"ats":"lever","slug":"jamcity"}]}` also fetched Riot Games, Kabam and thatgamecompany. The console prefill is unchanged. An input of `{}` still uses those three boards in code. Sending only `boards` or only a preset does not add them.
- `lever.co` hosts wait at least 1 second between requests. `api.lever.co`, `api.eu.lever.co` and `jobs.eu.lever.co` publish `Crawl-delay: 1`. `minRequestIntervalMs` can only raise that floor. Greenhouse and Ashby stay on the configured gap (default 250 ms); their robots.txt files set no crawl-delay.
- Nordeus `applyUrl` and `postingUrl` are `https://job-boards.greenhouse.io/embed/job_app?for=nordeus&token=ID`. The employer URL `http://www.nordeus.com/careers?gh_jid=…` redirects to `https://www.nordeus.com`, whose certificate does not cover `www`. `job-boards.greenhouse.io/nordeus/jobs/ID` redirects to that same host, so it is not used. Any Greenhouse `absolute_url` that is not https, or whose host is `nordeus.com`, uses the embed. https employer URLs are unchanged.
- `npm run verify-presets` waits 30 seconds for a board page or apply URL and retries that request once. The Jam City board page exceeded the old 12 second limit.
- Description snippets also remove word-form emails (`jane at studio dot com`), 7-digit phone numbers (`555-1234`), and Firstname Lastname pairs near recruiter, hiring, manager, team-lead, a sign-off, or "reach out to" / "talk to" / "ask". Snippets stay off by default. The scrub is best-effort.

#### Changed

- Pricing text no longer says platform or compute cost is charged on top of the per-result price. Under pay-per-event, Apify charges only the event price.
- Docs no longer say 4 requests per second. They describe the real per-host gaps above.
- The game-studios verification note matches what `verify-presets` checks. A 401/403 on an employer https site (Epic Games can do this) is a warning, not a claim that every apply URL returned 200.

### \[0.2.0] - 2026-10-27

#### Fixed

- Ashby hybrid and onsite jobs are no longer `remote: true` just because `isRemote` is true (53 game-studio rows, and 139 on the `ramp` board). When `workplaceType` is known, `remote` is true only for `remote`. `isRemote` is used only when `workplaceType` is missing. Greenhouse already infers workplace from text and sets `remote` from that. Lever already follows `workplaceType` and does not let location text override hybrid or onsite.
- An unparseable `postedSince` fails the run with the parse message (for example `Could not parse postedSince "last tuesday"`) instead of `Unexpected error`.
- A `sources` value that is one string is treated as one source. It is no longer iterated character by character.
- Careers-page scans use an 8 second timeout and one retry. API requests keep their own timeout and retry settings, so a host that resets the connection (careers.roblox.com does this for non-browser clients) cannot stall the run for about 150 seconds.

#### Changed

- Removed Supercell from the game-studios preset and from the `sources` prefill. `jobs.ashbyhq.com/supercell` and its apply URLs return 404; the company's postings are on supercell.com. The sample source is `ashby:thatgamecompany`. Also removed Theorycraft Games: `jobs.lever.co/theorycraftgames` and its apply URL return 404. The preset is 21 boards.
- `sources` now has a default (Riot Games, Kabam, thatgamecompany), so an input of `{}` fetches those boards instead of failing with "No valid job boards".
- `npm run verify-presets` also requests each board's ATS-hosted page and a sample apply URL. A 404 fails the board. A 401/403 or dropped connection on the employer's own domain is a warning.
- README: Ashby `applyUrl` is the ATS-hosted posting, not the employer's site. Location filters are plain text (22 Montreal/Vancouver rows do not contain "Canada"; 88 remote rows do not contain "Remote"). Platform cost is about $0.005 per 1,000 rows, mostly dataset writes.
- Pay-per-event note no longer mentions `npm run start:ppe`. Local charging is simulated with `apify run` and `ACTOR_TEST_PAY_PER_EVENT`.

#### Added

- README section on deploying from the GitHub monorepo so the Docker build includes `shared/`.

### \[0.1.0] - 2026-10-27

#### Added

- Sources: Greenhouse Job Board API, Lever Postings API (global + EU host fallback), Ashby public job posting API.
- Input: board URLs, job URLs, careers pages that embed a board, `ats:slug`, bare slugs (auto-probed), explicit `boards` JSON.
- "Game studios" preset: 23 boards verified live on 2026-10-27 (`npm run verify-presets`).
- Filters: keywords (title/department/team), exclude keywords, location, remote only, posted since (date or span).
- Normalized output: company, ATS, title, department, team, employment type, locations, remote flag + workplace type,
  salary min/max/currency/period (only when the employer publishes it), ISO posted date, employer apply URL, source job id.
- Optional description snippet (plain text, 500 chars, emails/phones/handles/profile links/contact names/URLs removed).
- Delta mode (`onlyNew`) with a named key-value store, so scheduled runs only return and charge for new postings.
- Retries with exponential backoff + jitter on 429/5xx/timeouts/network errors, `Retry-After`, per-host concurrency and
  minimum request interval; bad slugs are reported and skipped.
- De-duplication of inputs, job ids and identical re-posts (optional).
- Run summary in the `OUTPUT` key-value record; dataset schema with "Overview" and "With salary" views.
- Pay-per-event config (`result` = $0.0015) in `.actor/pay_per_event.json` (not applied on Apify yet).
