# Changelog of Florida Contractor License Lookup & Verify (DBPR) (`scrapebench/florida-contractor-license-lookup`) Actor

- **URL**: https://apify.com/scrapebench/florida-contractor-license-lookup/changelog.md
- **Full Actor documentation**: https://apify.com/scrapebench/florida-contractor-license-lookup.md

## Changelog

### 0.4 - 2026-09-06

#### Added

- **Person-name search — you now find every contractor licence someone holds.** DBPR's Name
  search has always had two independent inputs, business (Org Name) and person (Last/First Name);
  this actor only ever sent the business one, so an individual licensee whose surname is not part
  of a business name was unreachable. A name query now runs both and unions the results, deduped
  by licence number — the same thing the California sibling does with CSLB's business and
  personnel indexes. Measured 2026-09-06: the surname ARIZA returns 2 contractor licences from the
  business index and **14 more** from the person index. `matched_by` says which index found a row.
- **Scoped to Florida's contractor boards.** DBPR's name search covers all **40** of its boards,
  and the actor was sending no board filter at all — so a *contractor* lookup returned, and billed
  for, cosmetologists, realtors, talent agencies and food trucks. Name searches now run against
  Construction Industry (CILB) and Electrical Contractors (ECLB). "ARIZA" went from 12 rows
  (2 contractors) to 16 rows (16 contractors); "SMITH, JOHN" reaches **zero** contractors on
  DBPR's first unfiltered page and 64 with the board set. A licence number you type is still
  looked up as given, on any board.

#### Fixed

- **A middle name no longer returns nothing.** DBPR matches First Name as a prefix of the first
  given name only, while *displaying* names as "ARIZA, JOHN MARIO" — so pasting a name straight
  out of DBPR's own results found 0 licences. Only the first given name is sent now:
  "ARIZA, JOHN MARIO" returns all three of his licences instead of none.
- **A common surname can no longer crowd out the person index.** The two indexes are interleaved
  rather than concatenated, so `maxResults` is split fairly between them — otherwise "SMITH"
  (3,457 business matches on one board) would spend the whole cap before a single person match
  was reached. Worth knowing: the California sibling still has this bug — 'Phil Ransome' at
  maxResults=5 returns five unrelated companies containing "Phil" and no personnel match at all.
- **`source_url` is a working link again.** Every detail-enriched row cited
  `.../VerifyLicensee/LicenseDetail?lic=<number>`, which DBPR answers with **HTTP 500** — verified
  on three live licences. DBPR keys its detail page on an opaque record ID
  (`?ID=28E60BE6…`), not on the licence number, so the audit link on every enriched row was dead.
  Rows now cite the URL the detail page was actually served at, which works cold with no session.

### 0.3 - 2026-08-21

#### Fixed

- **Name searches now return what you asked for.** `Maximum results` had no effect above 10 on a
  name search: the actor always requested a single 10-record page from DBPR, so asking for 50
  returned about 4 licences and asking for 1000 returned the same 4. It now requests as many
  records as your `Maximum results` setting wants, up to the 100 DBPR will serve in one page —
  measured on a search matching 22,979 licences, that is 4 results before and 54 after, in the
  same single request. Licence-number lookups are unaffected: they return the one record they
  always did.
- **Name searches now page.** A name search reads as many result pages as your `Maximum results`
  needs, so raising the cap genuinely returns more: on a search matching 22,979 licences, a cap of
  200 returns 200 and 1000 returns 1000. Licence-number lookups are one request as always.
- **The run log now tells you when a search matched more than you got back**, e.g. `returning 120
  of 22979 DBPR matches (read 3 of 230 pages)`. DBPR states the true total in its own response and
  the actor used to discard it, so a capped result looked exactly like a complete answer.

#### Changed

- The refusal guard also checks **where a response landed**, not only what it says. A portal that
  goes down by redirecting to a maintenance page on another domain serves a perfectly valid page
  with a 200 status; that now degrades to the usual free, unbilled "source unavailable" row
  instead of parsing as "no such licence".

### 0.2 - 2026-07-21

#### Fixed

- **A blank `query` no longer fails the run (#133).** 6 of 94 customer runs were failing with
  `ValueError` -> exit 91. `query` is `required` in the input schema, but JSON-Schema "required"
  only checks PRESENCE, so `{"query": ""}` sails through - which is exactly what an API caller
  sends when a spreadsheet cell is blank. Apify's "under maintenance" badge counts failed runs
  regardless of whose fault the input was, so a blank query now returns a clean 0-row run that
  charges nothing and says why in the log. `minLength: 1` on the schema stops the empty case
  before a run is even created.
- **Default run timeout raised 300s -> 3600s.** `maxResults` accepts up to 1000 and
  `includeDetails` is on by default, so a large lookup could not finish inside the inherited
  300s platform default (1 TIMED-OUT run in the same 30-day window).

### 0.1.1

- Fix intermittent run failures that drove the Store "under maintenance" flag (#46): a transient
  DBPR blip (e.g. `httpx.ReadTimeout`) that survived `request_with_retries` propagated out of
  `build_records` and failed the whole run. Now a still-failing search after retries degrades to a
  clean, charge-nothing 0-row run, and a detail-page timeout keeps the list-tier match instead of
  sinking the run. Non-transient errors (challenge page, 4xx, parse) still raise.

### \[0.1.0] - Unreleased

- Initial scaffold generated from the portfolio actor template (wiring only; sample data).
