A building permit is a clear sign that work is about to start. Someone has a project, a budget and a timeline. For roofers, solar installers, HVAC and pool companies, remodelers, material suppliers and equipment rental businesses, new permits are some of the most targeted leads around.
This guide covers where permit data comes from, why collecting it by hand is painful, and how to pull it from five major US cities with the Building Permits Scraper on Apify.
The problem
Many cities publish permits on open-data portals, but every city uses its own dataset, field names and update schedule. One city calls it "work description," another calls it "permit type," and valuation fields vary in format. Comparing or combining cities means cleaning up each source separately.
The manual way (and why it hurts)
You could visit each city's portal, filter by issue date, export a CSV, and then line up the columns in a spreadsheet. Then you'd search the descriptions for "roof" or "solar" and repeat it all next week. Some portals limit exports or time out on large date ranges, and a few publish several days late, so it's easy to miss permits.
The sources
The Actor reads official, public open-data portals that update daily:
| City | Portal |
|---|---|
| Chicago | data.cityofchicago.org Building Permits |
| New York | data.cityofnewyork.us DOB NOW Build Approved Permits |
| Los Angeles | data.lacity.org Building Permits Issued 2020-present |
| Austin | data.austintexas.gov Issued Construction Permits |
| Seattle | data.seattle.gov Building Permits |
Everything is normalized into one schema.
Step 1: Configure the input
Open the Actor page and click Try for free. The defaults:
{
"cities": [
"chicago",
"new-york",
"los-angeles",
"austin",
"seattle"
],
"daysBack": 7,
"maxItemsPerCity": 20
}
Useful options:
-
cities: any ofchicago,new-york,los-angeles,austin,seattle. -
daysBackordateFrom/dateTo: the issue-date window. -
keywords: match any word in the work description, for example["ROOF", "SOLAR", "POOL"]. Matching isn't case-sensitive. -
minValuation: a minimum reported job value. Permits without a value are excluded. -
maxItemsPerCity: a per-city cap, newest first.
Step 2: Read the output
| city | permitNumber | issueDate | permitType | address | valuation |
|---|---|---|---|---|---|
| Chicago | 101083682 | 2026-10-28 | PERMIT - RENOVATION/ALTERATION | 2620 W 51ST ST | 15000 |
| Chicago | 101084071 | 2026-10-28 | PERMIT - NEW CONSTRUCTION | 1733 W JULIAN ST | 475000 |
| Chicago | 101084225 | 2026-10-28 | PERMIT - RENOVATION/ALTERATION | 13122 S ELLIS AVE | 14500000 |
Full records also include status, applied date, work type, description, ZIP, latitude and longitude, and contractor, owner or applicant details where the city publishes them.
Not every city publishes every field:
- Contractor phone is only available for Austin.
- Owner name is only available for Chicago and New York.
- Los Angeles doesn't publish contractor names, and Chicago doesn't publish ZIP codes.
- Valuation is whatever the applicant reported, and some cities use placeholders like 0 or 1.
- Los Angeles usually runs three to five days behind.
Step 3: Schedule it
A one-off export is useful, but the real value is getting new records on a timetable without thinking about it.
- In Apify Console, open Schedules and click Create new.
- Set the cron expression. For example,
0 6 * * *runs it every morning at 6:00. - Add the Actor (Building Permits Scraper) and paste the same input JSON you used above.
- Optional: under the Actor's Integrations tab, connect Google Sheets, Slack, Zapier, Make or a webhook so each finished run lands where your team already works.
For a trade-specific feed, save one schedule per trade, such as a roofing schedule with keywords: ["ROOF"] and a solar schedule with keywords: ["SOLAR"]. Use a daysBack of a few days so late-publishing cities are still covered, and de-duplicate on permitNumber.
Step 4: Run it from Python with apify-client
If you'd rather pull the data into your own scripts or CRM, use the official apify-client package (pip install apify-client). Grab your API token from Settings > API & Integrations in Apify Console and keep it in an environment variable rather than in code.
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run_input = {
"cities": [
"austin",
"seattle"
],
"daysBack": 3,
"keywords": [
"ROOF",
"SOLAR"
],
"minValuation": 10000,
"maxItemsPerCity": 50
}
run = client.actor("perskekits/building-permits-multi-city").call(run_input=run_input)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
print(item.get("city"), item.get("permitNumber"), item.get("permitType"), item.get("valuation"))
Because each record has latitude and longitude, you can map the results or filter them to your service area before sending them to your sales team.
What it costs
The Actor uses Apify's pay-per-event pricing, so you pay for results, not for server time:
- $0.005 per run start
- $0.005 per permit returned
For example, 100 permits cost about $0.505, and 1,000 permits cost about $5.01. Apify's free plan includes a small amount of monthly platform credit, which is enough to try small runs. Check the Actor's pricing tab for the current rates before you run anything large.
FAQ
Which cities are supported?
Chicago, New York City, Los Angeles, Austin and Seattle. You can request more cities on the Actor's Issues tab.
Can I filter by trade or job size?
Yes. Use keywords for the trade and minValuation for job size.
Why are some fields empty?
Each city publishes a different set of fields. Anything a city doesn't publish comes back as null.
How current is the data?
Most portals update daily. Los Angeles usually lags three to five days.
Is it legal to use permits for marketing?
Permits are public records. You're responsible for following CAN-SPAM, TCPA, do-not-call and any local rules when you contact owners or contractors.
Wrapping up
Permits tell you who's about to spend money on a building project. Instead of stitching together five portals, get one clean feed every morning. Try the Building Permits Scraper on Apify.
Top comments (0)