# US Building Permits Scraper - 10 Cities, One Schema

Scrape US building permits from 10 city open-data portals into one clean schema. Contractor and applicant leads with address, valuation, and dates. Filter and export to CSV.

Canonical product page: https://mayd-it.com/data-tools/actors/us-building-permits-scraper/
Public Actor: https://apify.com/maydit/us-building-permits-scraper

## Example input

```json
{
  "cities": [
    "seattle",
    "chicago",
    "baton_rouge",
    "marin_county"
  ],
  "issuedSince": "2026-06-01",
  "permitTypeKeywords": [
    "roof",
    "solar",
    "hvac"
  ],
  "onlyWithContractor": true,
  "minValuation": 5000,
  "maxResultsPerCity": 10
}
```

## Full guide

![Compare permits across local sources — illustrative workflow](https://api.apify.com/v2/key-value-stores/YGxD9JRpduom8Q5Zd/records/us-building-permits-scraper-2080453f656f.png?signature=1JBOMAXN9PaKE1V9L4uOG)

## Compare permits across local sources

For construction and local-market research: collect building permit records from ten supported city and county open-data portals into one consistent table. Addresses, dates, declared valuation and contractor fields depend on what each source publishes.

[Open this Actor in Console](https://console.apify.com/actors/EvCD8QyPV2uMbPG6U/input) · [First run](#try-this-actor) · [Results](#result-preview) · [Cost](#usage-and-pricing) · [Full guide](#full-guide)

## Try this Actor

- **First run:** Choose one supported source in **Cities** and review the per-city result cap before the first run. Inspect its mapped fields and date coverage before expanding to the full source list.
- **Read the results:** Open the default Dataset for permit rows and **SUMMARY** for per-source coverage. Use the permit ID and official record link when following up on a project.
- **Keep in mind:** Coverage is limited to the listed portals, not all US permits. Declared valuation is not actual spend, contractor names are not always available, and a permit alone does not establish purchasing intent or vendor-selection timing.

## Result preview

The dataset view highlights these columns:

- **Source city** · `sourceCity`
- **Permit ID** · `permitId`
- **Permit type** · `permitType`
- **Permit class** · `permitClass`
- **Status** · `status`
- **Work description** · `workDescription`

Use the original source links and the field definitions below when interpreting the results. CSV works well for flat columns; JSON preserves nested values.

## Usage and pricing

Base result rate on the FREE tier: **$3 per 1,000 results**. The [Pricing tab](https://apify.com/maydit/us-building-permits-scraper/pricing) shows your plan's result rate and any Actor Start charge. Set a small result limit for the first run; the requested limit is not a guarantee of that many results.

---

<!-- CUSTOMER-WORKFLOW-2026-10-13:START -->
## Customer workflow: A weekly construction project review queue

Collect a bounded issue-date slice from one service-area portal, reconcile repeat permits into one row, and send a reviewed project shortlist to your team rather than appending duplicate leads.

**Setup:** Connect your own Apify and Google Sheets project register or CRM staging table through [Make](https://docs.apify.com/integrations/make) or [n8n](https://docs.apify.com/integrations/n8n). Keep credentials in your connector's credential manager.

```json
{
  "cities": [
    "chicago"
  ],
  "issuedSince": "2026-10-01",
  "issuedUntil": "2026-10-12",
  "permitTypeKeywords": [],
  "onlyWithContractor": false,
  "maxResultsPerCity": 100,
  "maxRunSeconds": 240,
  "includeRaw": false
}
```

Start with a **300-second Run timeout** and **$0.50 maximum run charge**, alongside the input's 240-second budget. Check current pricing and the selected scope before increasing either limit.

1. Create a Projects sheet and run the bounded example above. Its dates are fixed examples: replace them with your intended issue-date window. Use a rolling overlap for repeat collection, while recognizing that older revisions can still be missed.
2. Review `SUMMARY.failedSources`, `perCity`, `stoppedEarlyOnTimeLimit`, and `stoppedOnChargeLimit`. This Actor has no SUMMARY status/incomplete fields. A per-city count at your cap means a sample; narrower dates can help inspect the rest.
3. Create `record_key` from `JSON.stringify([sourceCity, String(permitId)])`. Upsert that key using Sheets "Append or Update Row" in n8n, or a key lookup/update branch in Make. Quarantine missing permit IDs; an address alone is not a safe identity.
4. Queue new keys or changed business fields for review. Compare status, work description, address, valuation, contractor and dates; exclude `scrapedAt`. Keep source links so a person can qualify the project before any outreach.

| Destination information | Actor fields |
| --- | --- |
| Source plus permit identity | `sourceCity`, `permitId` |
| Project | `permitType`, `workDescription`, `status` |
| Location | `address` |
| Published project details | `valuation`, `contractorName` |
| Dates and source | `issueDate`, `appliedDate`, `sourceUrl`, `scrapedAt` |

**Keep the meaning:** Deduplication lives in your destination; this Actor has no native change-only mode. Ten portals are supported, with at most 50,000 source rows scanned per city. Even output below your cap does not certify an exhaustive date window. Declared valuation is not actual spending or buying intent.

**Empty or interrupted runs:** A clean empty run can mean no matching inspected permits. Preserve old projects. A time/spending stop or failed source needs review, even when the run saved useful rows.

**Reliable delivery:** Read the completed run's own SUMMARY and paginated dataset. Retain its run ID; if destination delivery fails, replay that dataset and deduplicate by run ID plus row key. Keep destination writes sequential, and write source text literally.

**Validation:** Inputs and mappings were checked locally against this Actor. The exact customer connection has not been run end to end. Choose recurrence after a manual bootstrap; this guide creates no schedule. See the [run API](https://docs.apify.com/api/v2/actors-runs-post), [pagination guide](https://docs.apify.com/api/v2/dataset-items-get), and the full Actor guide below for detailed setup and limits.
<!-- CUSTOMER-WORKFLOW-2026-10-13:END -->

## Full guide

> Pull building permits from 10 US city open-data portals into a single normalized dataset - built for construction lead generation and sales-territory monitoring.

### What it does

This **building permits scraper** aggregates permit records from 10 official US city open-data portals and normalizes them into **one clean schema**, so you never have to reconcile a different field layout for every city. It pulls high-intent construction data - permit type, status, address, valuation, dates, and (where the city publishes it) contractor and applicant names - and lets you filter by city, minimum project value, keyword, contractor presence, and issue-date range. Use it as a lightweight **US building permits API** for construction lead generation, then export straight to CSV, JSON, or Excel from the Apify dataset.

The moat is the per-city field mapping: each source portal labels its columns differently, and this actor maps all of them into the same output fields so your CRM, spreadsheet, or pipeline sees consistent **building permit data by city**.

### Who it's for

- **Construction lead-gen teams** sourcing new-project and renovation leads
- **Contractors and subcontractors** watching their service area for fresh permits
- **Building-material suppliers and distributors** timing outreach to project starts
- **Roofers, solar installers, and HVAC sales teams** chasing permit-triggered demand
- **Real-estate and proptech** teams enriching properties with permit activity
- **Market researchers and analysts** tracking construction volume by city

### What you get / Output

Every record is normalized to the same fields across all 10 cities:

| Field | Description |
|---|---|
| `permitId` | Official permit identifier from the source city |
| `permitType` | Type/category of permit (new build, alteration, etc.) |
| `permitClass` | Class, work type or use of the permit, as the city labels it |
| `status` | Current permit status |
| `workDescription` | Work description / scope text |
| `address` | Street address of the permitted work |
| `city` | The source portal's city (or county, for Marin County) |
| `state` | US state |
| `zip` | ZIP / postal code |
| `valuation` | Declared project value or estimated cost |
| `contractorName` | Contractor on the permit (where the city publishes it), with stray quote marks removed; placeholders such as "N/A" are left empty |
| `applicantName` | Applicant / filer on the permit (where the city publishes it), cleaned the same way |
| `issueDate` | Date the permit was issued |
| `appliedDate` | Date the permit was filed / applied |
| `latitude` | Latitude of the site |
| `longitude` | Longitude of the site |
| `sourceCity` | Which of the 10 source portals the record came from |
| `sourceUrl` | Link back to the record on the source portal, where the city provides one |
| `scrapedAt` | When the record was collected |

> Note: not every city publishes every field. `contractorName`, `applicantName`, and `valuation` availability varies by source portal - the actor fills what the city exposes and leaves the rest empty rather than inventing data.

### Input / How to query

Configure a run with these filters and modes:

- **Cities** - choose one, several, or all 10 supported portals
- **Minimum job valuation** - keep only projects at or above a dollar threshold
- **Permit type keywords** - match against the permit type, work description and class (e.g. `roof`, `solar`, `remodel`)
- **Only permits with a contractor name** - drop permits that name no contractor
- **Issued since / Issued until** - restrict by issue date
- **Multi-city pull** - fetch several cities in a single run; each city's raw fields are normalized into the shared schema automatically
- **Max run seconds** - wall-clock budget for the run (default 240). Cities are fetched one after another; when the budget is reached the Actor stops cleanly, finishes successfully with the permits it already has, names the cities it did not reach, and records `stoppedEarlyOnTimeLimit: true` in the run's `SUMMARY`. Raise it, together with the run timeout, for a deliberately large pull

Results land in the run's dataset, exportable as **CSV**, JSON, Excel, or via the Apify API.

### Example use cases

- **Roofing / solar / HVAC lead gen** - keyword-filter for `roof`, `solar`, `HVAC`, or `re-roof` across your target cities and pull fresh permits with addresses to build a call list.
- **Contractor competitive tracking** - pull permits that name a contractor, then filter the dataset on a competitor's `contractorName` to see where and what they're building.
- **Material supplier timing** - filter by valuation to surface large new-construction projects the moment they're filed, then reach out before the concrete order is placed.
- **Real-estate / proptech enrichment** - use `latitude`/`longitude` and `address` to join permit activity onto your property database.
- **Market research** - pull all cities over a date range to measure construction volume and mix by `permitType` and `valuation`.

### Recurring use / scheduling

Set up an **Apify Schedule** (for example, a daily or weekly cron) so the actor re-runs on its own and captures newly filed and newly issued permits without manual effort - ideal for **monitoring new building permits in a sales territory**.

For clean, no-duplicate feeds:

- Use the **date-range filter** to only pull records since your last run.
- Point runs at the **same named dataset** and dedupe downstream on `permitId` + `sourceCity` (unique per city).
- Wire an **integration** (webhook, Google Sheets, Slack, or your CRM) to push each new batch straight into your workflow.


<!-- REPORT-WATCH-WORKFLOW:START -->
#### Optional: check a final reporting CSV

If you turn these results into a daily or weekly report, check the final transformed CSV separately from the Actor run. First inspect `SUMMARY.failedSources`, `stoppedEarlyOnTimeLimit` and `stoppedOnChargeLimit`; do not present a partial collection as complete.

For a synthetic or sanitized summary without names or addresses, group collected rows by `sourceCity` and count them. Your workflow could produce `source_city,permit_rows,report_generated_at`: these are **derived summary columns**, not this Actor’s native output. A generation timestamp records when you made that summary; it does not establish fresh permits. Do not use `issueDate` as report-generation time.

Try the [free browser CSV checker](https://reportwatch.mayd-it.com/tools/csv-checker) with required summary headers and a deliberate row range. Allow zero rows when a valid empty result is expected. A header/row check does not validate the permit count’s accuracy or source completeness.

The [final-export workflow guide](https://reportwatch.mayd-it.com/workflows#apify-exports) explains optional recurring checks. Report Watch accepts up to 1 MiB, 100 columns and 10,000 data rows. Monitoring needs separate paid setup and a configured HTTP submission; it is not built into this Actor. Keep Apify’s native monitoring if it already covers your needs.
<!-- REPORT-WATCH-WORKFLOW:END -->
### FAQ

#### How do I get building permit data for construction leads?

Run this actor against the cities you sell into, optionally filter by keyword, valuation, or date, and export the dataset to CSV or push it to your CRM. Each row includes the address, permit type, dates, and - where the city publishes it - the contractor and applicant, which is exactly the contact-and-context data lead-gen teams need.

#### Which US cities publish building permits as open data?

This actor covers 10 official portals: Chicago, San Francisco, New York City (DOB NOW), Austin, Seattle, Los Angeles, Baton Rouge, Cincinnati, Mesa, and Marin County. All are public Socrata open-data sources.

#### How can I scrape building permits from multiple cities into one format?

That is the core feature. Select multiple cities in one run and the actor maps each portal's differently-named columns into the same normalized schema, so every record shares the same fields regardless of source city.

#### How do I find new construction and renovation permits daily?

Schedule the actor to run daily and filter by issue date so each run returns recent records. Combine with a keyword like `new` or `addition` to focus on new construction and renovations.

#### Can I get building permit data with contractor and applicant names?

Where the source city publishes them, yes - the output includes `contractorName` and `applicantName`. Coverage of these fields varies by city; portals that expose them are mapped through, and those that don't are left blank rather than guessed.

#### What is the best building permits API for contractors?

For contractors who want a wide, normalized feed across many cities without managing a different integration per portal, this actor works as a pay-per-result building permits API: one input schema, one output schema, 10 cities, filterable by minimum valuation, keyword, contractor presence, and issue date.

#### How do I export building permit data to CSV or Excel?

Results go to the run's Apify dataset, which exports to CSV, Excel, JSON, or XML with one click, or via the Apify API for automated pipelines.

#### How do I monitor new building permits in my sales territory?

Pick the cities in your territory, set a schedule, filter by date range so each run is incremental, and route the output to Google Sheets, a webhook, or your CRM. Dedupe on `permitId` + `sourceCity` to keep the feed clean.

#### Is building permit data free and legal to scrape?

The underlying records come from official municipal open-data portals published for public use. This actor reads those public datasets; you are responsible for complying with each portal's terms and any applicable usage rules.

#### How do roofers, solar, and HVAC companies find permit leads?

They keyword-filter permit descriptions for terms like `roof`, `solar`, `PV`, or `HVAC`, restrict to their service-area cities and a recent date range, and pull the resulting addresses as a fresh lead list - then schedule it to repeat.

#### How do I filter building permits by valuation or project cost?

Use the minimum job valuation input to focus on large projects; it also drops permits whose city published no valuation. There is no maximum input - to target small jobs or a size band, filter the exported dataset on `valuation`.


### Data source & notes

- **Source:** 10 official US city Socrata open-data portals - Chicago, San Francisco, New York City (DOB NOW), Austin, Seattle, Los Angeles, Baton Rouge, Cincinnati, Mesa, and Marin County.
- **Public data:** all records originate from public municipal open-data datasets. Respect each portal's terms of use.
- **Field coverage varies by city.** `contractorName`, `applicantName`, and `valuation` are populated where the source city publishes them; the actor never fabricates values.
- **Coverage:** the 10 listed cities only - this actor does not cover every US municipality, and permit freshness depends on how often each city updates its open-data portal.

### Related actors

Part of a set of US public-records tools that work well together:

- [New Business Leads Scraper](https://apify.com/maydit/us-business-licenses-scraper) - newly registered businesses in the same metros - the other half of a local prospecting list
- [Restaurant Inspection Data Scraper](https://apify.com/maydit/us-restaurant-inspections-scraper) - the same city open-data approach applied to food-service inspections

See the full collection at [apify.com/maydit](https://apify.com/maydit).

**Free guide:** [Turning building permit open data into contractor leads](https://mayd-it.com/data-tools/guides/building-permit-data-roofing-solar-hvac-leads/) - the undocumented traps in this data source, measured against the live API.

### Found this useful?

If this actor saved you time, a short review on the Apify Store genuinely helps. Reviews are how the next person decides whether a tool is worth trying.

Something broken, missing a field, or a source you want covered? Open an issue on the **Issues** tab and it will get looked at - that is the fastest route to getting it changed.

#### Run spending limits

The run stops at its spending limit. `SUMMARY.datasetItemsSaved` reports the number of rows actually saved, and `SUMMARY.stoppedOnChargeLimit` identifies this stop. A final batch may be saved only in part; increase the spending limit to collect more results.

Marin County identity: when the published permit number is blank, `permitId` uses the portal's published `unique_id` or `permit_tracking_id`. This identifies the source record; it does not assert that an unnumbered application has been issued.

## Help with your run

Open the **Issues** tab on this Actor with the run URL, the input field that caused trouble and what you expected to receive. Remove API keys and private information before posting.

[Explore more Mayd-It Actors](https://apify.com/maydit)

## Understand your run

Open **Run report** in the run’s Output tab for saved-record counts, reported source issues and limits, and suggested next steps. Use **CSV export** to take the saved results into a spreadsheet. The existing JSON results and detailed machine-readable summary remain available.

The report reflects this run’s recorded coverage. A successful run or a saved result does not establish that every possible source record was collected.
