# Changelog of Salary Data API: Pay Ranges by Job Title & Level (`redomic/salary-data`) Actor

- **URL**: https://apify.com/redomic/salary-data/changelog.md
- **Full Actor documentation**: https://apify.com/redomic/salary-data.md

## Changelog

- 2026-11-15: Benchmark mode adds optional `benchmarkByCompany` to return one row per company (with `company`, percentiles and `n`); single-employer groups are allowed in this mode. No listing copy or price change.
- 2026-11-15: Drop sample-job-URL columns that are empty in every row of a run, and remove closedOn/status/changeType from the default jobs Store view so no always-empty columns remain.

### 0.1.0 - 2026-11-08

- 2026-11-15: Split job vs benchmark views (separate dataset views/schemas), job rows return fresh-first with `asOf`/`dataAsOf`, and benchmarks remain auditable with `asOf`, `n` and median posted salary. No listing copy or price change.
- 2026-11-14: Moved unavailable-source STATUS to the run key-value store (no dataset status row on index failure) and verified benchmark percentiles (p25<=p50<=p75, n>=minSample) and non-null job salaryMin/Max in fixture runs. No listing copy or price change.
- 2026-11-13: Added `rowType` (benchmark/job/cellStatus), moved status/cell-status rows to the run key-value store, and declared two dataset views (benchmarks, jobs). Benchmark rows now include `window`, `asOf`, `currencyMix` and derived `roleFamily` for title queries; README adds fixture-backed price proof. No listing copy or price change.
- 2026-11-13: New `mode: closed` benchmarks what roles that actually closed paid (CLOSED-JOBS-\* shards, `closedOn` on every row/cell, `status: closed`, freshness/days-open validated against closedOn, `closedScanned`/`closedMatched` in status/summary). Thin cells stay `insufficient_sample` with null percentiles. README section with a real fixture-run table. No listing copy or price change.
- 2026-11-13: New `mode: "closed"` benchmarks pay from postings that actually closed (CLOSED-JOBS shards), with `closedOn`/`status` on rows and `closedScanned`/`closedMatched` in summary/status. Freshness and days-open filters use `closedOn`; thin closed cells return `insufficient_sample`. No listing copy or price change.
- 2026-11-13: Jobs and benchmarks can be filtered by role age with `minDaysOpen`/`maxDaysOpen` (dataAsOf minus firstSeen/trackedSince, surfaced as `daysOpen`); summary reports `excludedByDaysOpen`. No listing copy or price change.
- 2026-11-13: README now documents `additionalPay` with real bonus fixture output (bonus: USD 20000-30000 year, dataAsOf 2026-11-08); rows remain null when absent. No listing copy or price change.
- 2026-11-13: Job rows now include `additionalPay` (bonus/commission/overtime and other posted extras) when present in source postings; null when absent. Monitor diffs track `additionalPay` changes. No listing copy or price change.
- 2026-11-12: `jobTitles` queries now fill `roleFamily` on benchmark rows and cells with the most common role family among the matched postings (was always null); it stays null only when no matched posting has one. No listing copy or price change.
- 2026-11-12: README benchmark/sample figures regenerated and verified from a fresh fixture run (open_jobs_sample.json.gz, dataAsOf 2026-11-08); every displayed figure byte-traces to run output with per-cell n, companies and asOf, and thin/low-diversity cells shown as insufficient_sample with null percentiles. No listing copy or price change.
- 2026-11-11: Job rows now include `daysOpen` (dataAsOf minus firstSeen, fallback trackedSince) and `trackedSince` to expose time-on-market history from the daily snapshot; README and dataset schema updated, with tests covering fixture rows. No listing copy or price change.
- 2026-11-10: README "Cells" and "Salary-band and freshness filters" tables (fresh-30-day jobs with ageDays; sub-$150k-banded Mid benchmark cell) pinned byte-equal to a fresh fixture run by `test_readme_cells_and_filter_tables_match_fresh_run`; US software-engineer run monotonicity (Intern < Junior < Mid < Senior) and README n/p50 byte-match re-verified against a fresh run, and no `(computed)` placeholder remains anywhere in the README. No listing copy or price change.
- 2026-11-10: README sample tables regenerated from the real 2026-11-08 fixture run and pinned by tests (benchmark n/p50 byte-equal a fresh run; jobs table carries the run's first rows with the correct remote flag); no (computed) placeholders, percentile basis and fixed FX table stated. Default input `{}` verified to return >=1 real benchmark row (jobTitles stays a form prefill, no source default). No listing copy or price change.
- 2026-11-10: Monitor mode: new optional `monitor`/`stateKey` inputs return only `new`/`changed`/`removed` rows (with `changeType`) for job rows and benchmark cells, diffed against a named key-value store (`salary-data-monitor`) keyed by job URL/id or the cell group key, fingerprinting title/salaryAnnualMin/Max/country/seniority (jobs) or percentiles/n/companies (cells). Only `new`/`changed` rows are billed; `removed` rows are free, and a quiet run ends SUCCEEDED with 0 billed rows. Defaults leave old inputs unchanged. README Monitor section with real two-snapshot fixture output; `tests/test_monitor.py` covers the diff and the two-run billing flow. No listing copy or price change.
- 2026-11-10: Seniority normalized from title (Intern/Junior/Mid/Senior/Lead-Principal/Manager/Director/Executive, whole-word so "international" is not Intern) with monotonic p50 guard (Intern < Junior < Mid < Senior) per query/country/currency; countries normalized to ISO-3166-1 alpha-2 (inputs accept codes or names, e.g. `US`/`United States`, `CA`/`Canada`). Cells with n<5 or <5 employers return `insufficient_sample` with null percentiles in cells mode (Senior/Canada n=6 from 3 companies is thin, never a benchmark); benchmark thin cells stay omitted and counted in `skippedSmallCells`, so benchmark runs keep returning real data rows. README/examples updated from the real 2026-11-08 fixture (Senior Engineering-Data/engineer cells suppressed below Mid). No listing copy or price change.
- 2026-11-09: Filtered cells now proven to return insufficient_sample (band and freshness each thin a dense cell below n=5 in tests), README filter numbers pinned to the real 2026-11-08 fixture through the main-flow pre-match (band Mid n=20 p50 $205,000; fresh Mid n=11 p50 $215,000), Input table wording clarified to range-overlap. No listing copy or price change.
- 2026-11-09: Salary-band and freshness filters: new optional `minSalaryAnnual`/`maxSalaryAnnual` (annualised, report currency, keep on range overlap) and `postedWithinDays` (dataAsOf minus postedAt/firstSeen) inputs, `ageDays` on job rows, benchmarks recompute on the filtered set, summary reports `excludedBySalaryBand`/`excludedByFreshness`. Defaults (0) leave old inputs unchanged. README section with real fixture output.
- 2026-11-09: Sanity and shape cleanup: Intern titles carrying senior-level pay above $150k USD are excluded and Intern medians above Junior are suppressed; benchmarks and cells now need at least 5 distinct employers or they are omitted/insufficient_sample, so single-employer cells like Canada Senior n=6 no longer headline as benchmarks. Output is unified flat: sample arrays became sampleCompanies string plus sampleJobUrl1/2/3, duplicate sampleSize/asOf/role/location removed, benchmark and cell rows share the same core fields plus city for cells. README regenerated from a real 2026-11-08 fixture run (p10-p90 real, no placeholders), percentile basis (annualised midpoint, linear interpolation), fixed FX table, enumerated roleFamily/seniority values and US/SF coverage bias documented. No listing copy or price change.
- 2026-11-09: Country is now a real country or null: US states and cities map to their country, Remote stays as `remote` (never a country), coordinates stripped via `core/ats clean_location`; default-run countries no longer include states or Remote. No listing copy or price change.
- 2026-11-09: Benchmark cells output mode: new optional `cells` flag groups by role x city x seniority with n/sampleSize, p25/p50/p75, asOf (current posted salaries only, no trend claim). Cells with n<5 return `_status insufficient_sample` with null percentiles instead of a fake median; dense cells return real percentiles from live shards. Thin and dense tests cover synthetic and fixture cells; no new listing, title/description/SEO or price change.
- 2026-11-09: Publish pre-checks tightened. Benchmark `sampleCompanies` and `companies` now treat the same employer posted as "Acme", "ACME" or " acme " as one company, so both stay distinct on multi-board source data, and a real-fixture test asserts it. Store SEO fields (title/description/seoTitle/seoDescription/categories) are pinned by a test against the `listing_ready` limits.
- Benchmark samples: `sampleCompanies` returns distinct companies (up to 5) and `sampleJobUrls` returns one URL per sampled company.
- Initial release: benchmark percentiles (p10-p90, annualised, with n/companies) and salary-bearing job rows from employer-posted career-site data (daily snapshot). Modes: benchmark, jobs. Filters: jobTitles, roleFamilies, seniority, countries, locations, remoteOnly, companies, currency, minSample. Pricing: $0.02/benchmark row, $0.002/job row. No scraping at run time.

### 0.1.1 - 2026-11-14

- 2026-11-14: Benchmark mode supports `output: "events"` to emit only material p25/p50/p75 shifts (above configurable `changeBand`, default 5%) with `n`, `companies`, `window`, `asOf` and `confidence`; unchanged scheduled runs return 0 events while saving baseline state. Benchmark rows now include `confidence`.

### 0.1.2 - 2026-11-14

- 2026-11-15: DAY-UPDATE - benchmarks/jobs split into dataset views with rowType, status/control rows kept in the run key-value store, benchmark maxItems default 20 (Try <= $0.40), and README snapshot figures regenerated from META fixtures with fixture-backed verification of non-null p10/p50/p90/n. No listing copy or price change.
- 2026-11-14: Keep benchmark dataset rows data-only (STATUS/SUMMARY/unavailable stored in the run key-value store) and add regression coverage that a 20-row benchmark run has no blank p10/p25/p50/p75/p90/n. No listing copy or price change.
- 2026-11-14: Cut default `maxItems` from 100 to 20 (benchmark Try estimate ~$0.40 at $0.02/row) and refreshed README live snapshot figures to 324,776 open jobs with salary (dataAsOf 2026-11-14, 14 days history). No listing copy or price change.
- 2026-11-14: Job rows include `evergreen` (open >=180 days since firstSeen/trackedSince). New optional `excludeEvergreen` filter excludes evergreen postings and summary reports `excludedByEvergreen`. No listing copy or price change.
