# Changelog of ATS Jobs Scraper: Greenhouse, Lever, Ashby (`succinct_glider/ats-job-boards`) Actor

- **URL**: https://apify.com/succinct\_glider/ats-job-boards/changelog.md
- **Full Actor documentation**: https://apify.com/succinct\_glider/ats-job-boards.md

## Changelog

All notable changes to this Actor are documented here. The output fields are a contract: no field is
renamed, removed or retyped outside a major version.

### \[Unreleased]

#### Store page (2026-09-17)

- Title `ATS Jobs Scraper: Greenhouse, Lever, Ashby`, shorter description with the supported ATS first, and a
  description on the input form.
- README: intro that names the six ATS, table of supported job board URLs, and a section on using the Actor
  through the API, integrations and AI agents. The output fields are unchanged.

#### Improved (2026-09-16, build 0.1.8, tags `beta` and `latest`)

- Domain resolution: 40 of 50 benchmark companies resolved, up from 37 (80 % instead of 74 %). No company
  resolved before is lost; 4.7 requests per domain on average, up from 4.2.
  - When the domain name itself matches no board, a few naming variants are tried: a dropped suffix
    (`datadoghq.com` → `greenhouse:datadog`) or a compound word split (`starlingbank.com` →
    `workable:starling-bank`). Most domains get no variant and cost nothing extra.
  - The careers page is also looked for on `careers.{domain}` and `jobs.{domain}`, before the home page
    (`oda.com` → Teamtailor board on `careers.oda.com`).
  - Teamtailor's own application link (`tt.teamtailor.com`) is no longer mistaken for a company board.
- The first careers-page address, built from the input, now passes the same public-host check as redirects.
- Teamtailor careers hosts that serve their feed as JSON Feed (`application/feed+json`, e.g.
  `careers.oda.com`) are now read; the crawler used to reject them after the resolver had found them.

#### Fixed (2026-09-16, build 0.1.6, tags `beta` and `latest`)

- The end-of-run message read "Companies 1 not reached"; it now reads "1 company not reached".
- Boards read at once could both pass the cost-limit check for the last item: the SDK counts an item only
  once its push completes, then drops the extra one silently while the board recorded it as delivered.
  Pushes still in flight now count against the remaining budget.
- Found by the first platform run under pay-per-event (cost limit $0.05, Stripe board): the budget was
  checked every 25 jobs, so 50 jobs were pushed while the platform kept 34, and the tracked state recorded
  the 16 dropped jobs as known — they would never have been delivered. The budget is now checked before
  every push.
- Same run: stopping with `autoscaledPool.abort()` let the run write its `SUMMARY` before the board handler
  finished, so the board was listed as `not_reached` and its state write raced the exit. The crawl now stops
  with `crawler.stop()`, which lets running boards finish first.

#### Fixed (2026-09-16, build 0.1.4, tags `beta` and `latest`)

- Change tracking on a board larger than `maxJobsPerCompany`, `maxItems` or the cost limit never saved its
  state, so every scheduled run reported the same first jobs as `new` and charged for them again. A board
  read in part now saves what the run went through on top of the previous state; the next run continues
  from there, and jobs never reached are still not mistaken for closures.
- Domain resolution ran one domain at a time with no timeout on the board API probes: a hanging API could
  stall the run, and a long list of unresolvable domains could run for hours. Probes now time out after
  20 s, four domains are resolved at once, and the status message shows the resolution progress.
- A company domain whose careers page redirected to an internal address was requested before any check.
  Redirects are now followed one at a time, and only to public hosts on standard ports.

#### Platform validation (2026-09-16, build 0.1.1, tag `beta`)

- Prefill input: 77 jobs from 3 ATS, succeeded.
- Worst case for memory (Stripe 647 jobs, Ashby Pennylane and Ramp, descriptions in text and HTML,
  three boards parsed at once): peak 194 MB at 1024 MB allocated, 150 MB at 512 MB. `defaultMemoryMbytes`
  set to 512 accordingly.
- Unresolvable domains and invalid entries: run succeeds with warnings, as intended, so the public
  success rate is not lowered by a user's input.

#### Added

- Read the published jobs of a company from Greenhouse, Lever, Ashby, Workable, Personio or Teamtailor,
  normalised into a single output schema.
- Accept three input forms per company: a plain domain (`stripe.com`), a job board URL
  (`https://jobs.lever.co/contentsquare`) or an explicit `ats:slug` (`workable:blueground`).
- Resolve a plain domain to its ATS in two stages: read the company's careers page for a board link, then
  fall back to probing the board APIs with the domain label. A board is only accepted when it returns at
  least one published job.
- Cache resolutions for 30 days in the named state store, so a scheduled run pays the search cost once.
- Filter by title keyword, location keyword, remote flag and publication date, before saving, so filtered
  jobs are never charged.
- Optional job descriptions (off by default) and optional raw ATS fields.
- Per-company run summary in `SUMMARY`, distinguishing a board with no openings from one that was not
  found, not reached, ambiguous, unreadable or failed.
- Change tracking: `changeStatus` (`new`, `changed`, `unchanged`, `closed`) with `changedFields`,
  `previousValues` and `previousScrapedAt`. Closed jobs are returned as items, rebuilt from the previous
  state. `onlyChanges` skips unchanged jobs so they are not charged.
