# Changelog of Zillow Property Scraper — Agent Data, Zestimate, Price History (`afanasenko/zillow-property-agent-data-scraper`) Actor

- **URL**: https://apify.com/afanasenko/zillow-property-agent-data-scraper/changelog.md
- **Full Actor documentation**: https://apify.com/afanasenko/zillow-property-agent-data-scraper.md

## Changelog

All notable changes to the Zillow Property & Agent Data Scraper actor are documented here.
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

### \[2.3.100] - 2026-09-24

#### Fixed

- **Links sent without choosing a mode are no longer ignored.** A run left in ZIP code mode with
  only the example ZIP code (90210) that receives links in `startUrls`, `urls` or `zillowUrl` now
  works from those links: a list of property page links looks up every one of those properties in
  the same run, and a single link is searched as in URL mode. The run's log and message say what
  was searched.
- A limit sent as `maxItems`, `maxProperties` or `maxResults` is kept when a run is moved to the
  links it was sent; properties past it are listed in `SKIPPED_ITEMS` and aren't charged.
- Links sent alongside ZIP codes you entered are listed in `SKIPPED_ITEMS` as not searched, and the
  run says so.

### \[2.3.99] - 2026-09-23

#### Fixed

- **A run now stops at its "Maximum cost per run".** When that limit, or what is left of your
  account's usage limit, is used up, the run stops enriching and keeps every property it charged
  for. Its message and status say how many properties the limit paid for and how to get the rest.
  Properties it didn't reach are listed in `SKIPPED_ITEMS` and aren't charged.

### \[2.3.98] - 2026-09-23

#### Fixed

- Free-plan rows held back from a map-area search link now include street, city, state, ZIP code,
  status and home type.

### \[2.3.97] - 2026-09-23

#### Added

- URL mode also takes the link from `urls` or `startUrls` when `zillowUrl` is empty. One link is
  searched per run; any others are listed in the `SKIPPED_ITEMS` record.

#### Fixed

- A ZIP code page given in URL mode without a city in front of it (for example
  `https://www.zillow.com/90210/rentals/`) is now searched as that ZIP code; before, the run
  stopped saying the link could not be read.
- A search link copied after moving Zillow's map, with no city or ZIP in it, now returns homes
  inside the map area it shows.
- If a run cannot start because of a problem on our side, the Output tab now says the run stopped
  instead of loading forever.

### \[2.3.96] - 2026-09-10

#### Changed

- Clarified wording in the README, in two output column descriptions and in older entries of this
  changelog. Nothing changes in how the actor runs, what it returns or what it costs.
- When a run fails for a reason on our side, the error message now stays short and plain.

### \[2.3.95] - 2026-08-24

#### Changed

- **README — new `🧭 When to use it` section.** Six short answers to "is this the actor I need?",
  placed right after Quick start: agent lead lists, valuation and comps, FSBO / foreclosure /
  auction sourcing, property-type search, rental research, and school-quality filtering. Nothing
  new is claimed — each line names filters and columns the actor already ships.
- **README — the page title now matches the actor's title on the Store**
  ("Zillow Property Scraper — Agent Data, Zestimate, Price History"). The two had drifted apart.

Documentation only. Input schema, output dataset columns, KVS records, console output, error
messages, defaults, and pricing are all unchanged from 2.3.94. Saved tasks continue to work
identically.

### \[2.3.94] - 2026-08-18

Maintenance build — no user-facing changes. Input schema, output dataset
columns, KVS records, console output, error messages, defaults, and pricing
are all unchanged from 2.3.93. Saved tasks continue to work identically.

### \[2.3.93] - 2026-08-17

#### Fixed

- **A run that stops with an error now says what happened.** Previously, if the run hit an error
  the wording didn't specifically recognise, it ended with an empty dataset and no explanation
  anywhere — no message in the run's Storage tab, nothing on the status screen. It now reports
  what went wrong, whether any properties were saved before it stopped, and exactly what the run
  was charged. Errors the run already explained in detail are unchanged.

### \[2.3.92] - 2026-08-17

Maintenance build — no user-facing changes. Input schema, output dataset
columns, KVS records, console output, error messages, defaults, and pricing
are all unchanged from 2.3.91. Saved tasks continue to work identically.

### \[2.3.91] - 2026-08-17

Maintenance build — no user-facing changes. Input schema, output dataset
columns, KVS records, console output, error messages, defaults, and pricing
are all unchanged from 2.3.90. Saved tasks continue to work identically.

### \[2.3.90] - 2026-08-10

#### Fixed

- **A failed run no longer prints raw technical failure text.** That text went into the run log and
  into the run's storage records, and on a connection problem it carried a server address. None of
  it was ever useful to you. Failures now read as one short, plain sentence; the reasons you can act
  on are unchanged.

### \[2.3.89] - 2026-08-10

#### Added

- **Your results now show the properties the free plan matched and did not look up.** A free run
  enriches 15 properties. If the search matched more — a Miami Beach ZIP matched 820 — the results
  now carry up to 25 extra rows after your 15, each with `not_enriched` in the new
  **enrichmentStatus** column and, in **whyNotEnriched**, the full count: *"this search matched 820
  properties and the free plan enriches 15 per run, so 805 were left out."*
  - Those rows carry only what the search itself returned — address, price, beds, baths and the
    Zillow listing URL. **Every column that costs money to look up is empty on them**: agent name,
    email and phone, Zestimate, tax and sale history, schools, Walk Score.
  - **They are never charged.**
  - **A paid run never produces them**, and neither does a run started with the demo ZIP.
  - Until now this fact lived only in the run log, on the live status page and in the run's storage
    records. Anyone reading the results through the API or an AI assistant saw 15 properties and no
    way to tell "the free plan stopped at 15" from "this ZIP has 15 listings".

#### Changed

- **Two columns added to every row: `enrichmentStatus` and `whyNotEnriched`.** On a property that
  was looked up they read `enriched` and `N/A`. They are appended at the end of the row, so every
  column that existed before keeps its position.

### \[2.3.88] - 2026-07-30

#### Fixed

- **A search Zillow refuses is no longer reported as "no listings here".** When Zillow rejects a
  search it answers with an error rather than a result set, and the actor was reading that as an
  empty area — so a run would finish cleanly, save nothing, and advise you to try a residential ZIP
  while sitting on a ZIP with hundreds of listings. The rejection is now shown, in Zillow's own
  words, with the filter to change.
- **Setting a maximum number of bedrooms and a minimum number of bathrooms no longer returns
  nothing.** Zillow rejects that combination unless a maximum number of bathrooms is also set. The
  actor now supplies an upper bound for you; your minimum is unchanged and nothing extra is filtered
  out. Every run that hit this returned zero rows.
- **The ZIP Codes field now understands what people paste into it.** A whole comma-separated list in
  a single row is split into its ZIP codes, ranges like `07039-07047` are expanded, ZIP+4 codes are
  trimmed to five digits, and a copied block of text is unpacked.
- **Entries that are not US ZIP codes are no longer searched.** Web addresses, Canadian postal codes,
  county names and street addresses used to be sent to Zillow anyway, which sometimes matched
  unrelated homes — in the wrong country — and charged for them. Each is now skipped, named, and
  costs nothing.
- **One unreadable property no longer ends the whole run.** If a single listing cannot be saved, it
  is reported and skipped and the rest of the run continues, instead of the run failing with
  everything lost.

#### Changed

- The free-plan limit of 20 ZIP codes per run is now applied after a pasted list is split, so a list
  in one row is counted the same way as 20 separate rows.

### \[2.3.86] - 2026-07-30

#### Fixed

- **URL search now reads the Zillow pages it used to ignore.** One in eleven URL-mode runs was
  finishing with no rows and no explanation. Four kinds of link were the cause, and all four now
  work: a page for one street address (`/homes/119-E-Drexel-Ave_rb/`, or a `/homedetails/` page with
  no ZPID number in it) returns that home; a ZIP page (`/ocala-fl-34481/`) is searched as the ZIP
  itself; a state page (`/nc/land/`) searches the whole state; and a keyword page
  (`/fl/fixer-upper_att/`) applies the keyword and home types written into the URL.
- **Owner-posted searches return owner-posted listings.** A `/fsbo/` or `/by-owner/` URL was asking
  Zillow for the agent-listed pool, so the homes coming back were not the ones the link asked for —
  and each of them was billed. Zillow's owner channel also carries new-construction, bank-owned and
  auction homes; those are now dropped before enrichment, so you are not charged for them either.

#### Added

- **A plain-language note when a link cannot work.** Agent and company profile pages list no homes,
  and a neighbourhood inside a city is something Zillow shows on the map but will not search on its
  own. Both used to end the run at zero with nothing said. The run now names the shape, says which
  page to paste instead, and puts the same line in the run's status message for anyone reading over
  the API. Nothing is charged either way.
- **The cost ceiling is printed before anything is fetched**, and shown on the live status page, so a
  link that covers a whole state cannot surprise you.

### \[2.3.84] - 2026-07-29

#### Fixed

- **ZIP code search now returns homes in the ZIP you asked for.** Every ZIP was being turned into
  its city before the search ran, so asking for 78704 searched all of Austin: of 25 homes returned,
  1 was in 78704. Measured the same way, 60614 returned 3 of 25 and 33139 returned 9 of 25 — and
  every one of those homes was billed. Each ZIP is now searched on its own.
- **The property cap now applies to each ZIP, as its name always said.** ZIP codes were searched
  five at a time in one combined query, and the cap cut the combined result — three ZIPs with a cap
  of 10 returned 10 homes in total, sometimes none at all from the last ZIP. Each ZIP now gets its
  own cap. A run with more than one ZIP will return more homes than before, and cost more: the run
  now prints its cost ceiling before fetching anything, and the live status page shows it too.
- **One bad ZIP no longer spoils the others.** A ZIP that Zillow cannot search by itself used to
  drag the whole group of five down with it, returning unrelated addresses for all of them.

#### Added

- **A note when a ZIP has to be searched through its city.** A small number of US ZIP codes — PO-box
  and non-residential ones — cannot be searched directly. For those the run searches the surrounding
  city and keeps only the homes inside your ZIP; the homes it drops are never billed. Which ZIPs this
  happened to, and how many rows were dropped, is written to `RUN_SUMMARY.zipScope` and explained in
  plain language in the `USER_MESSAGE` record.

### \[2.3.82] - 2026-07-29

#### Changed

- **The Store listing now says what this actor returns** — agent emails and phones, price history,
  Zestimate, schools and 70+ fields per home, searchable by ZIP code, by ZPID list or by any Zillow
  URL — and the price line shows the per-property price instead of a generic label.

### \[2.3.81] - 2026-07-28

#### Fixed

- **The "Live status" link now works after a run has finished.** It pointed at the run's own
  container, which the platform shuts down as soon as the run ends — so opening it later showed
  nothing. It now opens the saved copy of the page, which stays available for as long as the run's
  storage does.

### \[2.3.80] - 2026-07-28

#### Added

- **New Example tasks section in the README** — eight ready-to-run searches you can start in one
  click: homes for sale, recently sold, rentals, foreclosures, new construction and agent leads.

### \[2.3.79] - 2026-07-28

Maintenance build — no user-facing changes.

### \[2.3.78] - 2026-07-28

#### Fixed

- **The run cost now shows your plan's price, not the Free-plan price.** Property Data Enriched costs less on paid plans ($0.08 on Free, down to $0.05 on Gold and above), but the live status page and `RUN_SUMMARY` always quoted $0.08. Both now quote what your subscription actually pays. Your bill is unchanged — only the number the run reported was wrong.
- **A run budget you set now buys the properties it should.** Because the run priced every property at $0.08, a "Max budget (USD)" of $1.00 stopped at 12 properties on a plan where $1.00 covers 20. The budget is now spent at your real price.

Same input shape, output columns, dataset shape, and pricing.

### \[2.3.76] - 2026-07-06

#### Changed

- Every "Upgrade" link in run messages now says what's behind it: paid plans start at $29/mo with $29 in platform credits, and the link opens your plan page in Apify Console.

#### Fixed

- The completed-screen upgrade box now quotes the current plan price — $29/mo with $29 in platform credits (it showed an outdated $49).

### \[2.3.75] - 2026-07-06

#### Added

- **Zero-result runs now explain themselves.** A run that saves 0 properties writes a `USER_MESSAGE` with the exact cause for that run — the search matched nothing (with ZIP/URL-specific fixes) vs listings were found but none could be enriched — plus a verified starter input and a note that empty results cost nothing. The live status page shows this diagnosis instead of a bare "Scraping Complete!", and the run's status message points API callers at it too.
- **Runs stopped by a missing or invalid input now say which field to fix.** An empty ZIP list, empty ZPID list, empty URL, or unknown mode writes a `USER_MESSAGE` naming the exact form field, with a starter example. Previously these runs failed with only a log-level error.
- **Failed runs get a real error screen.** The live status page now renders what stopped the run and what (if anything) was saved — previously it kept showing the in-progress view.
- `RUN_SUMMARY` additions: the `no_results` diagnosis now covers URL and ZPID modes (was ZIP-only), and a new `heldBackByFreeCap` field reports exactly how many matching listings the free plan's per-run cap clipped.
- README: one-click Example tasks section, 6 new FAQ entries (including "Why did my run return 0 properties?" and "Do I pay for runs that return nothing?"), a URL-mode expectation note, and more related actors.

#### Fixed

- **The free-plan cap message now reports only what the cap actually held back.** Its "left behind" number previously included delisted listings and failed lookups; it now counts exactly the listings clipped by the per-run cap, and the ZIP-cap note no longer appears on runs that returned nothing (those get the zero-result diagnosis instead).
- The paid-plan welcome note no longer appears on a failed run, and failed runs no longer consume a slot toward it.
- Stale input hints refreshed: the URL field now mentions plain city/browse URLs (supported since 2.3.70), and the ZIP field no longer claims a 5-ZIP limit.

Same input shape, output columns, KVS record keys, and pricing.

### \[2.3.73] - 2026-07-06

#### Fixed

- The "Upgrade" link in run summaries now opens your Apify plan page directly (the old link form could land on the wrong billing tab).

### \[2.3.72] - 2026-06-11

#### Added

- **Free-plan runs now report exactly what was held back** — when a free-plan ceiling fires (property cap, ZIP cap, per-run budget, masked agent emails), the run writes a `USER_MESSAGE` storage record with the real numbers for that exact search: how many matched properties were not enriched, how many ZIP codes were not searched, and how many agent emails were found but masked. Previously these limits applied silently and only appeared as ids in `FREE_LIMITS_APPLIED`.

#### Changed

- **Free plan now includes 15 runs per calendar month** (resets on the 1st; demo/test runs don't count). Runs over the allowance complete successfully with a clear message and charge nothing. Paid plans are unlimited, as before. Per-run free limits (15 properties / 20 ZIP codes / masked emails) are unchanged.

Same input shape, output columns, and pricing.

### \[2.3.71] - 2026-05-21

#### Added

- **Plain city URLs now work in URL mode** — paste a Zillow city link such as `https://www.zillow.com/austin-tx/` or `https://www.zillow.com/austin-tx/houses/` and the actor reads it as a location search; previously these completed with zero results unless you first applied a filter on Zillow. The home-type and listing segments — `/houses/`, `/condos/`, `/townhomes/`, `/apartments/`, `/multi-family/`, `/lots-land/`, `/manufactured/`, `/sold/`, `/rentals/`, `/fsbo/` — are mapped to the matching search. `searchQueryState`, `_rb` browse, recently-sold, and single-property `/homedetails/.../<zpid>_zpid/` URLs are unaffected.

Same input shape, output columns, KVS records, and pricing.

### \[2.3.70] - 2026-05-21

#### Fixed

- **Plain Zillow browse URLs now work in URL mode** — for-sale browse URLs like `https://www.zillow.com/homes/Beverly-Hills,-CA_rb/` are now read as a location search (previously they returned no results). Recently-sold, `searchQueryState`, and single-property `/homedetails/.../<zpid>_zpid/` URLs are unaffected; single-address shortcut URLs still return the existing guidance to use ZPID mode.

Same input shape, output columns, KVS records, and pricing.

### \[2.3.68] - 2026-05-19

#### Fixed

- **Apartment-building rentals now emit useful data** — multi-unit ForRent listings on Zillow are returned by the search with a lat-long fallback identifier (flagged as a building, e.g. zpid `"30.249971--81.81388"`) because Zillow doesn't assign stable property IDs to multi-unit rental buildings. Those identifiers can't be looked up for property details — so the actor was pushing 13 of 15 nearly-empty rows on a representative Jacksonville rentals search (customer-reported 2026-05-19: 15 enriched, only 1 with brokerName, 14 effectively empty). The actor now detects these buildings and emits a row directly from the search result: `brokerName` = building name, `streetAddress` / `city` / `state` / `zipcode` parsed from the address, `price` = lowest unit rent, `propertyDescription` = unit-floorplan summary, plus new fields `availabilityCount` and `unitsAvailable` (additive dataset\_schema additions). It skips the property-details lookup that could never succeed, so building rows also come back faster.

#### Changed

- **`dataset_schema.json`** — added two additive fields, `availabilityCount` (integer / null) and `unitsAvailable` (array / null), populated only for apartment-building rentals. `brokerName` description updated to call out the building-name-as-listing-company semantics for those rows.

Same input shape, output columns are a superset of the previous shape (two new fields), KVS records, and pricing are unchanged. Single-property ForSale/ForRent/RecentlySold/ZPID-mode rows are unaffected.

### \[2.3.67] - 2026-05-19

#### Fixed

- **Sale-channel routing bug for ForRent searches** — when both `isForSaleByAgent` and `isForSaleByOwner` had the same value, the actor asked for a catch-all listing channel that silently returns zero results on ForRent searches. Verified on ZIP 32218: the catch-all request → 0 properties; asking for agent-listed and owner-listed homes explicitly → 41 properties. The actor now always asks for both explicitly in that case, which works for both ForSale and ForRent. This was the root cause of the recurring "took form defaults, still got zero" rental complaints — including the customer-reported run on 32218 that motivated the 2.3.63 → 2.3.65 patch sequence.

Same input shape, output columns, KVS records, and pricing.

### \[2.3.65] - 2026-05-19

#### Fixed

- **Rental search default-fix — sale-flags clearing bug** — 2.3.64 set `isForSaleByAgent` and `isForSaleByOwner` to explicit `false` when clearing the form defaults. The search returns 0 results when both are explicitly `false`, but the full inventory when both are simply left unset. Verified on ZIP 32218 ForRent: explicit false → 0 properties; left unset → 41 found / 5 returned. The auto-fix now leaves both unset instead of setting them to false. No change to the trigger gate.

Same input shape, output columns, KVS records, and pricing.

### \[2.3.64] - 2026-05-19

#### Changed

- **Rental search default-fix — extended** — the auto-fix introduced in 2.3.63 also now clears the form-default sale-side listing flags (`isForSaleByAgent: true` + `isForSaleByOwner: true`) on `ForRent` searches when they match the form-default signature. Smoke-replay of the customer-reported zero-result run on ZIP 32218 showed apartments-on alone wasn't enough — the sale-side flags also poisoned the rental query and had to be cleared for the result set to recover. Same trigger gate (both flags both default to `true` together = form default; explicit selection of one side only is left alone).

Same input shape, output columns, KVS records, and pricing.

### \[2.3.63] - 2026-05-19

#### Changed

- **Rental search default-fix** — ZIP mode now auto-enables the "Apartments" home type when it detects you took the form defaults on a `ForRent` search (i.e. Houses + Townhomes + Condos + Multi-family all on, Apartments off). The original defaults are tuned for `ForSale` where apartments are rare; for rentals apartments are the majority of inventory, and the same defaults were yielding ~83% zero-result rate vs ~20% with apartments on. If you really want SFR-only rentals, set `isApartment=false` together with the other home types you want off — the auto-fix only triggers on the exact form-default signature.
- **Input schema** — `isApartment` field description rewritten to call out the rental-search best practice and explain the auto-fix.

Same input shape, output columns, KVS records, and pricing. URL and ZPID modes unchanged (URL mode reads filters from `searchQueryState`; ZPID mode bypasses filters entirely).

### \[2.3.62] - 2026-05-19

#### Changed

- **URL mode** — clearer error when `zillowUrl` is missing (now includes an example URL, how to obtain one, and the SDK call shape). Added a pre-flight warning when the URL looks like a single-address shortcut (e.g. `/homes/<address>_rb/`) — those redirect on Zillow's side and never return listings, so the warning points to the right alternative before any search is made. Added a tip when a `/homedetails/.../<zpid>_zpid/` URL is auto-converted to ZPID mode, suggesting the direct ZPID input as the cleaner shape for future runs.
- **RUN\_SUMMARY** — added `properties.capStatus` (`unlimited_complete` / `cap_reached` / `no_results`) and `properties.capNote` so consumers reading the run via the API can tell the difference between "this is every listing the search found" and "we hit the cap, more available." The same note is also logged to the run console. ZIP mode only. Solves the recurring confusion where users widen filters expecting more results and keep getting the same count.
- **Input schema** — `zillowUrl` field description rewritten to explain the search-URL requirement (must contain `searchQueryState=`) and point single-property users to ZPID mode.

Same input shape, output columns, KVS records, and pricing.

### \[2.3.61] - 2026-05-11

#### Changed

- **README** — Dropped the entire `Tips & Best Practices` section (64 lines, 4 sub-sections). Same input, output, and pricing.

### \[2.3.60] - 2026-05-11

#### Changed

- **README** — Dropped the standalone `Choose your mode` (28 lines, 3 sub-sections) and `How to use it (in one screen)` (9 lines) H2 sections. The three modes (ZIP / ZPID / URL) are now summarized in a compact 3-row table at the top of `What it does`. Trims 33 lines and 3 sub-sections; same input, output, and pricing.

### \[2.3.59] - 2026-05-08

#### Changed

- Maintenance build — no user-facing changes.

### \[2.3.58] - 2026-05-07

#### Changed

- **README** — refreshed structure for clarity (restored prior section layout). Same input, output, dataset shape, and pricing.

### \[2.3.57] - 2026-05-07

#### Changed

- **README** — aligned to Apify quality template. Hero rewritten with value-first prose and a "beyond what Zillow's official API offers" comparison; numbered 3-step Quick start; new `🛟 Support & feedback` section pointing to Apify Store reviews / bookmark / Issues tab; promoted disclaimer to dedicated `⚖️ Is it legal to scrape Zillow?` H2 with link to Apify's web-scraping legality blog; FAQ expanded to 12 Q\&As (including legality, ban risk, data freshness, per-run cost cap); output sample trimmed to the 25 most-used keys with a "+49 more in the dataset" note; tightened Tips & Best Practices to 4 sub-sections × 3 bullets. No functional changes — same input, output, dataset shape, and pricing.

### \[2.3.56] - 2026-05-07

#### Changed

- Top-level **input-schema description** reformatted to a 3-bullet layout — each step on its own line, bold labels (Choose Operation Mode | Inside Selected Mode | Tuning), and a link to the README's Tips & Best Practices anchor for cost / performance / data-quality guidance.

#### Fixed

- README — removed an unverified claim that runs go faster during off-peak hours from the Tips & Best Practices Performance section. The actor's runtime isn't tied to time-of-day; the surviving performance guidance (per-request timeout, ZIP-batch dedup) lives under a renamed **Batching & Bounds** sub-block.

### \[2.3.55] - 2026-05-07

#### Changed

- Top-level **input-schema description** rewritten to mirror the README's new "How to use it" decision flow (Choose Operation Mode → fill mode-specific input → tune ⚙️ Enrichments + 🔬 Filters). Customers and AI agents reading the schema now see the same 3-step framing as the README, instead of a flat enumeration of modes. Length 322 → 438 chars (in band, ≤500). No code or input-field changes.

### \[2.3.54] - 2026-05-07

#### Changed

- README — added a **🧭 How to use it (in one screen)** decision flow above the per-mode deep dives (Choose Operation Mode → Inside Selected Mode → Tuning) plus a full **💡 Tips & Best Practices** section before the FAQ (4 sub-sections: Getting Maximum Results, Cost Optimization, Performance, Data Quality). Customers were getting lost between ZIP / ZPID / URL modes — the new top-of-page flow makes the mode-decision and the per-mode required fields visible in one screen, and the Tips block consolidates cost-capping, sold-listings-defaults, nullable-column, and `SKIPPED_ITEMS` patterns that previously lived only in inline tips. No code changes.

### \[2.3.53] - 2026-05-07

#### Fixed

- `photos[]` column was returning only the main listing photo (single-element array) when `enrichPhotos: true` was set. Root cause: the photo gallery arrived in a different shape than the actor expected, so only the main photo was kept. Customers toggling the flag have been getting `[mainPhoto]` instead of the full gallery since the feature shipped. The actor now reads the gallery in either shape and returns every photo.

### \[2.3.52] - 2026-05-07

#### Fixed

- `walkScore`, `transitScore`, and `bikeScore` columns are no longer silently null when `enrichWalkScore` is enabled. The actor was reading the three scores under the wrong names, so all three columns had been emitting null on every enriched row since the feature shipped. They now fill in whenever scores are available for the property.

### \[2.3.51] - 2026-05-07

#### Changed

- Related actors section now lists the full Zillow family — `zillow-url-search` (paste any Zillow search URL) and `zillow-mcp-server` (MCP integration for Claude Desktop / Cursor / ChatGPT) added alongside the existing `zillow-zip-search` row.

### \[2.3.50] - 2026-05-06

#### Changed

- **Related actors section now lists only Zillow-domain actors.** Replaced the cross-domain narrative paragraph (Instagram / TikTok / YouTube author-profile pitch) with a 2-row table that surfaces the lighter single-mode `zillow-zip-search` spinoff as the natural alternative for users who only need ZIP-code input. Discoverability win for both actors — visitors landing on the multi-mode parent now see the simpler ZIP-only option without leaving the page.

### \[2.3.49] - 2026-05-06

#### Fixed

- **Per-field output documentation now visible in the Apify Console and to API/MCP clients.** The 74-column dataset schema (titles, descriptions, types, examples for every output field) shipped with 2.3.43 had been silently ignored because `.actor/actor.json` carried an inline display-only block instead of pointing to `./dataset_schema.json`. AI agents reading the dataset shape via API and visitors browsing the Console dataset view now see the same per-column documentation that's always been on disk.
- **Widened `lotAreaValue` and `daysOnZillow` type unions to include `string`.** Both columns occasionally come back from Zillow as pre-formatted strings instead of raw numbers; the previous integer/number/null union rejected those rows, producing zero dataset items in the brief window after the wiring fix above and before this build. No change to dataset shape — strings have always been emitted in those columns when Zillow reported them that way.

### \[2.3.47] - 2026-05-06

#### Added

- **Test-run cap on demo input.** ZIP-mode runs that leave the ZIP code at the example value `90210` and don't set any filters now cap output at 10 properties (~$0.80 max on FREE tier, ~$0.50 on DIAMOND), making "Try" runs predictable on any plan. Previously, paid customers experimenting with the default seed could trigger uncapped enrichment of however many listings 90210 currently returns ($4–14+ per trial). A console banner explains the cap, and a `USER_MESSAGE` Storage record with id `test_run_default_input_v1` makes the same explanation visible to API-origin runs that never open the run console.
- The cap is restored to the normal plan limit (paid: unlimited; free: 15 properties / run) as soon as you change the ZIP code, add at least one filter, or switch to ZPID list / Zillow URL mode.

### \[2.3.46] - 2026-05-06

#### Fixed

- **Same property no longer appears multiple times in the dataset** when input ZIPs resolve to overlapping locations. Previously, ZIP-code mode runs whose input ZIPs all mapped to the same city (e.g., 20 Manhattan ZIPs → "New York, NY") fired the same search query once per batch of 5 ZIPs and re-emitted the same listings each time, charging the per-property paid event multiple times for one property. The actor now (a) skips a batch entirely when its ZIPs are already covered by an earlier batch's resolved location, and (b) tracks already-processed zpids and skips re-enrichment if the same property is returned by two different searches.
- ZPID-mode runs that pass the same `zpid` more than once (e.g., a list with accidental duplicates) now charge & emit the property once instead of once per occurrence. Duplicate ZPIDs are recorded in `SKIPPED_ITEMS` with category `duplicate`.

### \[2.3.45] - 2026-05-05

#### Fixed

- URL-mode runs no longer hang for the full platform timeout when a listings request stalls. Each fetch attempt is now bounded at 15 seconds so transient slowdowns surface as a normal retry/error instead of a 12-minute run abort. Same bounding applies to ZIP / ZPID modes, where such stalls were rarer.

### \[2.3.44] - 2026-05-05

#### Fixed

- ZIP-code mode now reliably returns listings for every covered ZIP. Previously, ZIP codes that the listings search did not recognize as a "named place" (most rural and small-town ZIPs, e.g. `20101` Aldie VA, `60019` Des Plaines IL) silently returned zero results — the run completed without errors but produced an empty dataset. The actor now resolves each ZIP to its city/state via a bundled US locality database before searching, and reads both full and minimal search results. Customers using these ZIPs will start getting data on their next run.
- ZIPs that aren't in the US locality database (rare P.O.-only or unassigned codes such as `10809`, `11617`, `12425`) are now recorded in the `SKIPPED_ITEMS` storage record with category `not_resolvable` and a clear instruction to use a wider area / city name, instead of silently returning empty.

### \[2.3.43] - 2026-05-04

#### Changed

- README restructured to the LLM-discoverability template (16 sections, ~370 lines). Added an Apify status badge, an output sample inside the first 25 % of the page, a pay-per-event pricing table (replacing the older Compute-Units prose), a Free-vs-paid table, dedicated Resume / Storage Records / Programmatic-API sections, and a tighter Related-actors pointer.
- Every output column now has a per-field description, type, and example available in the Apify Console's dataset view and to clients that read the actor schema programmatically — covers all 74 emit-able columns across the 3 operation modes (ZIP / ZPID / URL).
- Top-level Input description rewritten to lead with the 3-mode positioning and pay-per-event pricing model so agent clients pick the right mode without reading the full schema.
- SEO description tightened on the Apify Store page (no functional change).

### \[2.3.42] - 2026-05-03

#### Fixed

- The `USER_MESSAGE` Storage record (welcome banner on a paid user's 1st run, check-in on the 3rd) now reliably lands. Previously it silently degraded to a no-op due to a per-actor named-storage scoping issue specific to this account.

### \[2.3.41] - 2026-05-03

#### Added

- New `SKIPPED_ITEMS` Storage record listing every zpid / ZIP / URL skipped during the run, with reason and category (`filter`, `not_found`, `error`, `free_limit`). Open the Storage tab → `SKIPPED_ITEMS` to see exactly which items hit which reason, and which ones errored (re-run those — most errors are transient). The Log also prints up to 3 examples per category at the end of the run.
- New `FREE_LIMITS_APPLIED` Storage record on free-plan runs that hit any of the free-tier ceilings (15-property cap, 20-ZIP cap, $1.20 budget, agent-email masking). Each entry has a stable `id` code for programmatic consumption. Empty/omitted on paid runs.
- New `USER_MESSAGE` Storage record on a paid user's 1st and 3rd paid run — short onboarding/check-in tips, also embedded in `RUN_SUMMARY`.

### \[2.3.38] - 2026-05-01

#### Fixed

- Empty mode-specific input fields (ZIP Codes / ZPID List / Zillow Search URL) now exit with a "Configuration Error" stop reason instead of an unhandled error message, so it's clearer that the run was rejected due to input rather than an actor crash. Run still ends with FAILED status — only the message text changes.

### \[2.3.37] - 2026-04-30

#### Fixed

- Lifted the hard cleanup deadline from 30 seconds to 60 seconds. With every finalize step now bounded by 2.3.36's 3-second timeouts, normal post-processing budget is roughly 12 seconds of bounded finalize plus the existing 20-second completed-screen display, which was bumping into the 30-second deadline; the 60-second window leaves comfortable headroom while still terminating any truly stuck run well before the 180-second platform timeout.

### \[2.3.36] - 2026-04-30

#### Fixed

- Bound every async finalization step (run-summary write, run-metadata fetch, first-run timestamp, analytics POST, status-page write) with a 3-second timeout each. Previous attempts assumed only the non-default key-value-store helpers stalled, but logs showed finalization hanging silently before those calls; bounding all of them ensures the run reliably exits within ~25 seconds of "Processing complete!" without falling back to the force-exit safety net.

### \[2.3.35] - 2026-04-30

#### Fixed

- Bounded the non-default key-value-store calls inside the analytics helpers with a 3-second timeout each, so they fail fast under `LIMITED_PERMISSIONS` instead of hanging silently.

### \[2.3.34] - 2026-04-30

#### Fixed

- Added a 30-second cleanup safety net so runs always terminate, even if the post-run analytics or key-value-store calls stall silently. Prevents the run from hitting the 180-second platform timeout when finalization paths hang on permission errors.

### \[2.3.33] - 2026-04-30

Maintenance build — no user-facing changes.

### \[2.3.32] - 2026-04-30

#### Fixed

- Followup to 2.3.31: closing the Live View server alone was not enough — keep-alive sockets from outbound HTTP fetches also held the event loop open. Added explicit `Actor.exit(0)` after the completed-screen window so the run terminates promptly. Completed runs now show `SUCCEEDED` within ~25 seconds of "Processing complete!" instead of hitting the 180-second hard timeout.

### \[2.3.31] - 2026-04-30

#### Fixed

- Run no longer hangs ~160 seconds after "Processing complete!" before exiting. The Live View web-server's existing connections are now actively closed before shutdown, so runs terminate within ~20 seconds of completion (the existing completed-screen display window) instead of hitting the 180-second hard timeout. Affects user-visible run status: completed runs now show `SUCCEEDED` instead of `TIMED-OUT`.

#### Changed (internal)

- Analytics `runStatus` field is now computed locally (`SUCCEEDED`/`FAILED`) rather than read from the still-in-progress `runs/last` metadata, which was reliably empty during the analytics POST.

### \[2.3.30] - 2026-04-29

Maintenance build — no user-facing changes. Internal analytics fixes only;
input schema, output dataset columns, KVS records, console output, error
messages, defaults, and pricing are all unchanged from 2.3.29.
