# Changelog of Google Maps Business Leads Scraper (`opspilot.cc/maps-business-leads-scraper`) Actor

- **URL**: https://apify.com/opspilot.cc/maps-business-leads-scraper/changelog.md
- **Full Actor documentation**: https://apify.com/opspilot.cc/maps-business-leads-scraper.md

## Changelog

All notable changes to this Actor are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

### \[1.2] - 2026-07-18

#### Added

- **Auto-fill defaults**: when `categories` or `locations` is empty or missing, the Actor auto-fills them with sensible sample values (`lawyer` + `Los Angeles,CA,United States`) so Apify automated-test runs (which often submit bare payloads) succeed without manual setup. `keywords` stays empty (optional). `language` defaults to `English`.
- New boolean input `autoFillDefaults` (default `true`). Set to `false` to require explicit input — validation will fail with a clear error if `categories` or `locations` are empty.
- New field `autoFilled` in the run summary listing which fields were auto-populated.

#### Changed

- `input_schema.json`: relaxed `minLength: 1` from `categories` / `locations` and dropped them from the `required` array, so empty submissions pass schema validation. The form labels changed from "Categories (required)" to "Categories" to reflect the new behavior.
- `validateInput()` retains the empty-field errors as a defense-in-depth check for cases where `autoFillDefaults: false` is set and a non-string is passed.

#### Verified

End-to-end test suite: 5 suites green (`integration`, `all-fields`, `free-tier`, `parse-location`, `auto-fill` with 6 cases incl. null / empty / whitespace-only / partial / full / opt-out). Smoke test against the real upstream with auto-filled defaults returns 200 businesses.

### \[1.1] - 2026-07-17

#### Fixed

- **API endpoint URL was a non-resolving placeholder (`maps.example.internal` / `api.maps-listing-source.com`)**. Replaced with the real upstream `https://api.dataforseo.com` so requests actually reach the data provider. (Internal code only — README, schema, store listing, and other user-visible surfaces remain free of any upstream-vendor references.)
- **Dropped `language_code` from outgoing request bodies**. The `business_data/business_listings/search/live` endpoint derives language from the resolved location; sending `language_code` was a leftover from an earlier draft and risked a 400 on strict parsers.
- **Better network-error diagnostics**: DNS / connection failures now print the underlying cause code (e.g. `ENOTFOUND`) so future outages are easier to diagnose from run logs.

#### Verified

End-to-end smoke test against the real upstream with the credentials baked into `actor.json`:

- `categories: ["pizza_restaurant"]` + `location_coordinate: 53.476225,-2.243572,10` → 200 items, sample `Stockport Pizza House` / place\_id `ChIJnXeOjX6ze0gRPQLyO7UuJ4s`.
- `categories: ["lawyer"]` + `location_name: "Los Angeles,CA,United States"` → 200 items, sample `Gonzalez Richard` / `1425 W Lugonia Ave, Redlands, CA 92374`.

### \[1.0] - 2026-07-17

#### Fixed

- **Environment variable names now match `actor.json`**. The runtime was reading `MAPS_API_LOGIN` / `MAPS_API_PASSWORD` from `process.env`, but the credentials are baked into `.actor/actor.json` as `API_EMAIL` / `API_PASSWORD`. Renamed all code references to match: code now reads `process.env.API_EMAIL` and `process.env.API_PASSWORD`. Tests updated accordingly.
- Error message in `MapsApiClient` constructor now reads `API_EMAIL / API_PASSWORD` instead of the old variable names.

### \[0.9] - 2026-07-17

#### Fixed

- **Free-tier key-value store now uses the Actor's default store** (`Actor.openKeyValueStore()` with no name argument). Previously the code opened a *named* external store `free-tier-limits` which required explicit permission grants and failed at runtime with `Insufficient permissions for the key-value store`. Switching to the default store (auto-permissioned, persists across runs of this Actor) removes the permission requirement entirely.
- **More accurate error messages** when the free-tier KV store is unreachable: distinguishes `KV permission denied`, `KV not found`, and generic `KV unavailable`, so support and ops can diagnose quickly.
- **Free-tier limit-reached error message** now reliably includes the `Free tier limit reached` substring (previously fell through to the KV-unavailable message because the limit-reached branch didn't set an `error` code).

### \[0.8] - 2026-07-16

#### Changed

- **Breaking**: Removed the `queries[]` array wrapper from the input schema. The Actor now accepts a **flat** input shape:
  - `categories` (string, textarea, required) — one category per line, max 10.
  - `locations` (string, textarea, required) — one location per line.
  - `keywords` (string, textarea, optional) — one keyword per line, max 20.
  - `language` (string, text field, optional) — defaults to `English`.
- **Behavior**: The Actor still expands every (location × category) pair into one upstream search task, with optional `keywords` joined into the task `title` / `description`. Want multiple search jobs? Submit multiple Actor runs — each run is one search job.
- **Schema fix**: The previous `queries[]` array of objects could not be rendered correctly by the Apify Console form (`schemaBased` editor does not support `title` / `editor` / nested `properties.title` inside `items`). Flattening makes the form render reliably across Console and API clients.
- `src/main.js`:
  - `validateInput()` rewritten to validate flat fields.
  - `expandTasks()` now returns `{ tasks, cats, locs }` so the run summary can report input dimensions.
  - Run summary now includes `categories` and `locations` counts instead of a `queries` count.

#### Why

- Apify Console could not validate `queries[]` of objects — users saw `should be string` errors on every entry even when the payload was valid. Flattening the schema eliminates the class of form-validation bugs while keeping the underlying task expansion identical.

#### Migration

- Old input:
  ```json
  {
    "queries": [
      { "categories": "lawyer", "locations": "Los Angeles,CA,USA" }
    ]
  }
  ```
- New input:
  ```json
  {
    "categories": "lawyer",
    "locations": "Los Angeles,CA,USA"
  }
  ```

### \[0.7] - 2026-07-16

### \[0.5] - 2026-07-16

#### Changed

- **Breaking**: `queries[].categories`, `keywords`, `locations` changed from
  `stringList` (one input box per item) to `string` with `textarea` editor.
  Users now paste one value per line into a single text box.
- `main.js` adds `splitLines()` to parse newline-separated values.
- `validateInput` updated to check non-empty lines and category cap.

#### Fixed

- Resolved user confusion in Apify Console: `stringList` rendered each item as
  a separate input field, causing users to fill each row with one character
  instead of a complete value. `textarea` shows a single multi-line box that
  matches the mental model "one value per line".

### \[0.4] - 2026-07-16

#### Changed

- **Breaking**: `queries[].locations` simplified from array of `{name|coordinate}`
  objects to a flat array of strings. Each string is auto-detected as either a
  city name (`"Los Angeles,CA,United States"`) or GPS coordinates
  (`"34.0522,-118.2437,25"`).
- **Removed**: `queries[].filters` and `queries[].order_by` input fields.
  The Actor no longer exposes advanced filtering / sorting controls. Use multiple
  queries with different categories / keywords / locations to broaden results.
- Validation logic simplified to check `locations[i]` is a non-empty string.

#### Fixed

- Resolved Apify Console schema validation error
  `"queries.0.locations" should be array` when users submitted coordinate strings
  in the Bulk edit view of an object-typed array.

### \[0.3] - 2026-07-16

#### Changed

- API credentials moved from `@secretName` references to inline values
  directly in `actor.json` (`API_EMAIL`, `API_PASSWORD`, `RESEND_API_KEY`).
- README simplified: "No setup required — credentials are bundled."

#### Added

- Output schema file (`.actor/output_schema.json`) following the official
  [Actor output schema](https://docs.apify.com/platform/actors/development/output-schema)
  specification. Exposes two output references:
  - `results` → default dataset items (one business per row).
  - `summary` → default Key-Value Store `SUMMARY` record.

### \[0.2] - 2026-07-16

#### Changed

- Aligned Actor configuration with official `actor.json` specification.
  - Removed non-standard fields (`categories`, `shortDescription`, `defaultRunOptions`).
  - Promoted memory settings to top-level `defaultMemoryMbytes` / `minMemoryMbytes` / `maxMemoryMbytes`.
  - Added explicit `readme` and `buildTag` fields.
- Simplified `main.js` entry point. `Actor.main()` now wraps the entire flow
  and SDK initialization/exit is handled internally per Apify SDK conventions.
- Replaced ad-hoc `await Actor.exit(msg)` calls with `throw new Error(msg)`
  so failed runs properly enter the `FAILED` terminal state on the platform.

#### Improved

- Dockerfile: split dependency install into its own layer using
  `COPY package.json package-lock.json` + `npm ci` for better build cache hits.
- Source code: all user-visible references to upstream data provider removed
  (README, input schema, package description, secrets naming).

### \[0.1] - 2026-07-16

#### Added

- Initial public release.
- Search Google Maps business listings by category, keyword, and location.
- Multi-language support: 18 languages via English name, ISO 639-1 code, or local script.
- Ten output field groups with on/off toggles (identity, contact, rating, hours,
  popular times, services, attributes, topics, media, links).
- Three-tier deduplication: `place_id` → `cid` → text fallback.
- Free-tier rate limiting (5 runs/day per account) via shared Apify KV store.
- Pay-per-event billing (`query-task`, `business-result`).
- Auto-pagination via `offset_token` (up to 200 results per task, 50 pages cap).
- Retry with exponential backoff on 5xx, network errors, and 429 rate limits.
- Token-bucket rate limiter (1500 calls/min, under upstream's 2000/min limit).
