# Building Permit Signal — new US permits, contractors, values (`handa-labs/us-building-permit-monitor`) Actor

New building permits from official open data of NYC, Chicago, SF, LA, Seattle, Austin, Cincinnati and Montgomery County MD. Filter by keyword, value and ZIP; contractor names where published; only new permits since your last run.

- **URL**: https://apify.com/handa-labs/us-building-permit-monitor.md
- **Developed by:** [tatsuya handa](https://apify.com/handa-labs) (community)
- **Categories:** Lead generation, Real estate, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 permit records

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

### What is Building Permit Signal for?

**Newly issued building permits are the earliest public sign that money is about to be spent on a property.** This Actor reads the **official open-data permit registries** of eight US cities and counties, keeps only the permits that match your trade keywords, minimum value and ZIP codes, and in *monitor* mode returns **only the permits it has not shown you before**. Where the city publishes it, you also get the **contractor's business name, trade and phone**.

Typical users:

- **Building-product and service vendors** (roofing supply, HVAC, solar, windows, scaffolding, dumpsters, cleaning) who want the address and value of every new job in their territory the day it is permitted.
- **Subcontractors and suppliers** who want to know which general contractors are pulling permits right now.
- **Lenders, insurers, proptech and real-estate investors** tracking renovation and construction activity by ZIP.
- **AI agents and workflows** that need a clean JSON feed of "new construction activity" without scraping city portals.

No scraping of city websites: everything comes from the cities' own Socrata open-data APIs, so it does not break when a portal is redesigned and there is no proxy cost.

### Sample output

A `permit` record (Seattle, roofing keyword):

```json
{
  "type": "permit",
  "change": "new",
  "permitId": "seattle:6987654-CN",
  "city": "seattle",
  "cityName": "Seattle",
  "issuedDate": "2026-09-03",
  "permitType": "Building / Construction Permit",
  "workType": "Single Family/Duplex",
  "description": "Replace roof covering on existing single family residence, subject to field inspection",
  "value": 42000,
  "address": "1234 NW 65TH ST",
  "zip": "98117",
  "contractorName": "RAIN CITY ROOFING LLC",
  "contractorLicense": null,
  "contractorTrade": null,
  "contractorPhone": null,
  "status": "Permit Issued",
  "sourceUrl": "https://cosaccela.seattle.gov/portal/customize/LinkToRecord.aspx?altId=6987654-CN",
  "sourceDataset": "https://data.seattle.gov/d/76t5-zqzr",
  "latitude": 47.676,
  "longitude": -122.362,
  "checkedAt": "2026-09-04T03:50:12.000Z",
  "llmSummary": "Seattle: permit 6987654-CN issued 2026-09-03 (Single Family/Duplex), value 42000 at 1234 NW 65TH ST 98117; contractor RAIN CITY ROOFING LLC. Replace roof covering on existing single family residence, subject to field inspection"
}
```

The first record of every run is a `summary` with counts per city, the number of permits with a named contractor, the sum of declared values and any source errors.

### What you get

| Field | Meaning |
| --- | --- |
| `permitId` | `city:permit number`, stable across runs |
| `issuedDate` | Date the permit was issued (`YYYY-MM-DD`) |
| `permitType`, `workType` | City's own permit type and work class / use |
| `description` | Work description as filed |
| `value` | Declared or estimated job value in USD (null when the city does not publish it) |
| `address`, `zip`, `latitude`, `longitude` | Site of the work |
| `contractorName`, `contractorTrade`, `contractorPhone`, `contractorLicense` | Contractor business details **where the city publishes them** |
| `status` | Current permit status |
| `sourceUrl` | Link to the city's permit record when available |
| `change` | `new` (monitor mode), `baseline` (first run with `emitBaseline`) or `snapshot` |
| `llmSummary` | One-sentence description for LLM agents and alerts |

#### Coverage

| City | Dataset | Contractor name | Value | ZIP |
| --- | --- | --- | --- | --- |
| New York City (DOB NOW) | [rbx6-tga4](https://data.cityofnewyork.us/d/rbx6-tga4) | yes (business + license type) | yes | yes |
| Chicago | [ydr8-5enu](https://data.cityofchicago.org/d/ydr8-5enu) | yes (contractor contact) | yes | no |
| San Francisco | [i98e-djp9](https://data.sfgov.org/d/i98e-djp9) | no | yes | yes |
| Los Angeles | [pi9x-tg5x](https://data.lacity.org/d/pi9x-tg5x) | no | yes | yes |
| Seattle | [76t5-zqzr](https://data.seattle.gov/d/76t5-zqzr) | yes | yes | yes |
| Austin | [3syk-w9eu](https://data.austintexas.gov/d/3syk-w9eu) | yes (+ trade, phone) | yes | yes |
| Cincinnati | [uhjb-xac9](https://data.cincinnati-oh.gov/d/uhjb-xac9) | yes | yes | yes |
| Montgomery County, MD | [m88u-pqki](https://data.montgomerycountymd.gov/d/m88u-pqki) | no | yes | yes |

Owner and applicant **personal names are never exported**; only business names, license numbers and business phones are.

### Settings

| Input | Default | Notes |
| --- | --- | --- |
| `cities` | nyc, chicago, seattle, austin | Any subset of the eight registries |
| `keywords` | – | Any-match, case-insensitive, against description, work type, permit type and contractor trade. Empty = all permits |
| `excludeKeywords` | – | Drop permits containing any of these |
| `minValue` | – | Minimum declared value in USD |
| `zipPrefixes` | – | e.g. `981`, `78701` |
| `contractorOnly` | false | Only permits with a named contractor |
| `daysBack` | 7 | Window of issue dates (1–90). Use 1–3 for daily schedules, up to 90 for a first snapshot |
| `mode` | monitor | `monitor` = only permits not seen before; `snapshot` = everything matching |
| `watchlistId` | default | Name for the memory of a watch. Filters are part of the key too |
| `emitBaseline` | false | Emit the current permits on the first run of a watch (charged) |
| `maxPermits` | 2000 | Cap per run, most recent first |
| `socrataAppToken` | – | Optional; raises the open-data rate limit |

#### Monitor mode, watchlists and the free first run

1. Choose a `watchlistId` (e.g. `seattle-roofing`) and your filters. Run once. The **first run is a free baseline**: the Actor remembers the permits currently in the window and returns only a summary (no permit records are charged) unless `emitBaseline` is on.
2. Schedule the same input daily. Each later run returns **only permits issued since the last run** and charges only for those.
3. Different watchlists keep separate memories. Changing filters starts a new baseline for that watchlist.

Pricing: one `run-summary` event per non-baseline run, one `permit` event per permit record. No charge for Apify compute beyond the Actor start.

#### Example: solar and HVAC jobs over $20k in Austin and Seattle, daily

```json
{
  "cities": ["austin", "seattle"],
  "keywords": ["solar", "hvac", "heat pump", "mechanical"],
  "minValue": 20000,
  "daysBack": 3,
  "mode": "monitor",
  "watchlistId": "solar-hvac-tx-wa"
}
```

### Notes and limits

- Dates and values are whatever the city publishes; some registries lag a day or two behind the counter.
- Chicago does not publish ZIP codes; use `keywords` or the coordinates instead.
- Los Angeles publishes with roughly a one-week lag; use `daysBack` ≥ 10 there.
- Seattle and Austin publish contractor names and values only for a subset of permits.
- Chicago's contractor is taken from the first contact typed "…CONTRACTOR".
- New York City is served from DOB NOW (2020 onward). Legacy BIS permits are not included.
- Results are official public records provided as-is; verify licenses and values with the issuing authority before relying on them.

### 日本語

米国8都市・郡(ニューヨーク、シカゴ、サンフランシスコ、ロサンゼルス、シアトル、オースティン、シンシナティ、モンゴメリー郡)が公式オープンデータとして公開する**建築許可の新規発行**を取得します。キーワード(屋根・太陽光・空調など)、金額下限、ZIP で絞り込み、都市が公開している場合は**施工業者の会社名・業種・電話**も付きます。`monitor` モードでは前回以降に発行された許可だけを返し、初回はベースライン(無課金)です。個人名は出力しません。

# Actor input Schema

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

Which official permit registries to read.

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

Case-insensitive; matched against the work description, work type, permit type and contractor trade. Leave empty for all permits.

## `excludeKeywords` (type: `array`):

Drop permits whose description or type contains any of these.

## `minValue` (type: `integer`):

Keep only permits whose declared/estimated job value is at least this. Permits without a value are dropped when set.

## `zipPrefixes` (type: `array`):

Keep only permits whose ZIP starts with one of these (e.g. "981", "787"). Chicago does not publish ZIPs.

## `contractorOnly` (type: `boolean`):

Contractor names are published by NYC, Chicago, Seattle, Austin and Cincinnati.

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

Look at permits issued within the last N days (1–90).

## `mode` (type: `string`):

monitor = remember what was returned and only emit permits not seen before (first run is a free baseline). snapshot = return everything matching every time.

## `watchlistId` (type: `string`):

Name for this watch (e.g. "seattle-roofing"). Separate IDs keep separate memories; the filters are part of the key too.

## `emitBaseline` (type: `boolean`):

On the very first run of a watchlist, also output the current permits (charged). Default: only a free summary.

## `maxPermits` (type: `integer`):

Cap on permits kept per run (most recent first).

## `socrataAppToken` (type: `string`):

Raises the open-data rate limit. Not needed for normal use.

## Actor input object example

```json
{
  "cities": [
    "nyc",
    "chicago",
    "seattle",
    "austin"
  ],
  "keywords": [
    "roof",
    "solar",
    "hvac"
  ],
  "contractorOnly": false,
  "daysBack": 7,
  "mode": "monitor",
  "watchlistId": "default",
  "emitBaseline": false,
  "maxPermits": 2000
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

## `consoleRun` (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": [
        "nyc",
        "chicago",
        "seattle",
        "austin"
    ],
    "keywords": [
        "roof",
        "solar",
        "hvac"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("handa-labs/us-building-permit-monitor").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": [
        "nyc",
        "chicago",
        "seattle",
        "austin",
    ],
    "keywords": [
        "roof",
        "solar",
        "hvac",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("handa-labs/us-building-permit-monitor").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": [
    "nyc",
    "chicago",
    "seattle",
    "austin"
  ],
  "keywords": [
    "roof",
    "solar",
    "hvac"
  ]
}' |
apify call handa-labs/us-building-permit-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,handa-labs/us-building-permit-monitor"
        }
    }
}

```

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/jlvZhkO9P6fmBDbMQ/builds/zCXjAXGzMUpINHJMP/openapi.json
