DEV Community

Loomwise
Loomwise

Posted on Edited on

Texas HVAC and Electrical Contractor Lead List: 18,000 Licensed Businesses from TDLR Open Data (Python)

If you sell to trades (equipment distributors, insurance, software, staffing, training), Texas is the biggest market in the country, and its licensing agency publishes the whole register for free. The Texas Department of Licensing and Regulation (TDLR) license file on data.texas.gov is updated regularly and needs no API key.

When I ran this on Oct 23, 2026 it returned 9,249 A/C contractor businesses and 8,841 electrical contractor businesses (after dropping individual licensees). Harris County leads with 1,387 A/C contractors, then Dallas (876), Tarrant (601) and Bexar (523).

The script

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

def texas_contractors(license_type="A/C Contractor", county=None):
    where = f"license_type = '{license_type}'"
    if county:
        where += f" AND business_county = '{county.upper()}'"
    rows = requests.get("https://data.texas.gov/resource/7358-krk7.json", params={
        "$select": "license_type,license_number,business_name,business_county,business_city_state_zip,license_expiration_date_mmddccyy",
        "$where": where, "$order": "license_number DESC", "$limit": 50000}, timeout=120).json()
    return [r for r in rows if ENTITY.search(r.get("business_name", ""))]

types = requests.get("https://data.texas.gov/resource/7358-krk7.json", params={
    "$select": "license_type, count(*) AS n", "$group": "license_type", "$order": "n DESC", "$limit": 60}, timeout=120).json()
print([(t["license_type"], t["n"]) for t in types if "Contractor" in t["license_type"]])
ac = texas_contractors("A/C Contractor")
el = texas_contractors("Electrical Contractor")
print(len(ac), "A/C contractor businesses;", len(el), "electrical contractor businesses")
print("Top A/C counties:", Counter(r.get("business_county", "").title() for r in ac).most_common(5))
print(ac[:2])
Enter fullscreen mode Exit fullscreen mode

Swap the license type for Appliance Installation Contractor, Electrical Sign Contractor or Elevator Contractor, or pass county="Travis" to narrow down.

Things that will trip you up

  1. Individuals and businesses share one file. Technicians and journeymen are listed under their own names. Filtering business_name for LLC, Inc and similar markers keeps only companies. That's better for privacy, and companies are the buyers anyway.
  2. Expiration dates are MM/DD/YYYY text. Parse them in Python before sorting. A license expiring within 60 days is a renewal trigger, which is useful timing for insurance agents and CE course sellers.
  3. License numbers are strings, so $order sorts them as text. Higher numbers are generally newer licenses, but sort numerically in Python if it matters.
  4. "Out of State" is a real county value. Those are contractors based elsewhere who are licensed to work in Texas.

Turning it into a lead list

Export to CSV with csv.DictWriter, de-duplicate on license_number, and re-run monthly to catch new numbers. Pair it with a building permits feed to see which of these contractors are actually pulling jobs.


Optional: if you'd rather not maintain the script, I publish this as a hosted, scheduled tool: Texas Contractor Licenses (paid per result). I also publish Texas New Business Leads. Disclosure: they're my tools. The code above does the same job for free.

Free sample data

See the fields before you run anything:

Top comments (0)