DEV Community

Loomwise
Loomwise

Posted on

Find Newly Licensed Contractors in Oregon and Connecticut for Free (Python + State Open Data)

New contractor businesses buy a lot in their first months: insurance, bonds, trucks, tools, software, accounting and supplies. Several states publish their contractor license data as free open data, updated daily, with no login and no scraping. This post shows how to pull every new contractor business licensed in the last 30 days in Oregon and Connecticut with one short Python script, using only official state APIs.

Run on Oct 22, 2026, the script found:

  • Oregon: 495 new contractor businesses in 30 days. 275 were residential general contractors, 67 lead-safe renovators and 53 residential specialty contractors. The top counties were Multnomah (62), Washington (56), Marion (48) and Clackamas (40), and 84 were out-of-state firms entering Oregon.
  • Connecticut: 285 new contractor business licenses. 237 were home improvement contractors, 28 new home construction contractors, 15 major contractors and 3 demolition contractors.

The data sources

State Agency Dataset Notes
Oregon Construction Contractors Board (CCB) CCB Active Licenses Public domain. Includes the original registration date, phone, bond and insurance carrier
Connecticut Dept. of Consumer Protection State Licenses and Credentials Public domain. Covers every DCP license, not just contractors, with an entity type field

Both run on Socrata, so you query them with plain HTTP and SoQL.

The script

import requests, re
from datetime import date, timedelta
ENTITY = re.compile(r"\b(LLC|L\.L\.C\.|INC|CORP|CORPORATION|CO|COMPANY|LTD|LLP|LP|PLLC|PC)\b\.?", re.I)

def ct_new_contractors(days=30):
    since = (date.today() - timedelta(days=days)).isoformat()
    rows = requests.get("https://data.ct.gov/resource/ngch-56tr.json", params={
        "$select": "businessname,name,type,credential,fullcredentialcode,status,issuedate,city,zip",
        "$where": (f"issuedate >= '{since}' AND issuedate <= '{date.today()}T23:59:59' "
                   "AND upper(credential) like '%CONTRACTOR%' "
                   "AND type in ('LIMITED LIABILITY COMPANY','CORPORATION','BUSINESS','PARTNERSHIP')"),
        "$order": "issuedate DESC", "$limit": 5000}, timeout=120).json()
    return rows

def or_new_contractors(days=30):
    since = date.today() - timedelta(days=days)
    rows = requests.get("https://data.oregon.gov/resource/g77e-6bhs.json", params={
        "$select": "license_number,license_type,endorsement_text,full_name,city,county_name,orig_regis_date,ins_company,ins_exp_date",
        "$where": f"orig_regis_date like '%/{since.year}'", "$limit": 50000}, timeout=120).json()
    out = []
    for r in rows:
        m, d, y = map(int, r["orig_regis_date"].split("/"))
        if date(y, m, d) >= since and ENTITY.search(r.get("full_name", "")):
            out.append(r)
    return sorted(out, key=lambda r: r["orig_regis_date"][-4:] + r["orig_regis_date"][:5], reverse=True)

ct, orr = ct_new_contractors(), or_new_contractors()
print(len(ct), "new CT contractor businesses;", len(orr), "new OR contractor businesses")
Enter fullscreen mode Exit fullscreen mode

Three things that will trip you up

  1. Oregon stores dates as MM/DD/YYYY text. You can't run a date range query on the server. The script narrows by year with like '%/2026', then filters the exact window in Python. (For windows that cross New Year, add the previous year too.)
  2. Connecticut has some future issue dates (data-entry errors such as 2027-03-31). Add an upper bound of today, as the script does.
  3. Keep it to businesses. Both datasets include sole proprietors, whose names are personal names. Connecticut has a type column, so filter to LLCs, corporations and partnerships. Oregon doesn't, so keep only names with an entity marker (LLC, Inc, Corp...) and never pull the responsible-individual column. That's better for privacy, and businesses are the leads you want anyway.

Turning it into a weekly lead list

  • Run it weekly with days=7, de-duplicate on license number, and append to a sheet or your CRM.
  • Oregon's ins_company and ins_exp_date are useful for insurance agents: sort by expiration date for renewal timing.
  • In Connecticut, change the credential filter to '%LIQUOR%' or '%FOOD%' and the same script finds new restaurants and bars.

Optional: if you'd rather not maintain the script, I publish both as hosted, scheduled tools: Oregon CCB Contractor License Scraper and Connecticut License Lookup (paid per result). Disclosure: they're my tools. The code above does the same job for free.

Top comments (0)