US Building Permits Scraper — 45 Cities, 22 States
Pricing
from $4.80 / 1,000 building permit records
US Building Permits Scraper — 45 Cities, 22 States
Issued US building permits from 45 jurisdictions in 22 states — NYC, Chicago, LA County, San Francisco, Austin, Seattle. One normalised trade vocabulary, valuation with its basis, contractor, address, dates, and a named reason for every empty field. Pick cities or states, pick a date window.
Pricing
from $4.80 / 1,000 building permit records
Rating
0.0
(0)
Developer
Vladimir Chernykh
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
15 minutes ago
Last modified
Categories
Share
US Building Permits Scraper — Contractor, Trade & Valuation
Building permits from 45 open municipal permit systems in 22 US states, normalised to one record shape and one trade vocabulary. 12.6 million permits of history sit behind these layers; a run returns the fresh ones.
This is a building permit lead feed, not a permit dump. Every record answers the four questions a contractor, roofer, solar installer or supplier actually pays for: who is doing the work, what trade it is, how much it is worth, and where.
Why it is not just "permit records"
A jurisdiction's permit type is not a trade. Columbus writes
Roof, Siding, Windows, Doors, Nashville Building Residential - New, Anaheim
Residential Remodel. The field workType maps all of them onto one small vocabulary
(roofing, solar, hvac, electrical, plumbing, new-construction, remodel,
demolition, signage, other), which is what lets someone who sells roofs filter for
roofs in every city at once.
valuation never travels alone: valuationBasis says whether it is a declared job value
or a construction cost. A $450 000 construction cost and a $2 685 job value are not the
same number, and contractor totals never add them together silently — the portfolio row
sets valuationBasisMixed instead.
Coverage — 45 jurisdictions, 22 states
| State | Jurisdictions | State | Jurisdictions |
|---|---|---|---|
| AZ | 3 | MD | 2 |
| CA | 5 | MI | 1 |
| CO | 4 | NC | 1 |
| CT | 1 | NM | 1 |
| FL | 4 | NY | 1 |
| ID | 1 | OH | 4 |
| IL | 2 | OR | 1 |
| KS | 1 | PA | 1 |
| KY | 1 | TN | 2 |
| LA | 1 | TX | 4 |
| MA | 1 | WA | 3 |
Every jurisdiction, its history size and the API it comes from:
| City ID | Jurisdiction | State | Permits on record | API |
|---|---|---|---|---|
us-tx-austin | City of Austin | TX | 2 383 197 | Socrata |
us-ca-san-francisco | City and County of San Francisco | CA | 1 297 749 | Socrata |
us-ny-nyc | New York City | NY | 1 023 760 | Socrata |
us-ca-la-county | Los Angeles County (unincorporated + contract cities) | CA | 928 761 | ArcGIS |
us-il-chicago | City of Chicago | IL | 851 889 | Socrata |
us-oh-columbus | City of Columbus | OH | 688 315 | ArcGIS |
us-fl-miami-dade | Miami-Dade County | FL | 487 686 | ArcGIS |
us-wa-bellevue | City of Bellevue | WA | 445 189 | ArcGIS |
us-la-new-orleans | City of New Orleans | LA | 349 683 | Socrata |
us-co-boulder | City of Boulder | CO | 337 797 | ArcGIS |
us-md-baltimore-city | Baltimore City | MD | 295 869 | ArcGIS |
us-co-douglas | Douglas County | CO | 286 727 | ArcGIS |
us-co-boulder-county | Boulder County (unincorporated) | CO | 272 399 | ArcGIS |
us-md-bpds | Maryland Building Permit Data System | MD | 257 533 | ArcGIS |
us-fl-miami | City of Miami | FL | 236 389 | ArcGIS |
us-fl-alachua | Alachua County | FL | 217 559 | ArcGIS |
us-oh-cleveland | City of Cleveland | OH | 202 564 | ArcGIS |
us-wa-seattle | City of Seattle | WA | 194 053 | Socrata |
us-ca-anaheim | City of Anaheim | CA | 192 331 | ArcGIS |
us-nc-raleigh | City of Raleigh | NC | 184 893 | ArcGIS |
us-oh-cincinnati | City of Cincinnati | OH | 181 639 | Socrata |
us-ca-sacramento-county | Sacramento County | CA | 159 396 | ArcGIS |
us-tx-san-antonio | City of San Antonio | TX | 140 143 | ArcGIS |
us-wa-tacoma | City of Tacoma | WA | 112 920 | ArcGIS |
us-az-coconino | Coconino County | AZ | 111 645 | ArcGIS |
us-az-maricopa | Maricopa County | AZ | 106 341 | ArcGIS |
us-ma-somerville | City of Somerville | MA | 105 594 | Socrata |
us-fl-fort-pierce | City of Fort Pierce | FL | 94 909 | ArcGIS |
us-nm-las-cruces | City of Las Cruces | NM | 83 809 | ArcGIS |
us-tx-san-marcos | City of San Marcos | TX | 51 811 | ArcGIS |
us-mi-detroit | City of Detroit | MI | 47 998 | ArcGIS |
us-il-naperville | City of Naperville | IL | 38 886 | ArcGIS |
us-ct-hartford | City of Hartford | CT | 37 207 | ArcGIS |
us-tn-nashville | Metro Nashville / Davidson County | TN | 29 661 | ArcGIS |
us-tn-memphis | City of Memphis / Shelby County | TN | 28 404 | ArcGIS |
us-ky-louisville | Louisville Metro / Jefferson County | KY | 22 639 | ArcGIS |
us-az-tempe | City of Tempe | AZ | 20 744 | ArcGIS |
us-ks-overland-park | City of Overland Park | KS | 14 197 | ArcGIS |
us-tx-kerrville | City of Kerrville | TX | 11 098 | ArcGIS |
us-id-boise | City of Boise | ID | 11 022 | ArcGIS |
us-ca-placentia | City of Placentia | CA | 7 782 | ArcGIS |
us-oh-oxford | City of Oxford | OH | 7 460 | ArcGIS |
us-co-aspen | City of Aspen | CO | 6 553 | ArcGIS |
us-or-talent | City of Talent | OR | 6 401 | ArcGIS |
us-pa-allentown | City of Allentown | PA | 6 079 | ArcGIS |
37 layers are ArcGIS and 8 are Socrata. The Socrata half is what brought the largest systems into reach, because none of them publish an ArcGIS layer at all.
Verified live on 2026-11-02: all 45 jurisdictions answered, zero failures, 9 123 permits returned for a 7-day window (../../../docs/permits/measurement-notes.md).
What you get — one record per permit
{"source": "us-oh-columbus","state": "OH","permitId": "RSWDR2644234","permitIdType": "accela-alt-id","permitType": "1,2,3 Family","permitSubtype": "Roof, Siding, Windows, Doors","workType": "roofing","status": "Permit Issued","jurisdiction": "City of Columbus","countyName": "Franklin","countyFips": "39049","issuedDate": "2026-10-31","lastStatusDate": "2026-10-31","valuation": 8000,"valuationBasis": "job-value","situsAddress": {"street1": "3728 PENDLESTONE DR","street2": null,"city": "Columbus","state": "OH","zip": "43230"},"parcelId": "600188600","contractorName": "DTE ROOFING LLC","scopeOfWork": "Additions, Alterations and Conversions - Residential","sqft": 900,"units": 1,"completeness": "full","omitted": [{ "field": "ownerName", "reason": "not-published", "note": null },{ "field": "appliedDate", "reason": "source-lacks-field", "note": "the layer publishes the issue date only" }]}
| Group | Fields |
|---|---|
| Identity | permitId, permitIdType, permitType, permitSubtype |
| Trade | workType, scopeOfWork |
| Money | valuation, valuationBasis |
| Dates | appliedDate, issuedDate, finalizedDate, lastStatusDate |
| Status | status |
| Place | situsAddress (street1, street2, city, state, zip), parcelId, countyName, countyFips, jurisdiction, state |
| People | contractorName, contractorLicense, contractorPhone, ownerName |
| Size | sqft, units |
| Provenance | source, sourceApi, sourceLayerId, sourceUrl, scrapedAt, lookupType |
| Honesty | completeness, omitted[] |
Two dataset views ship with it: permits (one row per permit) and portfolios (one row per contractor, free of charge).
omitted — why a field is empty
A missing value is never ambiguous. Every null carries an entry saying why:
| Reason | Meaning |
|---|---|
source-lacks-field | the jurisdiction publishes no such column |
not-published | the column exists but is empty for this permit |
derived | built from several columns (a glued address, a split ZIP) |
zero-means-unknown | the source writes 0 where it means "not recorded" |
So Nashville's status is null with source-lacks-field, not "cancelled". Anaheim
publishes a contractor phone on under 9 % of rows. Both facts are in the dataset.
Inputs — cities, states, and dates
You choose which jurisdictions and which dates. All three selectors work together:
| Input | What it does |
|---|---|
cities | pick exact jurisdictions by ID (us-tx-austin, us-ny-nyc, …); empty means all 45 |
states | pick whole states (CA, TX, FL, …); combines with cities, a route listed twice is not fetched twice |
lastNDays | window size in days, default 7 |
issuedAfter / issuedBefore | exact window, YYYY-MM-DD; either alone is honoured, and they win over lastNDays |
{ "states": ["TX", "FL"], "lastNDays": 7, "maxResultsPerCity": 1000 }
{ "cities": ["us-ca-san-francisco", "us-il-chicago"], "issuedAfter": "2026-10-01", "issuedBefore": "2026-10-31" }
Four search modes run alongside the window:
| Input | Question it answers |
|---|---|
permitIds | what exactly was filed under this permit number |
contractorNames | everything this contractor built, plus a free portfolio row |
addresses | every permit ever filed on this address |
mode: "new" | what was issued since the last run (default) |
Window and limits
The ceiling of the window is today, and it is not cosmetic: several layers carry rows dated into the future, and a permit dated tomorrow has not been issued today — returning it on every run would bill the same permit twice.
maxResultsPerCity (default 1000) is per city, so adding cities never shrinks the others.
A city that reaches it is marked truncated in the summary, which is how a cut-off stays
distinguishable from a quiet city. Two hard ceilings sit above it: maxResultsPerCity
cannot exceed 5000, and the whole run stops at 20 000 records.
The run summary lists, for every city, returned, inWindow and truncated — so a
buyer can always tell "this suburb issued nothing" from "we cut you off at 1000".
Incremental mode
mode: "new" asks each layer for permits issued inside the window, newest first, and
filters out everything an earlier run already pushed. The seen-set lives in the named
store us-building-permits-state, one key per route, and the key deliberately does not
contain the window — putting lastNDays in the key makes every new window look like a
new search and re-charges the same permits.
Filtering happens before maxResultsPerCity is applied, so a page full of already-seen
permits cannot starve a run of the new ones behind them.
Cost
$5 per 1000 permits — that is $0.005 per permit, and Apify adds its 20 % margin at
checkout. One charged event (permit-scraped) per distinct permit pushed. A lookup that
matches nothing is not charged, the contractor portfolio row is free, and a permit already
returned by an earlier run is never charged twice.
A 7-day run across all 45 jurisdictions returns roughly 9 000 permits, which is about $45.
Honest limits
- Nine of the 45 feeds had nothing inside a 7-day window on 2026-11-02. That is the feed, not the actor: Boulder County's application feed lags six months, Aspen publishes closed permits only, Boise covers new housing and demolitions only. Each one still answers.
- Some columns are fees, not valuations (Bellevue
TOTALBILLED, CoconinoTotalFees, New Orleanstotalfees, Cincinnatifee, Somervilleapplication_amount) — they are not published asvaluationand say so inomitted. - A column named
owner_contractor(Placentia) is published as neither, because the layer never says which of the two it is. - New Orleans
bldgareaholds only0or4000, so it is a class flag, not an area, and is not published assqft. - Somerville publishes building permits, farmers-market licences and block-party licences
in one table. The route carries an internal filter for
department = 'ISD Building', so a permit lookup cannot return a licence that shares its digits.
Search terms
building permits, building permit scraper, construction permits, permit data,
US building permits, permit leads, contractor leads, roofing permits,
solar permits, HVAC permits, electrical permits, plumbing permits,
new construction permits, remodel permits, demolition permits, permit valuation,
job value, construction cost, issued permits, permit number lookup,
contractor license, contractor name, owner name, parcel number, APN,
property address, situs address, county FIPS, census tract,
New York City permits, NYC DOB permits, Austin permits, Chicago permits,
San Francisco permits, Los Angeles County permits, Seattle permits,
Columbus Ohio permits, Cleveland permits, Cincinnati permits, Miami-Dade permits,
Baltimore permits, Nashville permits, Memphis permits, Detroit permits,
Sacramento permits, Anaheim permits, Raleigh permits, Tacoma permits,
Bellevue permits, Boulder permits, Tempe permits, Maricopa County permits,
Coconino County permits, Douglas County permits, Alachua County permits,
lead generation, real estate data, construction intelligence,
building activity, permit feed, daily permit feed, incremental scraping
Run locally
npm run stage -- us-building-permitscd packages/actors/us-building-permitsmkdir -p storage/key_value_stores/default# then write storage/key_value_stores/default/INPUT.jsonAPIFY_LOCAL_STORAGE_DIR=./storage node src/main.js
npm run stage inlines packages/core into src/core; without it the import
./core/index.js does not resolve outside the workspace.
The input directory is key_value_stores — plural. The SDK v3 creates it itself and
silently ignores a hand-made key_value_store, so a run with a singular-name directory
starts on the defaults and looks exactly like "nothing matched": zero records, zero
misses, zero errors. That symptom means the input was not read; it never means the layers
are empty.
Adding a jurisdiction
Adding a city is a row in src/routes.json, not a new parser, and a
test checks the registry. The mapper enforces three rules: only mapped columns are
requested (a missing column makes ArcGIS reject the whole query), 0 in a valuation
column is "unknown", and every null gets an entry in omitted.
Tests
$node --test packages/actors/us-building-permits/test/*.test.js
408 tests. The fixtures are real rows from the layers, including the awkward ones: the zero valuation, the glued Anaheim address, the Nashville row with no status, the Somerville licence that shares digits with a permit.