DEV Community

Loomwise
Loomwise

Posted on

California Solar Permit Leads: Pull Every New Solar Install from County Open Data (Python)

Every rooftop solar system in California needs a building permit, and several California counties and cities publish those permits as free open data, updated daily. That makes permit data the most reliable signal of who just went solar: useful for battery and EV-charger installers, roofers, solar cleaning and monitoring services, and anyone selling to solar contractors.

This post shows how to pull every solar permit issued in the last 30 days from official open-data APIs, with one short Python script. No login, no scraping, no API key.

Run on Oct 22, 2026, the script found:

  • San Diego County (unincorporated): 260 solar permits in 30 days, mostly residential roof-mount PV through the county's online "HRA" express permit, plus ground-mount, commercial and battery (energy storage) permits.
  • Corona, CA: 32 solar permits, almost all labeled "SOLAR WITH ESS" (solar plus a home battery).

The data sources

Area Publisher Dataset What's in it
San Diego County (unincorporated) Planning & Development Services Building permits Record type, scope code, work description, address, valuation
Corona, CA City of Corona Permits Permit type and subtype, description, job value, address

Both run on Socrata, so you query them with plain HTTP and SoQL (a SQL-like query language passed as URL parameters).

The script

import requests
from datetime import date, timedelta
since = (date.today() - timedelta(days=30)).isoformat()
SOURCES = [  # (label, domain, dataset, date field, text fields to search)
    ("San Diego County", "data.sandiegocounty.gov", "red2-qccu", "issued_date", ["use", "primary_scope_code", "record_category"]),
    ("Corona", "corstat.coronaca.gov", "2agx-camz", "issued", ["description", "permitsubtype"]),
]
def solar(label, domain, ds, dfield, fields):
    like = " OR ".join(f"upper({f}) like '%SOLAR%' OR upper({f}) like '%PHOTOVOLTAIC%' OR upper({f}) like '%PV%'" for f in fields)
    r = requests.get(f"https://{domain}/resource/{ds}.json", params={
        "$where": f"{dfield} >= '{since}' AND ({like})", "$order": f"{dfield} DESC", "$limit": 5000}, timeout=120)
    r.raise_for_status(); return r.json()
for s in SOURCES:
    rows = solar(*s)
    print(f"{s[0]}: {len(rows)} solar permits since {since}")
    for r in rows[:3]:
        print("  ", r.get("issued_date") or r.get("issued"), r.get("full_address") or r.get("site_addr"), "|", (r.get("use") or r.get("description") or "")[:70].replace("\n", " "))
Enter fullscreen mode Exit fullscreen mode

Three things that will trip you up

  1. "Solar" isn't always in the same column. San Diego County puts the clearest signal in primary_scope_code (for example 8002 - REN - Solar Photovoltaic Roof Mount Residential - Online), while the free-text use column holds the work description. Search both, as the script does.
  2. Search for PV, not just "solar". Many permits say "PV" or "photovoltaic" and never use the word solar. The script matches %PV% too. Spot-check the results: in a few datasets PV can show up inside unrelated words.
  3. Batteries are their own leads. San Diego County issues separate "Advanced Energy Storage System" permits. If you sell batteries or backup power, search for ENERGY STORAGE or ESS on their own.

Turning it into a lead list

  • Run it weekly with timedelta(days=7) and de-duplicate on the permit number.
  • New solar owners are a fit for battery add-ons, critter guards, panel cleaning, monitoring and home insurance reviews. Mail or door-hang by address. Don't try to look up the homeowners: the address and permit are enough.
  • To find the busiest installers, group by the contractor column where a city publishes one.
  • The same pattern works anywhere a city publishes permits on Socrata: change the domain, dataset ID and field names.

Optional: if you'd rather not maintain this, I run it as a hosted, scheduled search covering California and other states: California Solar Permits (ready-made search) (paid per result). Disclosure: it's my tool. The script above does the same job for free.

Free sample data

See the fields before you run anything:

Top comments (0)