# Building Permits Scraper — Construction Leads (US & Canada) (`opendatafrontierlab/us-building-permits`) Actor

Get new building permits from 12 official city open-data portals (Chicago, NYC, LA, SF, Seattle, Austin, more) in one schema. Filter by trade keyword (solar, roofing, HVAC, ADU), date and project value. Daily 'only new' lead feeds. No login, no personal data. $3 per 1,000 permits.

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

## Pricing

from $3.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: Construction & Contractor Leads (US & Canada)

**Get newly issued building permits from 12 official city and county open-data portals in one clean, normalized dataset.** Filter by trade keyword (solar, roofing, HVAC, pool, ADU, demolition), date and project value. Schedule it daily with **"Only new since last run"** and get a fresh construction-lead feed in Google Sheets, your CRM, Slack or an AI agent.

**Price:** $3 per 1,000 permits ($0.003 per permit). There is no monthly rental and you pay only for the permits returned.

**Covered jurisdictions:** Chicago IL · New York City NY (DOB NOW) · Los Angeles CA · San Francisco CA · Seattle WA · Austin TX · Cincinnati OH · Mesa AZ · Marin County CA · Montgomery County MD (commercial) · Calgary AB · Edmonton AB.

**Tutorials & examples:** [How to get daily building permit leads for your city](https://github.com/opendatafrontierlab/awesome-public-data-actors/blob/main/tutorials/building-permit-leads.md), a step-by-step guide with a runnable Python example, on GitHub.

### What you get

- **Official sources only.** Every record comes from the city's or county's documented open-data API (Socrata SODA) and carries the dataset URL and license. There is no HTML scraping, no login and no proxy, so it doesn't break when a website is redesigned.
- **One schema for every city.** Permit number, permit type, work class, description, status, applied and issued dates, address, ZIP, project value (USD and local currency), fee, contractor business name, latitude/longitude and a link back to the source record.
- **Privacy-safe by design.** Owner names, applicant names, phone numbers and email addresses are **never collected**. Contractor *business* names are included only where the city publishes them, and you can switch them off.
- **Built for recurring lead feeds.** Use date windows, keyword filters, a minimum project value and de-duplication across runs (`onlyNewSinceLastRun`).
- **Polite and reliable.** It respects the portals' crawl delay and retries with backoff.

### Who uses it

| You are… | Use it to… |
|---|---|
| Roofing, solar, HVAC, electrical, plumbing or pool contractor | Find homeowners and GCs who just pulled a permit in your service area (`keyword: "roof"`, `"solar"`, `"pool"`) |
| Subcontractor or building-material/equipment supplier | Spot active job sites by area and project value (`minValuation: 250000`) |
| Real-estate investor, proptech or analyst | Track renovation activity, new construction and ADU trends by neighborhood |
| Insurance, home services or market research | Measure permit velocity and seasonality across cities |
| Developer or AI-agent builder | Give your app or agent a structured, citable feed of construction activity |

### How to use it

1. Click **Try for free**, choose jurisdictions (or `all`), and set `daysBack` and an optional `keyword`.
2. Click **Start**. A 7-day pull for 3 cities usually finishes in under 30 seconds.
3. Download the results as **Excel, CSV, JSON or XML**, or connect them to your tools (see Integrations).
4. For a daily feed, create a **Task** with `onlyNewSinceLastRun: true` and add a **Schedule** (e.g. every morning at 7:00).

### Input example

```json
{ "jurisdictions": ["chicago-il", "austin-tx", "seattle-wa"], "daysBack": 7, "keyword": "solar", "maxItems": 500, "onlyNewSinceLastRun": true }
```

| Field | What it does |
|---|---|
| `jurisdictions` | One or more source keys (e.g. `chicago-il`, `new-york-ny`), or `all` |
| `daysBack` / `issuedSince` / `issuedBefore` | Issue-date window (`issuedSince` overrides `daysBack`) |
| `keyword` | Full-text filter applied by the portal, e.g. `roof`, `solar`, `ADU`, `demolition`, `elevator` |
| `minValuation` | Minimum declared project value in local currency |
| `maxItemsPerJurisdiction`, `maxItems` | Spending caps; you pay only for the permits returned |
| `onlyNewSinceLastRun` | Skips permits already returned by earlier runs |
| `includeContractorBusinessName` | Includes the contractor company name (default on) |

### Output example (real record)

```json
{
  "id": "chicago-il:101083682",
  "jurisdictionKey": "chicago-il",
  "jurisdiction": "Chicago, IL",
  "permitNumber": "101083682",
  "permitType": "PERMIT - RENOVATION/ALTERATION",
  "workType": "SELF CERT",
  "description": "NEW KITCHEN EQUIPMENT AND SINKS INSTALLED WITHIN EXISTING BANQUET HALL KITCHEN.",
  "status": "ACTIVE",
  "appliedDate": "2026-05-19",
  "issuedDate": "2026-10-28",
  "address": "2620 W 51ST ST",
  "city": "Chicago",
  "state": "IL",
  "zip": null,
  "valuationUsd": 15000.0,
  "valuationLocal": 15000.0,
  "currency": "USD",
  "feeUsd": 2177.0,
  "contractorBusinessName": "I REMODEL YOUR HOME",
  "latitude": 41.80116489821182,
  "longitude": -87.69009853263145,
  "url": null,
  "source": {
    "portal": "data.cityofchicago.org",
    "datasetId": "ydr8-5enu",
    "datasetUrl": "https://data.cityofchicago.org/d/ydr8-5enu",
    "license": "City of Chicago Data Portal Terms of Use"
  },
  "scrapedAt": "2026-10-29T18:29:56+00:00"
}
```

### Pricing and cost examples

Pay per event: **$0.003 per permit returned** plus a tiny Actor-start fee ($0.00005). Platform compute is included in the price.

| Scenario | Permits | Cost |
|---|---|---|
| One city, last 7 days, keyword `solar` | ~50 | ~$0.15 |
| 3 cities, daily "only new" feed | ~100–500 per day | ~$0.30–$1.50 per day |
| All 12 jurisdictions, last 30 days, default `maxItems` cap | 2,000 | $6 |

Set `maxItems` to cap spending on any run. Apify's free plan includes monthly credit, so you can test without paying.

### Integrations

- **API and SDKs:** run it from any language with the [Apify API](https://docs.apify.com/api/v2) or the official JavaScript/Python clients (`apify-client`). The run endpoint `run-sync-get-dataset-items` returns permits in a single HTTP call.
- **Zapier, Make, n8n:** use the Apify app/node, e.g. "new permits → Google Sheets row → Slack message → CRM lead".
- **AI agents (MCP):** add the Actor to Claude, Cursor or any MCP client through the [Apify MCP server](https://mcp.apify.com) (`opendatafrontierlab/us-building-permits`), or use the Apify integrations for LangChain, LlamaIndex and CrewAI.
- **Webhooks:** get notified or trigger your pipeline when each run finishes.

Python example:

```python
from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("opendatafrontierlab/us-building-permits").call(run_input={"jurisdictions": ["austin-tx", "los-angeles-ca"], "daysBack": 7, "keyword": "solar", "maxItems": 50})
for permit in client.dataset(run.default_dataset_id).iterate_items():
    print(permit["issuedDate"], permit["address"], permit["valuationUsd"], permit["contractorBusinessName"])
```

### FAQ

**Is this legal to use?** Yes. All data comes from official government open-data portals published for public reuse. Each record links to its dataset and license (`source.license`). No personal contact data is collected.

**How fresh is the data?** It is as fresh as each portal. Most update daily; Los Angeles typically lags 2–5 days. Run it daily with `onlyNewSinceLastRun` to catch every new permit once.

**Can I get the homeowner's name, phone or email?** No. This Actor deliberately does not collect personal data. Use the address and contractor business name for your outreach and follow local marketing laws.

**Why is the project value sometimes `1` or empty?** Some cities record placeholder values (e.g. Austin and San Francisco over-the-counter permits). Values are passed through exactly as published. Use `minValuation` to filter.

**Do you cover my city?** The 12 jurisdictions above are supported today. Open an issue on the Actor's Issues tab: any city with a Socrata or ArcGIS open-data permits dataset can usually be added quickly.

**Will I get the same permit twice?** No, not in one run. San Francisco publishes one row per address, so only the permit's primary-address row is returned. New York City (DOB NOW) returns one record per work permit and work type, and Cincinnati one per permit and permit type, because those portals publish each as its own line. With `onlyNewSinceLastRun`, permits you already received are skipped.

**Are Canadian values in USD?** Calgary and Edmonton values are in CAD. Check the `currency` field.

### Related Actors

- [US Public Bids, RFPs & Grants](https://apify.com/opendatafrontierlab/us-public-bids): open government solicitations for contractors.
- [PDF to Markdown & RAG Chunks](https://apify.com/opendatafrontierlab/pdf-to-markdown-rag): turn plan sets, specs and RFP PDFs into searchable Markdown.

*This Actor is not affiliated with any government agency.*

# Changelog

This Actor's version history is a separate document: https://apify.com/opendatafrontierlab/us-building-permits/changelog.md

# Actor input Schema

## `jurisdictions` (type: `array`):

Cities/counties to pull permits from. Use source keys like 'chicago-il', 'new-york-ny', 'los-angeles-ca', or 'all' for all 12 supported jurisdictions.

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

Return permits issued in the last N days (e.g. 1 for a daily feed, 30 for a monthly pull). Ignored if 'Issued since' is set.

## `issuedSince` (type: `string`):

Optional absolute start date, inclusive, format YYYY-MM-DD (e.g. 2026-10-01). Overrides 'Days back'.

## `issuedBefore` (type: `string`):

Optional end date, exclusive, format YYYY-MM-DD.

## `keyword` (type: `string`):

Optional full-text filter matched against permit text by each portal, e.g. 'solar', 'roof', 'HVAC', 'pool', 'ADU', 'demolition', 'elevator'. One word or phrase.

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

Only return permits whose declared project value is at least this amount (local currency: USD, or CAD for Calgary/Edmonton). Permits without a value are dropped when set.

## `maxItemsPerJurisdiction` (type: `integer`):

Maximum permits per city/county, newest first. Use it to cap cost.

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

Maximum permits across all jurisdictions. You are charged $0.003 per permit returned, so this is your spending cap.

## `onlyNewSinceLastRun` (type: `boolean`):

Skip permits already returned by previous runs (state kept in a named key-value store). Ideal for scheduled daily lead feeds.

## `includeContractorBusinessName` (type: `boolean`):

Licensed contractor company name where the city publishes it. Owner/applicant names, phones and emails are never collected.

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

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

## Actor input object example

```json
{
  "jurisdictions": [
    "chicago-il",
    "seattle-wa",
    "austin-tx"
  ],
  "daysBack": 7,
  "maxItemsPerJurisdiction": 500,
  "maxItems": 2000,
  "onlyNewSinceLastRun": false,
  "includeContractorBusinessName": true
}
```

# Actor output Schema

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

Normalized building permit records, one item per permit.

# 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 = {
    "jurisdictions": [
        "chicago-il",
        "seattle-wa",
        "austin-tx"
    ]
};

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

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

```

## MCP server setup

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