# Changelog of Domains by Technology Analyzer (`opspilot.cc/domains-by-technology-analyzer`) Actor

- **URL**: https://apify.com/opspilot.cc/domains-by-technology-analyzer/changelog.md
- **Full Actor documentation**: https://apify.com/opspilot.cc/domains-by-technology-analyzer.md

## Changelog

All notable changes to **Domains by Technology Analyzer** are documented here.
Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

### \[0.2.1] - 2026-07-20

#### Changed

- **Per-run limit locked at 100** (was user-configurable up to 1,000). Users paginate by running again with the previous run's `_nextOffset` — simpler UX, predictable per-run cost, matches upstream's typical page size.

#### Fixed

- **Upstream `offset + limit <= 10000` hard ceiling** — discovered via smoke test: `offset: 787800, limit: 100` returns `Invalid Field: 'limit'`. This is an upstream limit, not a bug in the Actor. We now cap `offset` at 9,900 in the input schema and short-circuit the run with an empty result + `cappedByUpstream: true` if the caller exceeds it. Max reachable pages per query = 99 × 100 = 9,900 items.

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

#### Fixed

- **Pagination regression** — `limit` was hardcoded to 100 in `main.js:153`, so every run returned at most one upstream page regardless of user input. Replaced with a cursor-based loop that follows `offset_token` until the requested `limit` is reached (max 1,000 per run). Repro: pre-v0.2 with `limit: 500` returned 100 rows; v0.2 returns up to 500. Covered by `test/pagination.js` (6 scenarios including a 4-page 1,000-item walk).
- **Offset=0 silent skip** — discovered during smoke testing: initial implementation only called upstream when `offset > 0`, so the default `offset=0` path never made a request. Fixed by restructuring the loop to always fetch at least one page and follow the upstream cursor thereafter. Verified: `limit: 5` → 1 upstream request → 5 rows (pre-fix would have returned 0 rows).

#### Added

- **Input field `limit`** — integer, default 100, min 1, max 1000, `editor: "number"`. The previous version silently dropped this field because the schema did not expose it.
- **Input field `offset_token`** — string, opaque cursor returned by upstream from a previous run. Allows resuming a paginated run from where it left off. Numeric offsets are NOT supported by the upstream API (verified via real smoke test — passing `"offset_token": "900"` returns `Invalid Field: 'offset_token'`).
- **Per-row provenance**: every dataset row now carries `_query` (echo), `_totalCount`, `_pagesFetched`, `_collected`, and `_lastOffsetToken` so downstream consumers can detect partial vs. complete runs AND resume paginated runs by passing the token back as input.
- **Run SUMMARY**: stored at the `SUMMARY` key in the default KV store (per skill §3 output\_schema template).
- **PPE billing**: one `domain-result` event per domain row for paying users (`APIFY_USER_IS_PAYING === '1'`).
- **Free-tier counter** factored out to `src/lib/billing.js` using the Actor's default KV store (auto-permissioned).
- **Structured logger** factored out to `src/lib/logger.js` (mirrors `maps-business-leads-scraper` pattern).

#### Changed

- **Refactored into modules**:
  - `src/api_client.js` — DataForSEO HTTP client with retry policy (3 retries, 5xx/network/429 backoff; fail-fast on 4xx).
  - `src/lib/tech_lookup.js` — 812-entry alias table + `normalizeTechName()`.
  - `src/lib/logger.js` — structured logger that mirrors to `Actor.log`.
  - `src/lib/billing.js` — free-tier counter + PPE charging.
  - `src/main.js` — now 245 lines (down from 328), pure orchestration.
- **README** rewritten per the `apify-actor-dev` skill §11 SEO-friendly template (Why-Use / Quick-Start / Input-Reference / Output-Reference / Pagination / Pricing / FAQ).
- **Output schema** updated to the `actorOutputSchemaVersion: 1` shape with `template` URLs.
- **Country filter** now strictly enforces ISO-3166-1 alpha-2 (alpha-3 like `USA` is rejected).

#### Tests

- 49 assertions across 6 test files (`npm test`):
  - `normalize-tech.js` — alias lookup + case-insensitive matching.
  - `normalize-limit.js` — limit clamping, country regex validation.
  - `normalize-country.js` — placeholder (covered by `normalize-limit.js`).
  - `input-validation.js` — technology split/trim/dedupe.
  - `pagination.js` — 6 cursor-pagination scenarios (regression-locked).
  - `free-tier.js` — 5/day cap enforced via KV store.

#### Verified

- `npm test` — 49/49 pass.
- `apify validate-schema` — input + output schemas valid.
- Source modularity — `src/main.js` is now orchestration only.

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

#### Added

- Initial release. Single hardcoded `limit = 100`, no pagination, free-tier counter inline.
