# Building Permits Scraper: Construction Leads from 11 US Cities (`great_stuff/building-permits-scraper`) Actor

Newly issued building permits from 11 US city and county open-data portals: address, type, description, valuation, dates and contractor. Filter by city, keyword (solar, roof, pool, ADU) and value.

- **URL**: https://apify.com/great_stuff/building-permits-scraper.md
- **Developed by:** [Kamal Katul](https://apify.com/great_stuff) (community)
- **Categories:** Lead generation, Real estate
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.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?

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: New Construction Leads from 11 US Cities

Get **newly issued building permits** from official city open-data portals in **one clean, unified format**: permit number, type, work description, address, ZIP, map coordinates, declared project value, square footage, housing units and the **contractor's business name** (plus phone, email and license where the city publishes them).

Instead of learning 11 different portals, column names and date formats, you pick the cities, a date window and your filters, and get one spreadsheet-ready dataset.

### Use cases

- **Roofers, solar installers, residential HVAC, pool, window and remodeling companies**: find homes that just got a permit in your trade or in your area.
- **Building-material suppliers and equipment rental**: reach the general contractors who just started a job worth more than $100k.
- **Real-estate investors and analysts**: track new construction, ADUs and multifamily units by ZIP.
- **Insurance, lending and proptech**: monitor renovation and new-build activity.
- **Market researchers and journalists**: compare construction activity across cities.

### FAQ

**Which cities are covered?**
Austin, Dallas, Chicago, New York City, Los Angeles, San Francisco, Seattle, Baton Rouge, Cincinnati, Mesa and Marin County, from each city's official open-data portal.

**Why does one permit number appear more than once?**
Some cities issue several permits under one job number (for example foundation and structural) or list one permit at several addresses. Each is a separate record. Exact duplicate rows are removed and never charged.

**How far back can I go?**
Up to 10 years for most cities. Dallas publishes only about the latest 92 days through its API.

**Do I need an API key?**
No. A free Socrata app token is optional and only helps with very large pulls.

### Supported cities

| Key | City | Contractor data |
|---|---|---|
| `austin` | Austin, TX | name, trade, phone |
| `dallas` | Dallas, TX | name (when filled), latest 92 days only |
| `chicago` | Chicago, IL | all contractors by role (general, electrical, plumbing...) |
| `new-york` | New York City, NY (DOB NOW) | applicant business, license |
| `los-angeles` | Los Angeles, CA | none published |
| `san-francisco` | San Francisco, CA | none published |
| `seattle` | Seattle, WA | name (some permits) |
| `baton-rouge` | Baton Rouge / East Baton Rouge Parish, LA | name |
| `cincinnati` | Cincinnati, OH | company name |
| `mesa` | Mesa, AZ | name, phone, email, license |
| `marin-county` | Marin County, CA | name, license |

All sources are official government open-data portals (Socrata SODA API). No login, no browser, no proxies.

### How to use

1. Pick one or more **Cities**.
2. Choose **Issued in the last N days** (default 7) or an exact **Issued from / to** date range.
3. Optionally narrow it down: **Description contains** (e.g. `solar`, `roof`, `pool`, `ADU`, `new construction`), **Minimum project value**, **Residential or commercial**, **Only permits with a contractor name**.
4. Set **Max permits per city** and run. Download as CSV, Excel or JSON, or connect via API, Zapier or Make. Schedule it daily or weekly for a fresh lead feed.

### Input

| Field | Type | Description |
|---|---|---|
| `cities` | array | City keys from the table above. Required. |
| `daysBack` | integer | Issued within the last N days (default 7). |
| `dateFrom` / `dateTo` | string | Exact window, `YYYY-MM-DD`. |
| `keywords` | array | Keep permits whose description contains any of these words. |
| `excludeKeywords` | array | Drop permits whose description contains any of these words. |
| `permitClass` | string | `any`, `residential` or `commercial` (best effort; uses the city's class/type and, where missing, the description). |
| `minValuation` | integer | Minimum declared project value in USD. |
| `requireContractor` | boolean | Only permits that list a contractor. |
| `maxPermitsPerCity` | integer | Newest-first cap per city (default 100). |
| `socrataAppToken` | string | Optional free Socrata token for very large pulls. |

Example:

```json
{
  "cities": ["chicago", "new-york", "seattle"],
  "daysBack": 14,
  "keywords": ["roof", "solar"],
  "minValuation": 10000,
  "permitClass": "residential",
  "maxPermitsPerCity": 50
}
```

### Output

Every permit has the same fields regardless of city (`null` when the city does not publish that value). Real example from a test run on 2026-10-28:

```json
{
  "city": "Chicago",
  "state": "IL",
  "permitNumber": "100000000",
  "permitType": "PERMIT - NEW CONSTRUCTION",
  "permitClass": null,
  "workType": null,
  "description": "SPR 2019 CBRC. ERECT A NEW 2 STORY SINGLE FAMILY RESIDENCE W/ BASEMENT, REAR PATIO AND DETACHED 2 CAR FRAME GARAGE WITH 2 EVSE-READY PARKING SPACE WITH ROOF DECK ACCESSED BY 1-STORY OPEN STAIR. INSTALL 6 FEET HIGH WOOD FENCE AND 6 FEET IRON FENCE. ALL WORK AS PER PLANS.",
  "status": "ACTIVE",
  "appliedDate": "2026-07-08",
  "issuedDate": "2026-10-26",
  "address": "100 N EXAMPLE AVE",
  "zip": null,
  "latitude": 41.88,
  "longitude": -87.63,
  "valuation": 375000,
  "squareFeet": null,
  "housingUnits": null,
  "contractorName": "EXAMPLE CONSTRUCTION INC",
  "contractorTrade": "GENERAL CONTRACTOR",
  "contractorPhone": null,
  "contractorEmail": null,
  "contractorLicense": null,
  "contractors": [
    {
      "name": "EXAMPLE ELECTRIC INC",
      "role": "ELECTRICAL"
    },
    {
      "name": "EXAMPLE CONSTRUCTION INC",
      "role": "GENERAL CONTRACTOR"
    },
    {
      "name": "EXAMPLE PLUMBING INC",
      "role": "PLUMBER/PLUMBING"
    },
    {
      "name": "EXAMPLE MECHANICAL INC",
      "role": "REFRIGERATION"
    },
    {
      "name": "EXAMPLE MECHANICAL INC",
      "role": "VENTILATION"
    }
  ],
  "sourceUrl": null,
  "datasetUrl": "https://data.cityofchicago.org/d/ydr8-5enu",
  "scrapedAt": "2026-10-28T13:11:21.095Z"
}
```

A **RUN_SUMMARY** record in the key-value store shows, per city, how many rows were read, filtered out and saved, plus any source errors. If one city portal is down, the other cities still finish.

### Pricing

Pay per result: **$0.002 per permit** saved ($2 per 1,000). No start fee, and you are never charged for permits removed by your filters. Set a maximum spend per run in the Apify console and the Actor stops cleanly at your limit.

### Limits and tips

- Cities publish permits with a delay of 1 to 7 days, so "last 1 day" can be empty. Use 7 days or more for a reliable feed.
- Valuation is the value **declared by the applicant** and is sometimes $0 or $1 for small jobs.
- Dallas stores dates as text, so the Actor limits Dallas to the latest 92 days.
- The residential/commercial filter is a best-effort heuristic because each city classifies permits differently.
- Scope: permits for HVAC, mechanical or building-systems work on commercial or non-residential properties are not included. Residential permits of every trade are included.
- Want another city or county? Open an issue with its open-data link.

### Privacy and legal

Building permits are public records published by each city for reuse (for example, Austin's dataset is Public Domain). This Actor deliberately **does not output property-owner or applicant names**, only businesses acting as contractors, which is what the permit records them as. Use the data in line with the portal's terms and with anti-spam and telemarketing laws (CAN-SPAM, TCPA) when contacting businesses.

Not affiliated with any city government or with Socrata/Tyler Technologies.

# Actor input Schema

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

Which city open-data portals to search.

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

Look back this many days from today (or from 'Issued to'). Ignored when 'Issued from' is set. Most portals update daily, with a lag of 1 to 7 days.

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

Optional start date. Dallas is limited to the latest 92 days.

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

Optional end date. Defaults to today.

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

Keep permits whose work description contains any of these words, e.g. "solar", "pool", "roof", "new construction", "ADU". Case-insensitive.

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

Drop permits whose description contains any of these words.

## `permitClass` (type: `string`):

Best-effort filter based on each city's permit class and type fields.

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

Keep permits with a declared value at least this high. Permits with no value are dropped when this is set.

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

Drop permits with no contractor listed (useful for B2B supplier outreach).

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

Stop after this many permits per city (newest first). You pay only for saved permits.

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

Optional free Socrata app token for higher rate limits on very large pulls.

## Actor input object example

```json
{
  "cities": [
    "austin",
    "dallas"
  ],
  "daysBack": 7,
  "keywords": [],
  "excludeKeywords": [],
  "permitClass": "any",
  "minValuation": 0,
  "requireContractor": false,
  "maxPermitsPerCity": 15
}
```

# Actor output Schema

## `permits` (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": [
        "austin",
        "dallas"
    ],
    "daysBack": 7,
    "maxPermitsPerCity": 15
};

// Run the Actor and wait for it to finish
const run = await client.actor("great_stuff/building-permits-scraper").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": [
        "austin",
        "dallas",
    ],
    "daysBack": 7,
    "maxPermitsPerCity": 15,
}

# Run the Actor and wait for it to finish
run = client.actor("great_stuff/building-permits-scraper").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": [
    "austin",
    "dallas"
  ],
  "daysBack": 7,
  "maxPermitsPerCity": 15
}' |
apify call great_stuff/building-permits-scraper --silent --output-dataset

```

## MCP server setup

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

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/B3CE6iOEHhJrH7Cpf/builds/QRDhgXuG6NFuCDujg/openapi.json
