# Building Permits Feed: New US City Permits Daily (`longlever/building-permits-feed`) Actor

New building permits from 23 US city and county open-data feeds (NYC, LA, Chicago, SF, Austin, Seattle, DC and more) in one normalized schema, with a new-since-last-run mode and filters for trade (solar, roofing, HVAC, pool), valuation and contractor.

- **URL**: https://apify.com/longlever/building-permits-feed.md
- **Developed by:** [Todd Handler](https://apify.com/longlever) (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

from $0.90 / 1,000 permit returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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 Feed: new permits from 22 US cities and counties, daily, in one schema

Get every new building permit from the largest US cities that publish permits as open data (New York, Los Angeles, Chicago, San Francisco, Austin, Seattle, Washington DC, Miami-Dade and more) in **one clean schema**. Schedule it in **New mode** and each run returns only the permits issued since your last run.

- **23 city and county feeds, one schema.** Permit number, type, work description, trade tags, valuation, address, ZIP, coordinates, contractor, dates and a link to the permit on the city's own portal.
- **New since last run.** Daily new permits without diffing anything yourself. Late-published permits are still caught.
- **Trade filters that work across cities.** Solar, battery storage, EV chargers, roofing, HVAC, pools, ADUs, new construction, demolition and 15 more, detected from each city's own wording.
- **Valuation, contractor, ZIP and property-use filters.** Only $50k+ jobs, only residential, only your service area, or only a competitor's permits.
- **Monitored sources.** Every run checks each city's dataset for renames, removed columns and stale data, and reports it.
- **Cheap.** $1.50 per 1,000 permits, less on higher Apify plans.

### Who uses it

- **Contractors and home-service companies** (solar, roofing, HVAC, pools, windows) finding homeowners who just pulled a permit
- **Suppliers and distributors** selling to the contractors named on permits
- **Lenders, insurers and proptech** watching renovation and new-construction activity by ZIP
- **Market researchers and journalists** tracking construction trends
- **AI agents** that need fresh, structured permit data (see "For AI agents")

### Cities

| City | Platform | Permits per week (approx.) | Valuation | Contractor | Coordinates | License |
|---|---|---|---|---|---|---|
| New York, NY | Socrata | 2,900 | Yes | Yes | Yes | NYC Open Data Law (no use restrictions) |
| Los Angeles, CA | Socrata | 750 | Yes | No | Yes | City of Los Angeles Open Data terms |
| Chicago, IL | Socrata | 600 | Yes | Yes | Yes | City of Chicago Data Terms of Use |
| San Francisco, CA | Socrata | 250 | Yes | No | Yes | ODC PDDL (public domain) |
| Austin, TX | Socrata | 1,000 | Partial | Yes | Yes | Public domain |
| Seattle, WA | Socrata | 120 | Yes | No | Yes | Public domain |
| New Orleans, LA | Socrata | 260 | Yes | Yes | Yes | CC0 |
| Cincinnati, OH | Socrata | 140 | Yes | Yes | Partial | Public domain |
| Baton Rouge (parish), LA | Socrata | 120 | Yes | Partial | Yes | Public domain |
| Norfolk, VA | Socrata | 220 | Yes | No | Yes | City of Norfolk Open Data terms |
| Montgomery County, MD (residential and commercial) | Socrata | 130 | Yes | No | Yes | Public domain |
| Mesa, AZ | Socrata | 60 | Yes | Partial | Yes | City of Mesa Open Data Terms of Use |
| Kansas City, MO | Socrata | 100 | No | No | No | Open Data KC Terms of Use |
| Marin County, CA | Socrata | 90 | Yes | Yes | Yes | ODbL |
| Nashville, TN | ArcGIS | 220 | Yes | Yes | Yes | Metro Nashville Open Data policy |
| Columbus, OH | ArcGIS | 680 | Partial | No | Yes | CC0 |
| Fort Worth, TX | ArcGIS | 1,100 | Partial | No | No | City of Fort Worth Data Terms of Use |
| Baltimore, MD | ArcGIS | 530 | Yes | No | Yes | Open Baltimore terms |
| Cleveland, OH | ArcGIS | 160 | Yes | Yes | Yes | ODbL |
| Detroit, MI | ArcGIS | 150 | Yes | No | Yes | City of Detroit Open Data terms |
| Miami-Dade County, FL | ArcGIS | 1,000 | Yes | Yes | No | Miami-Dade County open data |
| Washington, DC | ArcGIS | 300 | No | No | Yes | CC BY 4.0 |

Notes:

- **Fort Worth** publishes filing dates, not issue dates, so its rows are new applications (`date_basis: filed`).
- **Kansas City** refreshes weekly. **Los Angeles, Cleveland and DC** publish a few days behind. The default 7-day window absorbs the lag, so nothing is missed in New mode.
- "Contractor" means the city publishes a contractor or permit-holder business name. Austin, Mesa and Miami-Dade also publish a contractor phone.

Want another city? Open an issue with the portal link. Cities on Socrata or ArcGIS are quick to add.

### Quick start

1. Pick your **Cities** (leave empty for all of them).
2. Optional: pick **Trades**, set a **Minimum valuation**, or add **ZIP codes**.
3. Click **Start**. Results appear in the **Permits** table and export as JSON, CSV or Excel.

#### Get new permits every day (recommended)

1. Set **Mode** to **New since last run** and keep **Lookback window** at 7 days.
2. Create a daily schedule under **Schedules** in Apify Console.
3. Send results to Google Sheets, Slack, email, a CRM, a webhook, Make or n8n with Apify integrations.

The first run returns every matching permit in the window (turn off **Return the window on the first New-mode run** to start silent). After that you get only permits you have not received before, marked `change_type: new`. Each run rechecks the whole window, so a permit the city publishes three days late still reaches you once.

The saved state is keyed to your filters, so changing a filter starts a fresh baseline. Set **State key** to give it a fixed name.

### Filters

| Filter | What it does |
|---|---|
| Trades (any) | `solar`, `battery_storage`, `ev_charger`, `generator`, `roofing`, `hvac`, `pool`, `electrical`, `plumbing`, `gas`, `new_construction`, `addition`, `remodel`, `adu`, `demolition`, `fence`, `sign`, `fire_protection`, `windows_doors`, `siding`, `foundation`, `deck_patio`, `elevator`, `grading_site`, `mechanical` |
| Permit categories (any) | `building`, `electrical`, `plumbing`, `mechanical`, `demolition`, `fire`, `sign`, `elevator`, `grading`, `other` |
| Permit type contains | Words matched against the city's own type, subtype and work class |
| Keywords and exclude keywords | Words matched against the description and type, for example `tesla` or `generator` |
| Minimum and maximum valuation | Declared job value in USD. Permits with no value are dropped when either is set |
| Property use | `residential` or `commercial`, where the city publishes it |
| Contractor or applicant contains | Track one company's permits, or a competitor's |
| Only permits that name a contractor | Drop permits with no contractor |
| ZIP codes | Exact ZIPs or prefixes, for example `787` for central Austin |
| Max permits per city | Snapshot mode only, keeps the newest |

Trade tags come from the permit type and the work description together, so a re-roof with a solar install gets both `roofing` and `solar`. Where a city has its own flags (Los Angeles solar and EV, San Francisco re-roof and ADU), those are used too.

### Output

One row per permit. A real example:

```json
{
  "permit_id": "cleveland:BRE26-043897",
  "source_id": "cleveland",
  "city": "Cleveland",
  "state": "OH",
  "permit_number": "BRE26-043897",
  "permit_type": "Construction Project",
  "permit_subtype": "Building Permits",
  "work_class": "Residential Electrical",
  "description": "5.785 kW Roof-Mount Solar System Installation. BUILDING PERMIT NO. BRB26-040076",
  "category": "electrical",
  "trade_tags": ["solar", "electrical"],
  "property_use": "residential",
  "status": "Inspection Pending",
  "applied_date": "2026-10-09",
  "issued_date": "2026-10-15",
  "date_basis": "issued",
  "valuation": 4698.0,
  "square_feet": null,
  "housing_units": null,
  "address": "2501 SCRANTON RD, CLEVELAND, OH, 44113",
  "zip": "44113",
  "latitude": 41.478103,
  "longitude": -81.694522,
  "parcel_id": "00411011",
  "contractor_name": "YELLOWLITE, INC",
  "contractor_license": "ELECT20-001512",
  "contractor_phone": null,
  "applicant_name": null,
  "record_url": "https://aca-prod.accela.com/COC/Cap/CapDetail.aspx?Module=BuildingHousing&...",
  "source_url": "https://data.clevelandohio.gov/datasets/c08ddeaf2d1e41679a03103ff4e9fe17",
  "license": "ODbL 1.0 (attribution, share-alike)",
  "change_type": null,
  "fetched_at": "2026-10-22T01:10:33Z",
  "raw": null
}
```

**Field notes**

- Fields a city does not publish are `null`. Valuations of $0 or $1 are placeholders in most cities and come through as `null`.
- `permit_id` is stable across runs: the city ID plus the permit number (New York adds the filing sequence, so a renewal is a new row).
- `issued_date` is the city's local date. `date_basis` is `filed` for Fort Worth.
- `record_url` links to the permit on the city's own permit system, where the city provides one (Austin, Seattle, Cincinnati, Columbus, Cleveland).
- `source_url` and `license` name the dataset each row came from. Keep them if you republish the data. Some datasets (Cleveland and Marin County, ODbL; DC, CC BY) ask for attribution.
- Turn on **Include the original record** to get every field the city publishes under `raw`.
- Owner names are not in the normalized fields. Where a city publishes them, they are in `raw`.

### Source health

Each run writes a `SUMMARY` record to the key-value store with one entry per city:

| Status | Meaning | Rows delivered? |
|---|---|---|
| `OK` | Dataset found, columns as expected, data is fresh | Yes |
| `RENAMED` | The dataset title changed | Yes |
| `SCHEMA_CHANGED` | A column the mapping reads is gone; those output fields are empty | Yes |
| `STALE` | The newest record is older than expected for that city | Yes, if any |
| `MISSING` | The dataset ID no longer resolves (moved or retired) | No |
| `BROKEN` | The date or permit-number column is gone | No |
| `ERROR` | The portal failed after retries | No |

The run status message lists any city that is not `OK`. One city failing never stops the others. The maintainer runs the same check on a schedule and updates a city's mapping when a portal changes.

### Pricing

Pay per event. You only pay for what the run delivers.

| Event | Price (Free and Starter plans) |
|---|---|
| Permit returned | $0.0015 ($1.50 per 1,000) |
| City checked (one per city per run) | $0.0005 |

Higher Apify plans get lower per-permit prices. A daily New-mode run over 5 cities filtered to roofing costs about $0.0025 for the checks plus about 4 cents for the permits, roughly $1.50 a month. Set a maximum cost per run in the run options and the Actor stops cleanly at that limit. In New mode, a city cut off by the limit is not marked as delivered, so the next run sends it.

### For AI agents

- **Input:** `cities` (array of city IDs from the table, empty for all), `mode` (`snapshot` or `new`), `daysBack`, and the optional filters above (`trades`, `minValuation`, `zipCodes`, `contractors`, `propertyUse`).
- **Output:** one row per permit; the `SUMMARY` key-value record holds per-city health.
- **Cost:** $0.0015 per permit plus $0.0005 per city. Use `trades`, `zipCodes` or `maxPermitsPerCity` to keep runs small.
- **City IDs:** `new-york-city`, `los-angeles`, `chicago`, `san-francisco`, `austin`, `seattle`, `new-orleans`, `cincinnati`, `baton-rouge`, `norfolk`, `montgomery-county-md-residential`, `montgomery-county-md-commercial`, `mesa`, `kansas-city`, `marin-county`, `nashville`, `columbus`, `fort-worth`, `baltimore`, `cleveland`, `detroit`, `miami-dade-county`, `washington-dc`.

### Limits and good practice

- The Actor reads public open-data APIs (Socrata and ArcGIS REST) that cities provide for reuse. It does not log in or scrape permit-system web pages.
- Requests are rate-limited per portal and retried on temporary errors.
- Each city provides its data as is and makes no warranty about it. This Actor is not affiliated with or endorsed by any city.
- Data is as accurate as each city's publication. Trade tags are keyword based and occasionally miss or over-tag unusual wording.
- Contractor phones are business lines as the city publishes them. Follow the rules that apply to your outreach (for example, do-not-call rules for phone marketing).

### Data sources and required notices

All permit data comes from the cities' and counties' own open-data portals. Each row's `source_url` and `license` name the dataset it came from. Data from the City of Mesa, Arizona is credited to the City of Mesa. Each city provides its data as is, makes no warranty about it, and has not endorsed this Actor.

The following notices are required by the cities' terms of use:

- City of Chicago: "This site provides applications using data that has been modified for use from its original source, www.cityofchicago.org, the official website of the City of Chicago. The City of Chicago makes no claims as to the content, accuracy, timeliness, or completeness of any of the data provided at this site. The data provided at this site is subject to change at any time. It is understood that the data provided at this site is being used at one's own risk."
- City of Kansas City: "This site provides applications using data that has been modified for use from its original source, www.data.kcmo.gov, the official open data website of the City of Kansas City. The City of Kansas City makes no claims as to the content, accuracy, timeliness, or completeness of any of the data provided at this site. The data provided at this site is subject to change at any time. It is understood that the data provided at this site is being used at one's own risk."
- City of Fort Worth: the City of Fort Worth does not warrant the data in any way and makes each of the disclaimers in its [Data Disclaimer and Terms of Use](https://mapit.fortworthtexas.gov/disclaimer.pdf).

### Support

Open an issue on the Actor page. Issues are answered within one business day. If the Actor saves you time, a rating helps other people find it.

# Actor input Schema

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

Which city and county permit feeds to check. Leave empty to check all of them.

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

Snapshot returns every matching permit issued in the lookback window. New returns only permits not returned by an earlier run with the same filters (schedule this daily).

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

Permits issued within this many days. In New mode the whole window is rechecked every run, so permits a city publishes a few days late are still caught. 7 suits a daily schedule.

## `trades` (type: `array`):

Keep permits tagged with any of these trades. Tags come from the permit type and work description.

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

Keep permits in any of these categories, derived from the permit type.

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

Keep permits whose type, subtype or work class contains any of these words, for example "Express" or "Single Family".

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

Keep permits whose description or type contains any of these (case-insensitive), for example "tesla" or "generator".

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

Drop permits whose description or type contains any of these.

## `minValuation` (type: `integer`):

Keep permits with a declared job value at or above this. Permits with no value are dropped when this is set.

## `maxValuation` (type: `integer`):

Keep permits with a declared job value at or below this.

## `propertyUse` (type: `string`):

Residential or commercial, where the city publishes it. Mixed-use permits match both.

## `contractors` (type: `array`):

Keep permits whose contractor or applicant name contains any of these, for example a competitor's name.

## `requireContractor` (type: `boolean`):

Drop permits with no contractor name. Not every city publishes contractors (see README).

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

Keep permits in these ZIP codes. A prefix like "787" matches every ZIP starting with it.

## `maxPermitsPerCity` (type: `integer`):

0 means no limit. Newest permits are kept. Ignored in New mode so no permit is ever skipped.

## `includeRawRecord` (type: `boolean`):

Add every field the city publishes, unmodified, under raw. Makes rows larger.

## `emitBaselineOnFirstRun` (type: `boolean`):

On the first run in New mode, return everything in the window. Turn off to only save a baseline, so the first alert carries only truly new permits.

## `stateKey` (type: `string`):

Optional name for the saved state. By default it is derived from your filters, so changing a filter starts a fresh baseline.

## `maxConcurrency` (type: `integer`):

Requests to each portal are also rate-limited.

## Actor input object example

```json
{
  "cities": [
    "chicago",
    "austin",
    "seattle",
    "nashville",
    "columbus"
  ],
  "mode": "snapshot",
  "daysBack": 3,
  "propertyUse": "any",
  "requireContractor": false,
  "maxPermitsPerCity": 20,
  "includeRawRecord": false,
  "emitBaselineOnFirstRun": true,
  "maxConcurrency": 6
}
```

# Actor output Schema

## `permits` (type: `string`):

One row per permit. In New mode, only permits not returned by an earlier run.

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

Every city checked with its status (OK, RENAMED, SCHEMA_CHANGED, STALE, MISSING, BROKEN, ERROR), newest record date and counts.

# 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",
        "austin",
        "seattle",
        "nashville",
        "columbus"
    ],
    "daysBack": 3,
    "maxPermitsPerCity": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("longlever/building-permits-feed").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",
        "austin",
        "seattle",
        "nashville",
        "columbus",
    ],
    "daysBack": 3,
    "maxPermitsPerCity": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("longlever/building-permits-feed").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",
    "austin",
    "seattle",
    "nashville",
    "columbus"
  ],
  "daysBack": 3,
  "maxPermitsPerCity": 20
}' |
apify call longlever/building-permits-feed --silent --output-dataset

```

## MCP server setup

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

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/PupWfSQx5wphTg75t/builds/ts97BQbPBJthziFIi/openapi.json
