# Changelog of NL Hiring Signal Tracker – Open Jobs per Company (`lwsdjfls/nl-hiring-signal-tracker`) Actor

- **URL**: https://apify.com/lwsdjfls/nl-hiring-signal-tracker/changelog.md
- **Full Actor documentation**: https://apify.com/lwsdjfls/nl-hiring-signal-tracker.md

## Changelog

### 0.1 (2026-09-17)

- Initial release: open jobs per company straight from the company's own applicant tracking system (Greenhouse, Lever, Recruitee, Homerun, Teamtailor, Workable and Ashby), with the number of open jobs, breakdowns by department and location, the oldest and newest posting date and the 30-day hiring velocity.
- ATS detection from a company domain (homepage, career pages and career sites on a subdomain such as werkenbij.acme.nl), or from a job board URL you supply. Every input company is reported: one without a detectable ATS comes back as `no-ats-detected` with the pages that were checked, and is not charged.
- Changes since the previous run per company, in one line: `+1 job (Frontend Engineer), −1 job (Account Executive Benelux), 3 open`, with a first-seen date per job. History is kept per `stateKey`, so separate account lists stay separate.
- An ATS that is down, slow or rate-limiting gives `unreachable`: the last known record is delivered again for free, with the reason in `statusReason`, and the next run picks that company up again. One failing company never fails the run.
- The velocity basis is always stated: `posted-date` when at least 80% of the open jobs carry an ATS posting date, `first-seen` for a board without posting dates (Homerun) once there are 30 days of history and the complete job list was stored, and `not-enough-history` with `newJobsLast30Days: null` otherwise. A job no earlier run stored looks new in every run, so it is never counted as one.
- Cost controls in the input: `includeJobList: false` delivers counts and breakdowns without job listings and charges none, `maxJobsPerCompany` caps the job list and its charges per company, and `maxCompaniesPerRun` caps charged companies per run, with the rest reported in the next run. The first two decide what is stored: changing one starts the history over, so the next run reports every company as `added`.
- GDPR: only job fields are read (ID, title, department, location, job URL, posting date). Job descriptions, recruiter and hiring-manager fields, feed authors and application mailboxes are cut out before parsing; e-mail addresses, phone numbers and links in a title, department or location are scrubbed; referral, recruiter and source parameters are stripped from job URLs. Personal data is never delivered and never stored.
- Dataset views for CRM work: one row per company, with the largest departments and locations as one readable column (`Engineering (2); Data (1)`), or one row per open job, both exportable as CSV.
- A run without companies says to add one to "Companies", with an example, instead of "expected array, received undefined".
- "No personal data" is replaced by what the Actor does: recruiter, contact and description fields are never read, an e-mail address or phone number typed into a title is removed, and a person's name a company writes into a job title is kept as published. `maxCompaniesPerRun` now says that one run takes at most the 10,000 companies the input accepts, so a higher value changes nothing.
- README corrected to match the code: the 30-day velocity is there from the first run for the six systems with posting dates, not only from the second run; the feed parse times now give about 0.3 seconds at the 10 MB limit and about half a second at the 20 MB limit, and the 200,000 limit counts tags and attributes; "never people" and "a hard guarantee" are replaced by what is read, and a person's name in a job title, department or location is kept as published. Claims without a source are softened or removed: why hiring is a buying signal, the typical schedule, why Teamtailor and Homerun are read from their feeds, the concurrency threshold of 8, "used worldwide", the reply time and the head start on job boards. Added: the Actor start in the pricing example, the most a run can cost at the default settings ($2,200, or $200 without the job list), the maximum cost per run in Apify Console, two limitations (one job board per company, and no descriptions, salaries, seniority or contract type in the job list) and the date of the last live check.
- A list too long for the run's timeout now delivers the companies it checked instead of ending TIMED-OUT with nothing delivered. Records are delivered once the companies of a run have been checked, so a run the platform stopped at its timeout delivered none of them, including the companies it had resolved. The Actor keeps a tenth of a second free for each record still to deliver, charged or free (core's DELIVERY_MS_PER_RECORD of 100 ms, measured on Apify on 22 September 2026). A request is sent while at least a second is left beyond that, with the time left as its timeout when that is shorter than a request timeout, so a fast answer near the end still counts and its first attempt never delays the delivery. Its retries keep their usual pauses (Retry-After included) and start while at least 10 seconds, or one request timeout if that is shorter, are left, so a source that fails once near the end is asked again as in a run without a timeout; such a retry can delay the delivery by at most the request timeout less 10 seconds, out of the time the run keeps free to save its state. A request sent with less than that left gets one attempt. A request whose retries were cut short makes the company `unreachable`, free, with its last known record. A company whose request ran out of the time left, whose one attempt failed for a temporary reason with no time left to try again, or that was stopped before its next request, and the companies not started, are not delivered or charged in that run, what earlier runs stored about them stays as it was (their last check included), and one warning in the log says how many there were and to raise the timeout or split the list. An invalid entry sends no request and is still reported while its record can be delivered, also after companies were left out. The run succeeds; its status message and `RUN_SUMMARY` count those companies as not processed, with the outcome `partial`. A run whose work fits its timeout delivers exactly the records and charges it delivers without one; only a company that would start less than a second before the delivery must start, or a retry that would start less than 10 seconds before it, is treated differently. Tests check this with the Console prefill input, a typical small list and four lists whose work ends close to the end of a 5-minute run's work window (450 and 470 fast job boards; a last board that answers 503 once; invalid entries after the last board), each with a timeout 5 and 60 minutes after the start.
- Every run checks, and lists, first the companies checked longest ago and those never checked, so the next run of a list too long for its timeout starts with the companies the previous run did not check instead of leaving out the same ones again. When every company on the list was last checked by the same run, the order of the list stays.
- The default run timeout is 3 hours (`runTimeoutSecs` in store/listing.json, which store-sync writes to Console), enough for the largest list: 10,000 companies at the default concurrency of 5, a company given as a domain typically taking 4 requests of about a second (the prefill, 4 requests, took 2 to 4 s in the local store-test runs of 21 and 22 September 2026, process start included), so 0.8 s per company, plus 0.1 s to deliver its record, plus 10 %: 9,900 s, rounded up to whole hours. The README states it, and that a caller can set another timeout in the run options.
- The state key (`stateKey`) is a secret input: stored encrypted, because it often names a customer. Apify checks no pattern on a secret field, so any text is now accepted instead of failing the run: a key of letters, digits, dashes and underscores keeps the state it had, other text gets a state of its own, spaces around it are ignored and a blank one counts as none.
- Store discounts: Apify's paid plans pay less for every event except the Actor start, 5% on the Bronze tier, 10% on Silver and 15% on Gold and higher. The prices in the README are the list prices, which the Free plan pays.
