# US Building Permits Scraper - Construction Leads (`batreek/us-building-permits`) Actor

Construction permits from 12 US cities (Chicago, NYC, LA, San Francisco, Seattle, Austin and more) in one unified format. Find new projects and contractor leads: filter by date, keyword, valuation and permit type. Official open data, no login, no personal data.

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

## Pricing

from $1.50 / 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

## US Building Permits Scraper — Construction Leads from 12 Cities, One Clean Format

Pull **fresh building permits** from 12 US cities and counties in a single run and get them back in **one unified schema**: permit number, issue date, permit type, work description, project valuation, address, ZIP, status, coordinates and (where the city publishes it) the contractor's business name.

All data comes from **official city open-data portals** (Socrata SODA APIs). No login, no proxies, no browser — so runs are fast, cheap and reliable.

### Why use this building permit scraper?

- 🏗️ **Construction & home-services lead generation** — find new roofing, solar, pool, HVAC, ADU, remodel and demolition projects the week they are permitted.
- 📈 **Market research** — track construction activity and project value by city, ZIP or permit type.
- 🏘️ **Real estate & investors** — spot neighborhoods with rising renovation and new-construction activity.
- 🧱 **Suppliers & distributors** — target contractors that just pulled permits.
- 🤖 **AI agents & automations** — a clean, consistent JSON feed you can schedule daily and push to Sheets, CRMs, Slack or n8n/Make/Zapier.

### Construction leads and contractor leads

Use the permit feed as a **construction leads** list. New roofing, solar, pool, HVAC, ADU, remodel and demolition **construction permits** show up here soon after the city issues them, usually within days. Where a city publishes the contractor's company name (New York City, Seattle, Austin and Cincinnati), each record includes it in `contractorBusiness`, so suppliers and subcontractors also get **contractor leads**: companies that just pulled a permit. The other cities publish no contractor field, so those records give you the project and address but no contractor. No personal names, phone numbers or emails are ever output.

### Supported cities

| City | Source |
|---|---|
| Chicago, IL | data.cityofchicago.org |
| New York City, NY (DOB NOW: Build approved permits) | data.cityofnewyork.us |
| Los Angeles, CA (LADBS, 2020–present) | data.lacity.org |
| San Francisco, CA | data.sf.gov |
| Seattle, WA | data.seattle.gov |
| Austin, TX | data.austintexas.gov |
| New Orleans, LA | data.nola.gov |
| Cambridge, MA (new construction) | data.cambridgema.gov |
| Montgomery County, MD (residential) | data.montgomerycountymd.gov |
| Mesa, AZ | data.mesaaz.gov |
| Cincinnati, OH | data.cincinnati-oh.gov |
| Baton Rouge, LA (East Baton Rouge Parish) | data.brla.gov |

Need another city? Open an issue on the Actor page and we will look at adding it.

### Input

| Field | What it does |
|---|---|
| `cities` | Cities to include. Empty = all. |
| `issuedAfter` / `daysBack` | Date window (default: last 30 days). |
| `keywords` | Keep permits whose type/description mentions any keyword, e.g. `solar`, `roof`, `pool`, `ADU`. |
| `minValuation` | Minimum reported project value in USD. |
| `maxResultsPerCity` | Cap per city, newest first. |
| `includeRaw` | Also return the original record (personal fields removed). |

Example — solar and roofing jobs over $10k in the last 90 days:

```json
{
  "cities": ["austin", "seattle", "los-angeles", "san-francisco"],
  "daysBack": 90,
  "keywords": ["solar", "roof"],
  "minValuation": 10000,
  "maxResultsPerCity": 500
}
```

### Output

```json
{
  "city": "Austin, TX",
  "cityKey": "austin",
  "state": "TX",
  "permitNumber": "2026-128634 PP",
  "issuedDate": "2026-10-05",
  "permitType": "Plumbing Permit",
  "workClass": "Irrigation",
  "description": "Install Sprinkler System",
  "valuationUsd": 4500,
  "address": "9309 BREEZE HILL DR",
  "zip": "78748",
  "status": "Active",
  "latitude": 30.17,
  "longitude": -97.82,
  "contractorBusiness": "Example Irrigation LLC",
  "sourceUrl": "https://abc.austintexas.gov/...",
  "sourceDataset": "https://data.austintexas.gov/resource/3syk-w9eu",
  "scrapedAt": "2026-10-17T20:00:00+00:00"
}
```

Download as JSON, CSV, Excel or via the Apify API. A `SUMMARY` record in the key-value store shows counts per city.

### Pricing

Pay only for results: **$1.50 per 1,000 permits** (pay-per-event, charged per permit saved). No monthly rental.

#### What will it cost?

| Permits | Cost |
|---|---|
| 150 permits (the pre-filled form: 3 cities × 50) | $0.23 |
| 1,000 permits | $1.50 |
| 10,000 permits | $15.00 |
| 100,000 permits | $150.00 |

Use `maxResultsPerCity` and the number of cities to control how many permits you get. Each run also has a $0.00005 start fee (one per GB of memory; the default memory needs one), which is too small to change these numbers. You pay only for these events: no rental fee and no separate compute charge. Set a **maximum cost per run** in the run options (or `maxTotalChargeUsd` in the API) and the Actor stops cleanly when it reaches it.

### Use it via API, code or AI agents

Everything in the Console also works over the API, so you can run it from code, a scheduler or an AI agent. Replace `<YOUR_APIFY_TOKEN>` with your token from **Apify Console → Settings → API & Integrations**.

**REST: run and get the results in one call** (waits up to 5 minutes; `maxTotalChargeUsd` caps what the run can cost):

```bash
curl -X POST "https://api.apify.com/v2/acts/batreek~us-building-permits/run-sync-get-dataset-items?maxTotalChargeUsd=1" \
  -H "Authorization: Bearer <YOUR_APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"cities": ["austin", "seattle"], "daysBack": 7, "keywords": ["solar", "roof"], "maxResultsPerCity": 50}'
```

**Python** (`pip install apify-client`):

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("batreek/us-building-permits").call(run_input={
    "cities": [
        "austin",
        "seattle"
    ],
    "daysBack": 7,
    "keywords": [
        "solar",
        "roof"
    ],
    "maxResultsPerCity": 50
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)
```

**JavaScript / Node.js** (`npm install apify-client`):

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('batreek/us-building-permits').call({"cities": ["austin", "seattle"], "daysBack": 7, "keywords": ["solar", "roof"], "maxResultsPerCity": 50});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

**AI agents (MCP):** add this Actor as a tool in Claude, ChatGPT, Cursor or any MCP client through Apify's hosted MCP server at `https://mcp.apify.com?tools=batreek/us-building-permits`. The agent can then pull fresh construction permits for a city or trade and read the structured results. Runs are billed to your Apify account like any other run.

**No-code:** use Apify's integrations (Zapier, Make, n8n, Google Sheets, Slack, webhooks) or put it on an Apify **Schedule**.

### Tips

- Schedule it **daily with `daysBack: 2`** to get a rolling feed of new permits without duplicates piling up.
- Combine `keywords` + `minValuation` to get high-value leads only.
- Valuation is what the applicant reported to the city; some permit types (e.g. electrical, signage) often report $0 or nothing.

### Data, privacy & legality

This Actor only reads **public open-data APIs that cities publish for reuse**. It deliberately **does not output personal data**: owner, applicant and contact names, phone numbers and emails are dropped, even in `raw` mode. Only business names explicitly published as contractor/company fields are kept. Check each city's open-data terms for your use case.

### FAQ

**How fresh is the data?** As fresh as each city's portal — most update daily.
**Why are some fields empty?** Not every city publishes every field (e.g. Chicago does not publish ZIP codes in this dataset).
**Can I get historical permits?** Yes, set `issuedAfter` to any date; history depth depends on the city.

# Actor input Schema

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

Which cities to pull permits from. Leave empty for all supported cities.

## `issuedAfter` (type: `string`):

Only permits issued on or after this date (YYYY-MM-DD). Leave empty to use 'Days back'.

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

If 'Issued on or after' is empty, fetch permits issued in the last N days.

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

Keep only permits whose type or description contains at least one of these words, e.g. solar, roof, pool, ADU, demolition, HVAC. Case-insensitive.

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

Keep only permits with a reported project value at or above this amount. Permits with no reported value are dropped when this is set.

## `maxResultsPerCity` (type: `integer`):

Upper limit of permits to return per city (newest first). You pay per permit saved, so this is your main cost control: 3 cities × 50 = 150 permits ≈ $0.23.

## `includeRaw` (type: `boolean`):

Attach the original open-data record (with personal-name and contact fields removed) under `raw`.

## Actor input object example

```json
{
  "cities": [
    "chicago",
    "austin",
    "seattle"
  ],
  "daysBack": 30,
  "maxResultsPerCity": 50,
  "includeRaw": false
}
```

# Actor output Schema

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

Building permits in a unified schema (one item per permit).

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

Permit counts per city and the date window used.

# 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"
    ],
    "maxResultsPerCity": 50
};

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

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

```

## MCP server setup

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