# US Building Permits – 8 Cities, One Schema (Official Open Data) (`knobby_woodworker_aig/us-building-permits-multi-city`) Actor

Issued building permits from 8 US city open-data portals (Chicago, NYC, LA, Austin, Seattle, Cincinnati, Mesa, Baton Rouge) in one clean schema. Adds trade category (solar, roofing, HVAC, ADU…), status, processing days, value band, contractor company. No homeowner names. Only-new mode.

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

## Pricing

from $2.00 / 1,000 building 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

## US Building Permits – 8 Cities, One Schema

Get **newly issued building permits** from the official open-data portals of **Chicago, New York City, Los Angeles, Austin, Seattle, Cincinnati, Mesa (AZ) and Baton Rouge** in **one normalized schema**. Every row is categorized by trade, so you can pull a roofing, solar, HVAC, ADU or new-construction lead list in a single run.

### What problems this solves

People using other permit scrapers report the same problems again and again. Here is how this Actor handles each one:

| Common complaint | How this Actor handles it |
|---|---|
| Every city has different column names, so you end up writing one parser per city | One schema for all 8 cities: `permitType`, `category`, `issueDate`, `valuationUsd`, `fullAddress`, `latitude/longitude`, `contractorBusinessName`… |
| Raw permit types like "Bldg-Alter/Repair" or "EXPRESS PERMIT PROGRAM" can't be filtered by trade | **Trade classifier.** `category` plus multi-label `tradeTags` cover 26 trades: solar, battery storage, EV charger, roofing, HVAC, plumbing, electrical, ADU, demolition, new construction, pool, fence, sign, elevator, tenant improvement and more |
| Output includes homeowners' and applicants' personal names and phones, which is a privacy and compliance risk | **No personal data.** Owner, applicant and person names, phones and emails are dropped. Only contractor *company* names are kept: a name is output only if it looks like a business (LLC, Inc, Co, Electric, Roofing…) |
| Expensive ($8–30 per 1,000) and slow browser scraping | Uses the official Socrata APIs directly, with no browser and no proxies. 1,000+ permits in seconds |
| Duplicate rows (NYC and Cincinnati repeat permits) | Deduped by city + permit number |
| Daily runs return the same permits again | **Only-new mode** remembers permit numbers between runs, so you can schedule it daily and get only fresh leads |
| One big city crowds out the rest | `maxItemsPerCity` cap |

### Enrichment added to every row

- `category` and `tradeTags`: trade classification from the permit type and scope text
- `statusNormalized`: one of issued / active / completed / expired / cancelled / in_review
- `processingDays`: days from application to issue, so you can compare permit speed by city
- `valuationBand`: <$10k, $10k–50k, $50k–250k, $250k–1M, $1M–10M, $10M+
- `daysSinceIssued`, a `fullAddress` ready for geocoding, and lat/long where the city publishes them
- `permitUrl`: a link to the official permit or property page where available (LA, NYC, Austin, Seattle, Cincinnati)
- `contractorBusinessName` and `contractorLicense` where the city publishes them

### Example uses

- **Solar, roofing and HVAC lead generation:** set `categories: ["roofing"]` and `onlyNew: true`, then schedule it daily.
- **Construction market intelligence:** new-construction volume and value by city or neighborhood.
- **Supplier and subcontractor prospecting:** find active general contractors by company name.
- **Real estate:** watch permit activity in specific ZIP codes.

### Input example

```json
{
  "cities": ["chicago", "nyc", "austin"],
  "daysBack": 7,
  "categories": ["solar", "roofing"],
  "minValuationUsd": 10000,
  "onlyNew": true
}
```

### Output example

```json
{
  "city": "Austin", "state": "TX", "permitNumber": "2026-123456 MP", "permitType": "Mechanical Permit",
  "category": "hvac", "tradeTags": ["hvac"], "statusNormalized": "active",
  "issueDate": "2026-10-29", "appliedDate": "2026-10-27", "processingDays": 2,
  "fullAddress": "1234 EXAMPLE ST, Austin, TX, 78704", "latitude": 30.25, "longitude": -97.76,
  "valuationUsd": 12000, "valuationBand": "$10k–50k",
  "contractorBusinessName": "Example Heat and AC Services LLC",
  "permitUrl": "https://abc.austintexas.gov/..."
}
```

### Data sources (all official, public)

| City | Portal dataset |
|---|---|
| Chicago | data.cityofchicago.org `ydr8-5enu` |
| New York City | data.cityofnewyork.us `rbx6-tga4` (DOB NOW work permits) |
| Los Angeles | data.lacity.org `pi9x-tg5x` |
| Austin | data.austintexas.gov `3syk-w9eu` |
| Seattle | data.seattle.gov `76t5-zqzr` |
| Cincinnati | data.cincinnati-oh.gov `uhjb-xac9` |
| Mesa | citydata.mesaaz.gov `dzpk-hxfb` |
| Baton Rouge | data.brla.gov `7fq7-8j7r` |

Cities update on their own schedules. Most update daily; Los Angeles usually lags about a week. Fields a city doesn't publish (for example, Chicago has no job value) are `null`.

### Pricing

Pay per result: you pay only for the permits you receive. The run summary (per-city counts, filters and errors) is saved to the `RUN_SUMMARY` record in the key-value store.

Want another city added? Open an issue. Any city on Socrata can be added quickly.

# Actor input Schema

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

Leave empty for all 8 cities.

## `daysBack` (type: `integer`):

Permits issued in the last N days (used when no start date is set). Note: some portals publish with a lag of a few days (LA ~7 days).

## `startDate` (type: `string`):

YYYY-MM-DD (overrides Days back).

## `endDate` (type: `string`):

YYYY-MM-DD. Default today.

## `categories` (type: `array`):

Keep permits tagged with any of these (e.g. solar, roofing, hvac, new_construction, adu). Leave empty for all.

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

Keep permits whose type or description contains any keyword (case-insensitive), e.g. heat pump, generator, restaurant.

## `zipCodes` (type: `array`):

Optional 5-digit ZIPs.

## `minValuationUsd` (type: `integer`):

Keep permits with declared project value at least this. Permits without a value are dropped when set.

## `statuses` (type: `array`):

Leave empty for all.

## `includeDescription` (type: `boolean`):

Full scope-of-work text.

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

Remember permit numbers between runs and output only permits not seen before. Ideal for daily lead feeds.

## `stateStoreName` (type: `string`):

Named key-value store for delta mode; use one per saved search.

## `maxItemsPerCity` (type: `integer`):

Cap per city so one big city doesn't crowd out the others.

## `maxItems` (type: `integer`):

Stop after this many rows overall.

## Actor input object example

```json
{
  "cities": [
    "chicago",
    "nyc",
    "austin"
  ],
  "daysBack": 7,
  "includeDescription": true,
  "onlyNew": false,
  "stateStoreName": "us-building-permits-state",
  "maxItemsPerCity": 200,
  "maxItems": 2000
}
```

# Actor output Schema

## `leads` (type: `string`):

No description

## `overview` (type: `string`):

No description

## `all` (type: `string`):

No description

## `summary` (type: `string`):

No description

# 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": [
        "chicago",
        "nyc",
        "austin"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("knobby_woodworker_aig/us-building-permits-multi-city").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": [
        "chicago",
        "nyc",
        "austin",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("knobby_woodworker_aig/us-building-permits-multi-city").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": [
    "chicago",
    "nyc",
    "austin"
  ]
}' |
apify call knobby_woodworker_aig/us-building-permits-multi-city --silent --output-dataset

```

## MCP server setup

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

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/eYNo12fE5FIqe2Zaj/builds/Ibsih7VhthSA9oyrz/openapi.json
