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", " "))
Three things that will trip you up
-
"Solar" isn't always in the same column. San Diego County puts the clearest signal in
primary_scope_code(for example8002 - REN - Solar Photovoltaic Roof Mount Residential - Online), while the free-textusecolumn holds the work description. Search both, as the script does. -
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. -
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 STORAGEorESSon 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:
- Building permit leads: free sample CSV
- Lead lists for leads for solar, roofing and hvac companies
- All public-data lead lists <!-- /loomwise-data-links -->
Top comments (0)