# Changelog of Wellfound Scraper | Startup Jobs with Salary AND Equity (`tqm/wellfound-scraper`) Actor

- **URL**: https://apify.com/tqm/wellfound-scraper/changelog.md
- **Full Actor documentation**: https://apify.com/tqm/wellfound-scraper.md

## Changelog

### Unreleased

#### 2026-09-08 — a short result set now says whose limit it was

Measured against the live Store listing: asking for the advertised maximum of **200** returned
**27** (14%), and the count reproduced exactly across separate runs — so this is the board's real
inventory, not a flaky scrape. The status message said only `Returned 27 Wellfound postings.`, which a
buyer who asked for 200 cannot tell apart from a broken Actor.

`finishRun()` now receives `requested: maxJobs` and appends the reason:

```
Returned 27 Wellfound postings. That is every posting on the board matching your
filters — asking for 200 will not return more today.
```

`RUN_STATS` also carries `requested` and `boardExhausted`. Logic and tests live in
`apify-actor-kit` (`inventory.ts`); `src/kit/` here is generated.

### \[2.1.0] - 2026-08-27

#### Fixed 🔴 — the default configuration returned ZERO rows

**The first thing a new buyer does returned nothing, and reported success.**

`roles` defaulted via `input.roles ?? ['devops-engineer']`. Apify applies
`INPUT_SCHEMA` defaults to `getInput()`, so an unset `roles` arrives as `[]` — not
`undefined` — and `[] ?? x` is `[]`. The code default was **unreachable**, so a run
with the prefilled defaults fetched no role pages at all: **0 rows, exit
`SUCCEEDED`**.

Fixed in both places, deliberately: `INPUT_SCHEMA` now carries the real default
(`["devops-engineer"]`), and the code falls back on **emptiness** rather than on
`undefined`, so the actor cannot be talked into fetching nothing.

**Measured: 0 rows → 25 rows on the default configuration.**

#### Added — a zero-row run now says why, and fails

Adopts the shared `src/kit/`, generated from [`apify-actor-kit`](https://github.com/Atredies/apify-actor-kit).
Too-narrow filters, every role page refused, and a change in Wellfound's page data
are now three distinct, named, failing outcomes with a per-filter breakdown.
`failOnZeroResults` (default `true`) opts back into quiet green runs. A refused role
page is counted rather than only logged, so `blocked` reaches the diagnosis.

#### Fixed — `useResidentialProxies` advertised something it does not do

The input has been a **no-op since 2.0.0** (the HTTP path needs no proxy), while its
schema still read *"recommended, costs more but avoids blocks"* and defaulted to
**true**. Now titled *deprecated — no-op*, described truthfully, and defaulted to
`false`. Residential proxy is billed at $10/GB; advertising it as recommended for a
path that never uses it is the worst kind of harmless.

#### Fixed — `maxJobs` advertised a ceiling it cannot reach

The schema offered up to **1,000**. Each Wellfound role page is a single
un-paginated request carrying roughly **25** listings (measured 2026-08-27), so the
real ceiling is ~25 × the number of roles. Maximum corrected to **200**.

#### Changed

- One batched `Actor.pushData()` per run instead of one call per row.

> Note: the 2.0.0 rewrite (browser → `__NEXT_DATA__` over HTTP, 2026-08-14)
> predates this changelog's coverage and is recorded in the TQM vault's
> *Scraper Commercialisation Plan* §2.

### Unreleased — 2026-08-13

Documentation only. **No behavior changes**; build `0.0.4` is unmodified.

#### Added

- Full documentation suite: `docs/ARCHITECTURE.md`, `docs/DEVELOPMENT.md`, `docs/WELLFOUND-DOM.md`,
  `docs/IMPROVEMENT-PLAN.md`, and `CLAUDE.md`.
- `.actor/dataset_schema.json` — the output surface was previously undeclared, and now carries
  per-field status warnings.
- README rewritten as a buyer-facing listing with a **"partially working, not ready to sell"** banner
  and a per-field status table.

#### Verified against live Wellfound

Two platform runs on 2026-08-13, both with `useResidentialProxies: false`:

| Input | Rows | Cost | Duration |
|---|---:|---:|---:|
| `maxJobs: 10` | 50 | $0.0548 | 221 s |
| `maxJobs: 100` | 50 | $0.0883 | 370 s |

**~$1.77 per 1,000 rows — the most expensive actor in the portfolio**, roughly 30× the HTTP-based
siblings, because it renders ~51 browser pages per run.

#### Assessment corrected

An earlier reading of the run — "SUCCEEDED, 50 rows, highest yield after HackerNews" — was **wrong**.
Row count is not a working scraper. Field-level inspection shows most of the output is unusable:

| Field | Status |
|---|---|
| `title`, `jobUrl`, `jobId`, `salary*` | ✅ genuinely work |
| `company` | 🔴 never extracts — **2 distinct values across 50 rows** (`"About the job"`, `"Unknown"`) |
| `location` | 🔴 never extracts — always `"Not specified"` |
| `role` | 🔴 never extracts — always `""` |
| `remote` | 🔴 `true` on 49/50 — keyword-matched against the whole page |
| `hasEquity` | 🔴 `true` on 44/50 — same cause |
| `companyUrl`, `companySize`, `funding`, `postedAt`, `experienceLevel` | 🔴 declared, never populated |

#### Findings recorded (not fixed)

- Detail-page selectors are `[class*="…"]` guesses written without a captured page; Wellfound ships
  hashed CSS class names, so none match. The company selector falls through to the first `<h2>`,
  which reads **"About the job"**.
- **`maxJobs` is inert.** The listing loop checks `processedCount >= maxJobs` before queueing, but
  `processedCount` is only incremented in the detail handler, which hasn't run — so it is `0`
  throughout and the `break` never fires. Verified: 10 and 100 both return 50.
- `remote`, `hasEquity` and `techStack` are matched against `document.body.textContent`, so
  Wellfound's own navigation and marketing copy answer for the job.
- **Row 0 is the login wall** — `<h1>` is `"Find what's next"`, which passes the only validity gate.
- `useResidentialProxies` defaults to `true`; on an account with no residential capacity the default
  configuration cannot run.
- `currency` is hard-coded `"USD"`; the salary regex reads `max` by computed array index.
- The `jobId` fallback embeds `Date.now()`.
- The `roles` filter is doubly broken — `role` never extracts, so the filter is always skipped.
- Fixed `delay()` calls total ~100 s of sleeping per run.

All are itemized and prioritized in `docs/IMPROVEMENT-PLAN.md`, which flags one investigation to run
**before** any selector work: capture a real detail page and check for JSON-LD or `__NEXT_DATA__`,
which would likely resolve most items at once.

**This actor should not be listed for sale until improvement-plan items 1–3 are fixed.**

***

### 0.0.4 and earlier

No changelog was kept. Build `0.0.4` is the deployed build as of 2026-08-13; the last source commit
was 2025-11-16. The actor carried the platform title *"Wellfound Scraper - not working yet"* until
2026-08-13.
