Washington's Department of Labor & Industries (L&I) licenses every construction contractor in the state, and publishes the full license file as open data on data.wa.gov, with each contractor's specialty, business type, city and license dates. Filter it by specialty and you get a list of brand-new trade businesses: exactly who equipment suppliers, insurance agents, bonding companies, fleet and uniform vendors, and trade-software companies want to reach.
This post shows how to pull new Washington contractor businesses by trade with one Python script, using only the official API.
Run on Oct 22, 2026, for licenses that took effect in the last 90 days (businesses only):
- Landscaping and tree service: 72, led by Seattle (8) and Tacoma (5)
- Painting: 66, led by Tacoma and Vancouver (4 each)
- HVAC/R: 48, spread out with Washougal and Spokane Valley on top (3 each)
- Roofing, gutters and siding: 34, led by Spokane (4), and Portland-based firms licensing into Washington (3)
The data source
L&I Contractor License Data - General on data.wa.gov (Socrata). It has about 160,000 licenses, with business name, license number, specialty code, business type, address, phone, effective and expiration dates, and status.
The script
import requests from collections import Counter from datetime import date, timedelta since = (date.today() - timedelta(days=90)).isoformat() TRADES = { # WA L&I specialty descriptions, grouped by trade "roofing": ["ROOFING", "GUTTERS/DOWNSPOUTS", "Siding"], "hvac": ["Heating/Vent/Air-Conditioning and Refrig (HVAC/R) ", "HVAC/RFRG"], "painting": ["PAINTING/WALLCOVERING"], "landscaping": ["LANDSCAPING", "Tree Removal Service"], } BUSINESS_TYPES = ("Corporation", "Limited Liability Company", "Partnership", "Limited Liability Partnership") SELECT = ("businessname,contractorlicensenumber,specialtycode1desc,businesstypecodedesc," "city,zip,licenseeffectivedate,licenseexpirationdate,contractorlicensestatus") # no principal names def new_contractors(specialties): where = (f"licenseeffectivedate >= '{since}' AND contractorlicensestatus = 'ACTIVE' " f"AND specialtycode1desc in ({','.join(repr(s) for s in specialties)}) " f"AND businesstypecodedesc in ({','.join(repr(b) for b in BUSINESS_TYPES)})") return requests.get("https://data.wa.gov/resource/m8qx-ubtq.json", params={ "$select": SELECT, "$where": where, "$order": "licenseeffectivedate DESC", "$limit": 5000}, timeout=120).json() for trade, specs in TRADES.items(): rows = new_contractors(specs) print(f"{trade}: {len(rows)} WA businesses licensed since {since}") print(" top cities:", Counter(r.get("city", "").title() for r in rows).most_common(5)) Three things that will trip you up
- Specialty labels are inconsistent. HVAC appears both as
Heating/Vent/Air-Conditioning and Refrig (HVAC/R)(with a trailing space) and asHVAC/RFRG. Get the real values first with$select=specialtycode1desc,count(*)&$group=specialtycode1desc, then copy them exactly. - Plumbers and electricians are a different license type. They're filed under
contractorlicensetypecodedesc=PLUMBING CONTRACTORorELECTRICAL CONTRACTOR, not under a specialty. Filter on that column for those trades. - Leave out individuals and principals. About 20% of licenses are held by individuals (sole proprietors), and the file has a principal-name column. The script keeps only corporations, LLCs and partnerships, and never selects the principal column. You're marketing to businesses, so that's all you need.
Ideas
- Shorten the window to 7 days and run it weekly for a fresh new-business feed.
- Watch
licenseexpirationdateto reach contractors before their bond and insurance renewals. - Cross-check any business on L&I's own verify page (
https://secure.lni.wa.gov/verify/Detail.aspx?LIC=<license number>).
Optional: I run this as hosted, scheduled searches: New WA Roofing Contractors and New WA HVAC Contractors (ready-made searches, paid per result). Disclosure: they're my tools. The script above does the same job for free.


