Building Permits Scraper - Multi-City Construction Leads
Pricing
from $1.50 / 1,000 results
Building Permits Scraper - Multi-City Construction Leads
Building permits from dozens of city open-data portals in one run. Cities are discovered from the Socrata catalog, not a hand-kept list. Personal contact fields are dropped at the server.
Pricing
from $1.50 / 1,000 results
Rating
0.0
(0)
Developer
ABDULWAHAB NASER RASHED ALQARAWI
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
4 days ago
Last modified
Categories
Share
Building Permits — Multi-City Construction Leads
Building permits from dozens of city open-data portals in a single run.
Cities are discovered, not hard-coded. Every competing Actor on this data is wired to a fixed list somebody maintains by hand. This one queries the Socrata open-data catalog, reads each dataset's own field list, and builds the request from what that city actually publishes. A new city appears in the catalog and it is covered without a code change.
Personal data is dropped at the server
Raw permit payloads carry real people. Chicago's has ten contact columns
including contact_1_name; Calgary has applicantname.
Filtering those after download is not filtering — the data still travelled,
still sat in a dataset, still leaks on a crash. So every request sends an
explicit $select of the safe columns only, and the server never emits the
rest.
Verified on a 1,800-row run across six cities: zero personal fields present.
What you get
One row per permit, normalised across cities that name everything differently.
| Field | Coverage |
|---|---|
address | 99% |
permitType | 97% |
description | 93% |
declaredValue | 73% |
issuedAt | 66% |
city | 31% |
contractor | 22% |
Plus source, dataset, permitNumber, status, appliedAt, completedAt,
fee, state, postcode, latitude, longitude, scrapedAt — and raw,
which keeps every extra column that city publishes so a buyer who needs a
local field still has it.
Newest first. Without an explicit sort these portals return insertion order, which hands back permits from 2006 — worthless as construction leads. Results are ordered by issue date descending wherever the city publishes one, with the portal's row id as a tie-break so paging never repeats or skips a permit that shares its issue date with others.
Every value has its declared type. Portals publish location, link and
checkbox columns as objects and booleans; a text field gets a string or null,
a number field a finite number or null — never an object, NaN or Infinity.
Input
| Option | Default | What it does |
|---|---|---|
query | building permits | Searched against the Socrata catalog to find cities |
maxCities | 8 | City portals per run |
maxPermitsPerCity | 500 | Permits per city |
issuedSince | empty | YYYY-MM-DD. Anything else fails the run immediately with a clear message, instead of every city rejecting the filter |
requestDelayMs | 300 | Politeness toward city servers |
onlyNew | false | Return only permits not delivered by an earlier run |
stateStoreName | permits-state | Named key-value store that remembers delivered permits for onlyNew |
sources | empty (all) | Only these cities, by dataset domain (e.g. data.cityofchicago.org) |
permitTypes | empty (all) | Only permits whose permitType contains one of these words, case-insensitive |
minDeclaredValue | empty (none) | Only permits with declaredValue at least this much |
Choosing cities and filtering permits
sources picks cities by their dataset domain — the same value as each
row's source and each coverage entry in the run report, and stable across
runs. A pasted link is reduced to its domain; anything that is not a domain
fails the run at once. Only cities that the catalog returns for your query
can be matched (the catalog is asked for its first 100 datasets); a requested
domain it did not return is listed under sourcesNotFound in the run report
and in the status message, never silently dropped. maxCities still applies.
permitTypes and minDeclaredValue are checked on each permit before
it is delivered, so a filtered-out permit is never charged, and with onlyNew
it is not remembered as delivered. A permit with no permitType never matches
a type filter, and one with no declaredValue never passes a minimum above 0 —
an unknown value is not treated as big enough. The filters are applied to the
permits read, and maxPermitsPerCity still bounds how many are read, so a
strict filter can return fewer permits than that limit. Values are in each
city's own currency (Calgary and Edmonton publish Canadian dollars). The run
report counts filteredOut overall and per city.
Only new permits (onlyNew)
Schedule the Actor daily with onlyNew on and each run returns only permits
you have not received before — you are not charged twice for the same lead.
- Each delivered permit is remembered by its permit number (or, where a city publishes none, a hash of its source, issue date, type, address and description), per city dataset, in a named key-value store in your account.
- Up to the last 50,000 permits per city are remembered.
- A permit is remembered only after it was actually delivered, so a run stopped by your spending limit never hides a permit you did not get.
maxPermitsPerCitythen counts permits checked (newest first), not permits returned — a quiet day returns few or none.
Measured locally, 14 Sep 2026, 2 cities × 5 permits: first run 10 permits, second run minutes later 0 permits.
Use it for
- Construction leads — filter
declaredValue, sort byissuedAt - Contractor prospecting — who is pulling permits, and for what size of work
- Market research — permit volume and declared value by city over time
- Supplier targeting — match
permitTypeto what you sell
Speed and cost
Measured locally, 14 Sep 2026: 15 permits from 3 cities (Chicago, Calgary, Seattle) in 12.5 seconds, 4 requests — a deliberately small run. Each city costs one request per 1,000 permits.
On 14 Sep 2026 the catalog offered 42 city datasets; 40 answered a request.
Honest notes
Coverage is uneven by design. city sits at 31% and contractor at 22%
because many portals simply do not publish those columns. They come back null
rather than the row changing shape — check the field you need before buying at
scale.
Some portals stop answering. They are city IT departments, not APIs with an
SLA. Unreachable cities are listed in the run report under skipped rather than
failing the run.
Dates vary in format between portals; they are passed through as the city publishes them rather than being guessed into a single format.
Every run publishes a report to the key-value store under RUN_REPORT:
cities covered, cities skipped and why, fields kept per city, and cost per 1,000.
The run's status message always summarises it, e.g.
15 permit(s) from 3 cities; skipped: ....
Questions or a city that will not load?
Open an issue with the portal domain.