# Building Permits Scraper: Construction Leads from 7 US Cities (`nightwave-owner/us-building-permits`) Actor

Returns issued building permits from 7 US city open data portals (Seattle, Austin, San Francisco, Cincinnati, Baton Rouge, New Orleans, Montgomery County MD): type, work description, address, ZIP, estimated cost, status and coordinates. Filter by city, date, cost and type.

- **URL**: https://apify.com/nightwave-owner/us-building-permits.md
- **Developed by:** [Viktor Wiberg](https://apify.com/nightwave-owner) (community)
- **Categories:** Lead generation, Real estate
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$3.00 / 1,000 permits

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Building Permits Scraper: Construction Leads from 7 US Cities

A permit scraper for contractor leads. It collects issued building permits from the official open data portals of seven US cities and counties, one row per permit in one schema: city, permit number, issue and application date, permit type, work class, category, work description, street address, ZIP, estimated project cost, status, coordinates and a link to the permit where the city has one.

Use it to find new construction and renovation projects for sales leads (roofing, solar, HVAC, windows, materials, equipment rental), to watch what gets built in a neighbourhood, or to feed a CRM or a market report with fresh permits every morning instead of searching each city portal by hand.

### What is covered

| City | Portal | Dataset | License |
|---|---|---|---|
| Seattle, WA | data.seattle.gov | [Building Permits](https://data.seattle.gov/d/76t5-zqzr) (`76t5-zqzr`) | Public Domain |
| Austin, TX | data.austintexas.gov | [Issued Construction Permits](https://data.austintexas.gov/d/3syk-w9eu) (`3syk-w9eu`) | Public Domain U.S. Government |
| San Francisco, CA | data.sfgov.org | [Building Permits](https://data.sfgov.org/d/i98e-djp9) (`i98e-djp9`) | Open Data Commons PDDL |
| Cincinnati, OH | data.cincinnati-oh.gov | [Cincinnati Building Permits](https://data.cincinnati-oh.gov/d/uhjb-xac9) (`uhjb-xac9`) | Public Domain |
| Baton Rouge, LA | data.brla.gov | [EBR Building Permits](https://data.brla.gov/d/7fq7-8j7r) (`7fq7-8j7r`) | Public Domain |
| New Orleans, LA | data.nola.gov | [Permits](https://data.nola.gov/d/rcm3-fn58) (`rcm3-fn58`) | CC0 1.0 (Public Domain Dedication) |
| Montgomery County, MD | data.montgomerycountymd.gov | [Residential Permit](https://data.montgomerycountymd.gov/d/m88u-pqki) (`m88u-pqki`) and [Commercial Permits](https://data.montgomerycountymd.gov/d/i26v-w6bd) (`i26v-w6bd`) | Public Domain |

All datasets are updated by the cities every day or every few days. The actor reads them live through the Socrata SODA API, so a run always returns what the portal has at that moment.

- Filters: city, issue date range, minimum estimated cost, permit type words and keywords in the work description.
- Newest issued first across all selected cities.
- With no input the actor returns the 50 newest permits issued in the last 30 days across all seven cities.

### Example from a real run

This is the input and the first two rows of a run on the Apify platform on 3 October 2026 (run `yPcsczfd2nBRbu4PU`).

Input:

```json
{
  "cities": ["seattle", "austin"],
  "minCost": 100000,
  "maxResults": 3
}
```

Output (excerpt):

```json
[
  {
    "city": "Austin",
    "state": "TX",
    "permitNumber": "2025-027099 BP",
    "issuedDate": "2026-10-01",
    "appliedDate": "2024-05-29",
    "permitType": "Building Permit",
    "workClass": "New",
    "category": "Commercial",
    "workDescription": "Ground up construction of a single story welding shop (Custom Manufacturing) with mezzanine",
    "address": "4503 LUCKSINGER LN",
    "zip": "78745",
    "estimatedCost": 500000,
    "status": "Active",
    "latitude": 30.21523347,
    "longitude": -97.76602047,
    "permitUrl": "https://abc.austintexas.gov/web/permit/public-search-other?t_detail=1&t_selected_folderrsn=13479763",
    "sourceDataset": "https://data.austintexas.gov/d/3syk-w9eu",
    "sourcePortal": "data.austintexas.gov",
    "license": "Public Domain U.S. Government"
  },
  {
    "city": "Seattle",
    "state": "WA",
    "permitNumber": "7164872-CN",
    "issuedDate": "2026-10-01",
    "appliedDate": "2026-09-29",
    "permitType": "Building",
    "workClass": "Addition/Alteration",
    "category": "Non-Residential",
    "workDescription": "Construct repairs to existing commercial building at south elevation to remove and replace windows siding in kind STFI.",
    "address": "2227 NW 57TH ST",
    "zip": "98107",
    "estimatedCost": 180408,
    "status": "Issued",
    "latitude": 47.66989921,
    "longitude": -122.38599435,
    "permitUrl": "https://services.seattle.gov/portal/customize/LinkToRecord.aspx?altId=7164872-CN",
    "sourceDataset": "https://data.seattle.gov/d/76t5-zqzr",
    "sourcePortal": "data.seattle.gov",
    "license": "Public Domain"
  }
]
```

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `cities` | array | all seven | `seattle`, `austin`, `san-francisco`, `cincinnati`, `baton-rouge`, `new-orleans`, `montgomery-county-md`. Names such as "San Francisco" or "SF" also work. |
| `issuedFrom` | date | 30 days ago | Permits issued on or after this date, `YYYY-MM-DD`. |
| `issuedTo` | date | today | Permits issued on or before this date. |
| `minCost` | integer | none | Minimum estimated or declared project cost in USD. Permits without a cost are left out when it is set. |
| `permitTypes` | array | all | Words that must appear in `permitType`, `workClass` or `category`, for example `["new", "demolition"]`. |
| `keywords` | array | none | Words that must appear in `workDescription`, for example `["solar", "roof"]`. |
| `maxResults` | integer | 50 | Rows in total, 1 to 10 000. |
| `onlyNew` | boolean | false | Only permits that earlier runs with the same input did not deliver. |

### Output

| Field | Description |
|---|---|
| `city`, `state` | City or county and two letter state code. |
| `permitNumber` | The permit or record number used by the city. |
| `issuedDate` | Date the permit was issued, `YYYY-MM-DD`. |
| `appliedDate` | Date the application was filed, when the city publishes it. |
| `permitType` | Permit type as the city names it, for example "Building Permit", "Demolition", "Mechanical HVAC". |
| `workClass` | New, addition, alteration, repair and similar, where the city has it. |
| `category` | Residential or commercial class, or land use. |
| `workDescription` | The city's description of the work (up to 2 000 characters). |
| `address`, `zip` | Street address of the site and five digit ZIP. New Orleans publishes no ZIP. |
| `estimatedCost` | Estimated, declared or construction value in USD as the city records it. |
| `status` | Current status, for example Issued, Active, Finaled. Baton Rouge publishes no status. |
| `latitude`, `longitude` | Coordinates of the site, `null` when missing. |
| `permitUrl` | Link to the permit in the city's own system (Seattle, Austin, Cincinnati). |
| `sourceDataset`, `sourcePortal`, `license` | Where the row comes from and under which license. |

The schema leaves out names and contact details on purpose: owner, applicant, contractor and permittee columns are never requested from the portals.

### Monitoring and scheduling

Set `onlyNew` to `true` to use the actor as a daily permit alert. The actor then remembers which permits it has delivered for the same input, in a named key-value store in your Apify account (`nightwave-state-us-building-permits`, one record per input). Each run returns and charges only permits that earlier runs did not deliver. The first run returns everything in the selection. A run without news finishes successfully with 0 rows.

`onlyNew` and `maxResults` are not part of the remembered input, so you can change them without starting over. Changing any other field starts a fresh state. Leave `issuedFrom` empty in a schedule: it then always looks 30 days back, and permits you already have are skipped.

A second run straight after the first, with `onlyNew` and the same input, returns 0 rows and is not charged, unless a city published new permits in between.

Example: every morning at 07:00, new commercial permits worth 250 000 USD or more in Austin and Seattle. In Apify Console, open **Schedules**, create a schedule with the cron expression `0 7 * * *` and add this actor with the input below.

```json
{
  "cities": ["austin", "seattle"],
  "permitTypes": ["commercial", "non-residential"],
  "minCost": 250000,
  "onlyNew": true,
  "maxResults": 500
}
```

The same schedule through the Apify API:

```sh
curl -X POST "https://api.apify.com/v2/schedules?token=<YOUR_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"name": "daily-building-permits", "cronExpression": "0 7 * * *", "timezone": "America/Chicago", "isEnabled": true, "isExclusive": true,
       "actions": [{"type": "RUN_ACTOR", "actorId": "nightwave-owner~us-building-permits",
                    "runInput": {"contentType": "application/json; charset=utf-8", "body": "<the input above as a JSON string>"}}]}'
```

Connect a webhook or an integration (Slack, e-mail, Google Sheets, HubSpot) to the actor in Apify Console if you want the new rows sent somewhere when the run finishes.

### Limitations

- Permit types, statuses and cost fields differ between cities. The actor keeps each city's own wording in `permitType`, `workClass` and `category` instead of forcing them into a shared list, so `permitTypes` matches on words. Try `["new"]`, `["demolition"]`, `["roof"]`, `["solar"]` or `["commercial"]`.
- `estimatedCost` is what the applicant declared to the city. Some cities record 0 for trade permits (electrical, plumbing) and some do not record it at all.
- A city that does not answer is logged as a warning and the run continues with the other cities. The run fails only if every selected portal fails.
- Chicago, New York City and Los Angeles are not included. Their permit datasets do not carry a public domain license in the portal metadata (Chicago's terms of use add an indemnity obligation, and the New York and Los Angeles datasets state no license), so they are left out until that changes.
- Each city is read with at most 20 000 rows per run. Narrow the date range if you need more history from one city.

### Use cases

- **Contractors and suppliers:** new construction, additions and remodels above a cost threshold, every morning.
- **Solar, roofing and HVAC companies:** filter on `keywords` such as `["solar"]`, `["reroof"]` or `["heat pump"]`.
- **Real estate and market research:** count permits per ZIP or per month to see where building activity grows.
- **Local journalism and planning:** track demolitions and new housing in a neighbourhood.

### FAQ

**Is this the official data?** Yes. Every row comes straight from the city's own Socrata portal, and `sourceDataset` links to the dataset.

**How fresh is it?** As fresh as the portal. Most of the cities above update daily, so permits issued yesterday are usually in today's data.

**Can you add my city?** Only cities whose permit dataset is published under a public domain or equivalent open license are added. Write to kontakt@nightwave.se with the dataset link.

**Do I get owner or contractor names?** No. The actor is built for project data, not personal data.

### Data source and license

All data comes from the Socrata Open Data API (SODA) of each portal listed in the table above. The license of each dataset was read from the portal's own metadata (`license` in the Socrata catalog at api.us.socrata.com) on 3 October 2026, and is repeated in the `license` field of every row:

- Seattle, Cincinnati, Baton Rouge, Montgomery County: "Public Domain".
- Austin: "Public Domain U.S. Government" (https://www.usa.gov/government-works).
- San Francisco: "Open Data Commons Public Domain Dedication and License" (http://opendatacommons.org/licenses/pddl/1.0/).
- New Orleans: "Creative Commons 1.0 Universal (Public Domain Dedication)" (https://creativecommons.org/publicdomain/zero/1.0/).

These licenses allow copying, changing and commercial reuse without permission. The cities provide the data as is and make no warranty about its accuracy. This actor is not affiliated with or endorsed by any of the cities.

### Pricing

Pay per result: 0.003 USD per permit returned (event `permit`), which is 3 USD per 1 000 permits. Apify bills platform usage on top as usual. It is small: a test run that returned 1 000 permits from all seven cities took 10 seconds and used 0.0002 USD of platform usage. `maxResults` caps how many rows a run returns, so you always know the highest possible cost.

### Contact

Built and maintained by Nightwave AB. Questions, bugs and feature requests: kontakt@nightwave.se

### På svenska

Actorn hämtar beviljade bygglov från de öppna dataportalerna i sju amerikanska städer och län (Seattle, Austin, San Francisco, Cincinnati, Baton Rouge, New Orleans och Montgomery County i Maryland), en rad per bygglov i ett gemensamt format: stad, lovnummer, beviljat datum, ansökningsdatum, typ av lov, typ av arbete, kategori, beskrivning, adress, postnummer, uppskattad kostnad i USD, status, koordinater och länk till lovet där staden har en.

- Källa: varje stads Socrata-portal (SODA API), läst direkt vid körningen. Ingen API-nyckel behövs.
- Licens: bara dataset som enligt portalens egen metadata är public domain (Public Domain, PDDL eller CC0) är med, kontrollerat 3 oktober 2026. Licensen står i fältet `license` på varje rad. Chicago, New York och Los Angeles är inte med eftersom deras dataset saknar en sådan licens.
- Inga personuppgifter: kolumner med ägare, sökande, entreprenörer och kontaktpersoner hämtas aldrig.
- Filter: stad, datumintervall, lägsta kostnad, ord i lovtypen och sökord i beskrivningen. Utan input fås de 50 senaste loven från de senaste 30 dagarna.
- Med `onlyNew: true` levereras bara lov som tidigare körningar med samma input inte har levererat, vilket passar för daglig bevakning (se "Monitoring and scheduling").
- Pris: 0,003 USD per bygglov (3 USD per 1 000) plus Apifys plattformsanvändning.
- Kontakt: kontakt@nightwave.se

# Actor input Schema

## `cities` (type: `array`):

Which cities to read. Leave empty for all seven. Example: \["seattle", "austin"].

## `issuedFrom` (type: `string`):

Only permits issued on or after this date, YYYY-MM-DD. Defaults to 30 days ago. Example: 2026-09-01.

## `issuedTo` (type: `string`):

Only permits issued on or before this date, YYYY-MM-DD. Leave empty for up to today. Example: 2026-09-30.

## `minCost` (type: `integer`):

Optional. Only permits whose estimated or declared project cost is at least this many US dollars. Permits without a cost are left out when this is set, so there is no default. Example: 100000.

## `permitTypes` (type: `array`):

Words that must appear in the permit type, work class or category (case insensitive, any of them). Example: \["new", "demolition", "commercial"]. Leave empty for all types.

## `keywords` (type: `array`):

Words that must appear in the work description (case insensitive, any of them). Example: \["solar", "roof"].

## `maxResults` (type: `integer`):

Maximum number of permits in total, newest issued first across the selected cities, for example 50. 1 to 10 000, defaults to 50.

## `onlyNew` (type: `boolean`):

Return only permits that earlier runs with the same input did not deliver, for example true for daily lead monitoring. Defaults to false.

## Actor input object example

```json
{
  "cities": [
    "seattle",
    "austin"
  ],
  "issuedFrom": "2026-09-01",
  "issuedTo": "2026-09-30",
  "minCost": 100000,
  "permitTypes": [
    "new",
    "demolition"
  ],
  "keywords": [
    "solar",
    "roof"
  ],
  "maxResults": 50,
  "onlyNew": true
}
```

# Actor output Schema

## `results` (type: `string`):

All permits produced by the run, as JSON. Open in Apify Console or download via the dataset API.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "cities": [
        "seattle",
        "austin"
    ],
    "maxResults": 50,
    "onlyNew": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("nightwave-owner/us-building-permits").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "cities": [
        "seattle",
        "austin",
    ],
    "maxResults": 50,
    "onlyNew": False,
}

# Run the Actor and wait for it to finish
run = client.actor("nightwave-owner/us-building-permits").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "cities": [
    "seattle",
    "austin"
  ],
  "maxResults": 50,
  "onlyNew": false
}' |
apify call nightwave-owner/us-building-permits --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,nightwave-owner/us-building-permits"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/f0AzqSs4QWLFjOt9k/builds/xhf1ed3dLCujytTQq/openapi.json
