# US Building Permits Scraper: Construction Leads, 17 Cities (`latamdatatools/us-building-permits`) Actor

New building permits from official open-data portals of Chicago, New York, Los Angeles, San Francisco, Seattle, Austin and 11 more US cities and counties in one schema: type, work tags, value, address, dates and contractor company. Value filters and an only-new mode for daily lead alerts.

- **URL**: https://apify.com/latamdatatools/us-building-permits.md
- **Developed by:** [Julian Genuario](https://apify.com/latamdatatools) (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 $4.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

## US Building Permits Scraper: Construction Leads, 17 Cities

Get **newly issued building permits** from the official open-data portals of 17 US cities and counties in **one clean schema**, with filters for project value, property type, kind of work and keywords, plus an **"only new" mode** for daily lead alerts.

Every portal publishes permits with its own column names, date formats and quirks. This Actor reads each city's official Socrata API, maps everything to the same fields and gives you one table you can drop into a CRM, a spreadsheet or a webhook.

### What does US Building Permits Scraper do?

US Building Permits Scraper collects new building permits from the official open-data portals of 17 US cities and counties (Chicago, New York, Los Angeles, San Francisco, Seattle, Austin and more) in one normalized schema: permit type, work tags (solar, roofing, new construction...), job value, address, dates and contractor company. Use it for daily construction leads for contractors, suppliers and solar installers.

### How to use US Building Permits Scraper

1. Click **Try for free** on this page (a free Apify account is enough to start).
2. Fill in the input (see the examples below) or keep the prefilled example.
3. Click **Start** and wait for the run to finish.
4. Download the results from the **Output** tab as JSON, CSV, Excel or HTML, or read them with the Apify API.
5. Optional: create a **Schedule** to run it daily or weekly, and connect a webhook or an integration (Make, Zapier, n8n, Slack).

### Who uses it

- **Contractors and subcontractors**: roofers, electricians, HVAC, plumbers, pool builders, fence and window companies looking for jobs that just got permitted.
- **Building material suppliers and equipment rental**: find new construction and large remodels early.
- **Solar, EV charger and battery installers**: track solar and EV permits by city to size the market or watch competitors.
- **Real-estate, lenders and market analysts**: new construction and ADU activity by ZIP code.

### Cities and counties covered

| Source key | Jurisdiction | Portal | Contractor company |
|---|---|---|---|
| `chicago` | Chicago, IL | data.cityofchicago.org | yes |
| `new-york-city` | New York City, NY (DOB NOW: Build) | data.cityofnewyork.us | yes |
| `los-angeles` | Los Angeles, CA | data.lacity.org | no |
| `san-francisco` | San Francisco, CA | data.sf.gov | no |
| `seattle` | Seattle, WA | data.seattle.gov | yes |
| `austin` | Austin, TX | data.austintexas.gov | yes |
| `cincinnati` | Cincinnati, OH | data.cincinnati-oh.gov | yes |
| `norfolk` | Norfolk, VA | data.norfolk.gov | no |
| `montgomery-county-md` | Montgomery County, MD (residential + commercial) | data.montgomerycountymd.gov | no |
| `cambridge` | Cambridge, MA (new construction + additions/alterations) | data.cambridgema.gov | partial |
| `marin-county` | Marin County, CA | data.marincounty.gov | yes |
| `corona` | Corona, CA | corstat.coronaca.gov | no |
| `baton-rouge` | East Baton Rouge Parish, LA | data.brla.gov | yes |
| `new-orleans` | New Orleans, LA | data.nola.gov | yes |
| `mesa` | Mesa, AZ | citydata.mesaaz.gov | yes |
| `buffalo` | Buffalo, NY | data.buffalony.gov | partial |
| `orlando` | Orlando, FL | data.cityoforlando.net | yes |

Most portals update daily; a few weekly (Corona). New permits usually show up 1-5 days after they are issued.

### Privacy by design

Permit datasets often include owner names, applicant names and phone numbers. **This Actor never outputs them.** Only these are returned:

- the **contractor company name**, and only when it is clearly a business (a legal suffix like LLC / Inc / Corp, or a trade name like "Roofing", "Electric", "Construction"). Names that look like a person are dropped, and "Company LLC - John Smith" becomes "Company LLC";
- the **job site address**, permit type, work description, value and dates.

Phone numbers, email addresses and long account numbers that some cities leave inside the free-text description are replaced with `[phone removed]`, `[email removed]` or `[number removed]`.

### Input

| Field | What it does |
|---|---|
| **Cities / counties** | Any combination of the sources above. |
| **Days back** / **Issued from / to** | Issue-date window (up to 366 days per run). |
| **Minimum / maximum project value** | Declared job value in USD. Permits without a value are dropped unless you turn on **Keep permits without a value**. |
| **Property type** | Residential or commercial (from the city's class field when there is one, otherwise from type and use). |
| **Work tags** | New construction, addition/alteration, demolition, roofing, solar, electrical, plumbing, mechanical/HVAC, pool, sign, fence, EV charger, ADU, windows/doors, fire protection, elevator. |
| **Keywords** | Any word or phrase in the type, work class, description or contractor. |
| **Only new permits** + **Alert name** | Incremental mode: each run returns only permits not delivered before for that alert. Schedule it daily. |
| **Max permits per run / per city**, **Max pages per city** | Limits. |
| **Socrata app token** | Optional, only for very large pulls. |

#### Example: daily alert of new homes and solar jobs over USD 50,000

```json
{
  "sources": ["chicago", "austin", "los-angeles", "seattle"],
  "daysBack": 7,
  "minValue": 50000,
  "workTags": ["new-construction", "solar"],
  "onlyNew": true,
  "alertName": "new-homes-and-solar"
}
```

### Output

One row per permit, always with the same keys (`null` when a city does not publish that field):

```json
{
  "source": "seattle",
  "city": "Seattle",
  "state": "WA",
  "permitId": "7120118-CN",
  "permitType": "Addition/Alteration",
  "workType": "Single Family/Duplex",
  "description": "Allow housing for new detached housing unit per land use code. Existing detached dwelling to remain. Construct new one family dwelling per plan.",
  "status": "Issued",
  "valuationUsd": 258470,
  "squareFeet": null,
  "housingUnits": 1,
  "appliedDate": "2026-04-20",
  "issuedDate": "2026-10-23",
  "expiresDate": null,
  "address": "510 N 143RD ST",
  "zip": "98133",
  "latitude": 47.73262864,
  "longitude": -122.35252131,
  "contractorCompany": null,
  "propertyClass": "residential",
  "recordUrl": "https://services.seattle.gov/portal/customize/LinkToRecord.aspx?altId=7120118-CN",
  "workTags": ["new-construction", "addition-alteration"],
  "matchedKeywords": [],
  "datasetUrl": "https://data.seattle.gov/d/76t5-zqzr",
  "fetchedAt": "2026-10-26T22:01:00.247Z"
}
```

A `RUN_SUMMARY` record in the key-value store lists rows read, output, filtered and already seen per city, plus any source errors.

Export as JSON, CSV, Excel or send to Make, Zapier, n8n, Google Sheets or a webhook with Apify integrations.

### Pricing

Pay per event:

- **USD 0.01 per run** (start).
- **USD 4 per 1,000 permits** in your results (USD 0.004 each).

Permits removed by your filters, duplicates and (in "only new" mode) permits already delivered are **not charged**. If a city's portal is down, that city is skipped with an error in the summary and nothing is charged for it. The Actor stops when your "max charge per run" is reached.

### Good to know

- Values are the **declared** job value from the application, not a final cost. Many trade permits (electrical, plumbing, mechanical) carry no value.
- Work tags and residential/commercial come from the city's own fields and text matching: good for filtering, not a legal classification.
- Each city decides what it publishes. Some cities do not publish contractor names at all (see the table).
- Data comes from official public open-data APIs, read politely (about 1 request per second per portal, as their robots.txt asks). Check each portal's terms for your use case.

### FAQ

**Can I call it from my own code or from an AI agent?** Yes. Start runs and fetch results with the Apify API or the JavaScript / Python clients, or use it as a tool from Claude, Cursor and other MCP clients through the Apify MCP server (`mcp.apify.com`).

**Can I run it automatically?** Yes. Create a Schedule in Apify Console (for example every morning) and add a webhook or an integration to get the results where you need them.

**Which export formats are available?** JSON, JSONL, CSV, Excel (XLSX), XML, RSS and HTML table, from the Output tab or the API.

**What does it cost?** See the Pricing section above. You only pay for the events listed there.

### Changelog

- 0.1 (2026-10): first version, 19 datasets in 17 jurisdictions.

# Actor input Schema

## `sources` (type: `array`):

Official open-data portals to read (Socrata API of each city or county).

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

Permits issued in the last N days (0 = today only). Ignored when "Issued from" is set, and on "only new" runs after the first one. Cities publish with a 1-5 day lag.

## `dateFrom` (type: `string`):

YYYY-MM-DD. Maximum range 366 days per run.

## `dateTo` (type: `string`):

YYYY-MM-DD. Default: today.

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

Keep permits whose declared job value is at least this amount.

## `maxValue` (type: `integer`):

Keep permits whose declared job value is at most this amount.

## `includeWithoutValue` (type: `boolean`):

Some cities do not publish a value for every permit (Austin trade permits, for example). Turn on to keep those when you set a minimum or maximum value.

## `propertyClass` (type: `string`):

Residential or commercial comes from the class field of the city when it has one, otherwise from the permit type and use. Permits that cannot be classified are dropped by this filter.

## `workTags` (type: `array`):

Keep permits with ANY of these tags (derived from permit type and description).

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

Keep permits where ANY keyword appears in the type, work class, description or contractor (case insensitive).

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

Remember what was delivered and return only permits not seen in earlier runs with the same alert name. Ideal for a daily schedule.

## `alertName` (type: `string`):

Separate memory per alert, so several schedules with different filters do not interfere.

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

Stop after this many permits in total.

## `maxItemsPerSource` (type: `integer`):

Cap per city or county.

## `maxPagesPerSource` (type: `integer`):

1 page = 1,000 permits read from the portal (before your filters).

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

Free token from any Socrata portal (Developer settings). Only needed for very large pulls; the public API works without it.

## Actor input object example

```json
{
  "sources": [
    "chicago",
    "seattle",
    "austin"
  ],
  "daysBack": 7,
  "includeWithoutValue": false,
  "propertyClass": "any",
  "onlyNew": false,
  "alertName": "default",
  "maxItems": 1000,
  "maxItemsPerSource": 0,
  "maxPagesPerSource": 30
}
```

# Actor output Schema

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

No description

## `runSummary` (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 = {
    "sources": [
        "chicago",
        "seattle",
        "austin"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("latamdatatools/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 = { "sources": [
        "chicago",
        "seattle",
        "austin",
    ] }

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

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,latamdatatools/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/2KdktlyyaPbs52wOV/builds/fnca84hRM17f4r27S/openapi.json
