# Building Permits Scraper - Permit Data API & Contractor Leads (`highfiddle/building-permit-leads`) Actor

Building permit data from 31 US & Canada cities in one run: new construction leads with contractor name, phone and license, project value, trade and lead score. Only-new mode for daily schedules.

- **URL**: https://apify.com/highfiddle/building-permit-leads.md
- **Developed by:** [Vincent Vann](https://apify.com/highfiddle) (community)
- **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 lead delivereds

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: Permit Data API & Contractor Leads

**Building Permits Scraper** pulls **building permit data** and **new construction leads** from **31 official
city and county open-data portals** in the US and Canada in one run: New York City, Los Angeles, Chicago, Austin,
Dallas, San Antonio, Seattle, San Francisco, Philadelphia, Boston, Washington DC, Miami, Miami-Dade, Nashville,
Raleigh, Columbus, Cleveland, Detroit, Baltimore and more.

Every permit record comes with its **trade** (roofing, solar, HVAC, pool, ADU, new construction...), a
**HOT / WARM / COLD lead score**, the project value, and the **contractor's name, phone, email and license**
wherever the city publishes them. Use it as a permit records API or as a daily contractor leads list.

Built for roofers, solar installers, HVAC and pool companies, building-material suppliers, insurance and
home-services marketers who want a fresh list of real construction projects every morning.

### Why this building permit scraper

- **31 jurisdictions, 4 data platforms.** Socrata, ArcGIS, CKAN and CARTO portals, not just one. Includes cities
  most permit scrapers skip: Raleigh, Nashville, Columbus, Cleveland, Detroit, Baltimore, Washington DC, Miami,
  Miami-Dade, Philadelphia, Boston, San Antonio, Collin County TX, Calgary and Edmonton.
- **Many cities per run.** Pick cities, or whole states (e.g. `TX` adds Austin, Dallas, San Antonio and Collin County).
- **One clean schema.** Every city's columns are mapped to the same fields, dates are `YYYY-MM-DD`, values are
  numbers, phones are `(512) 555-0199`.
- **Trade categories on every permit**, from the work description first, so "Addition/Alteration" boilerplate
  doesn't hide what the job actually is.
- **Lead score** (0-100) and grade, with the reasons spelled out.
- **Only new permits** mode for daily schedules: you never pay twice for the same permit.
- **Contractor summary**: one row per contractor with permit count, total value, main trades and contact details.
- **Never fails because one city is down.** Other cities still deliver, and `RUN_SUMMARY` says what happened.
- **Honest coverage.** The table below shows exactly which fields each city publishes.

### What you get per permit record

| Field | Example |
| --- | --- |
| `lead_grade`, `lead_score`, `lead_reasons` | `HOT`, `74`, `["issued in the last 7 days", "contractor phone", ...]` |
| `issue_date`, `applied_date`, `days_since_issued` | `2026-11-03`, `2026-10-21`, `2` |
| `primary_trade`, `trades` | `roofing`, `["roofing", "remodel"]` |
| `permit_number`, `permit_type`, `work_class`, `description`, `status` | `BLDR-012345-2026`, `Building`, `Alterations/Repairs`, `Removing and replacing roof...`, `Issued` |
| `address`, `city`, `state`, `zip`, `latitude`, `longitude` | `123 Main St`, `Raleigh`, `NC`, `27609`, ... |
| `property_type` | `residential` / `commercial` |
| `project_value` | `18500` |
| `contractor_name`, `contractor_phone`, `contractor_email`, `contractor_license`, `contractor_address` | `Oak City Roofing LLC`, `(919) 555-0142`, ... |
| `owner_builder`, `owner_name`, `applicant_name` | `false`, ... |
| `description_phone` | A phone number written inside the permit description (common in Nashville) |
| `record_url`, `source_dataset`, `source_platform` | Link to the permit on the city's site when available |

#### Example output

One permit record (illustrative values, real field names):

```json
{
  "record_type": "permit",
  "record_id": "raleigh:BLDR-012345-2026",
  "city_key": "raleigh",
  "city": "Raleigh",
  "state": "NC",
  "country": "US",
  "permit_number": "BLDR-012345-2026",
  "permit_type": "Building",
  "work_class": "Alterations/Repairs",
  "description": "Remove and replace 24 sq of roofing with architectural shingles, ice and water shield at valleys",
  "status": "Issued",
  "primary_trade": "roofing",
  "trades": ["roofing", "remodel"],
  "property_type": "residential",
  "address": "1234 Example Oak Dr",
  "zip": "27609",
  "latitude": 35.8432,
  "longitude": -78.6382,
  "applied_date": "2026-10-28",
  "issue_date": "2026-11-03",
  "expiration_date": "2027-05-02",
  "days_since_issued": 2,
  "project_value": 18500,
  "contractor_name": "Oak City Roofing LLC",
  "contractor_phone": "(919) 555-0142",
  "contractor_email": "office@example.com",
  "contractor_license": "NC-12345",
  "contractor_city": "RALEIGH",
  "contractor_state": "NC",
  "owner_builder": false,
  "owner_name": "J DOE",
  "lead_score": 88,
  "lead_grade": "HOT",
  "lead_reasons": ["issued in the last 7 days", "contractor phone", "contractor email", "high-intent trade: roofing"],
  "source_platform": "arcgis",
  "source_dataset": "https://services.arcgis.com/v400IkDOw1ad7Yad/arcgis/rest/services/Building_Permits/FeatureServer/0",
  "scraped_at": "2026-11-05T15:40:00+00:00"
}
```

One contractor summary row (`outputMode: "contractors"`):

```json
{
  "record_type": "contractor",
  "city_key": "austin",
  "contractor_name": "Example Air & Heat LLC",
  "contractor_phone": "(512) 555-0188",
  "permit_count": 13,
  "total_project_value": 412000,
  "top_trades": ["hvac", "new_construction"],
  "latest_issue_date": "2026-11-04",
  "recent_permits": [{ "permit_number": "2026-123456 MP", "issue_date": "2026-11-04", "address": "500 Example St", "primary_trade": "hvac", "project_value": 9800 }]
}
```

Trades: `solar`, `roofing`, `pool`, `adu`, `new_construction`, `addition`, `demolition`, `hvac`, `electrical`,
`plumbing`, `fire_protection`, `windows_doors`, `siding`, `fence_deck`, `foundation`, `elevator`, `sign`, `remodel`
(and `other`).

### Building permit data coverage by city

✅ = filled on most permits · ◐ = filled on some permits · — = the city does not publish it.
Measured on a live 7-day sample (November 2026; Montgomery County from its published columns, as its feed lagged
that week). Contact fields appear only where the city itself publishes them.

| Key | Jurisdiction | Value | Contractor | Phone | Email | License | Owner |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `anaheim` | Anaheim, CA | ✅ | ✅ | ✅ | — | — | — |
| `austin` | Austin, TX | ◐ | ✅ | ✅ | — | — | — |
| `baltimore` | Baltimore, MD | ✅ | — | — | — | — | — |
| `baton-rouge` | East Baton Rouge Parish, LA | ◐ | ◐ | — | — | — | ✅ |
| `boston` | Boston, MA | ✅ | — | — | — | — | — |
| `calgary` | Calgary, AB | ✅ | ✅ | — | — | — | — |
| `cambridge-ma` | Cambridge, MA | ✅ | ✅ | — | — | — | — |
| `chicago` | Chicago, IL | ✅ | ✅ | — | — | — | ✅ |
| `cincinnati` | Cincinnati, OH | ✅ | ✅ | — | — | — | — |
| `cleveland` | Cleveland, OH | ✅ | ✅ | — | — | ✅ | — |
| `collin-county-tx` | Collin County, TX (Plano, Frisco, McKinney...) | ◐ | ✅ | — | — | — | ✅ |
| `columbus` | Columbus, OH | ◐ | ✅ | — | — | — | — |
| `dallas` | Dallas, TX | — | — | — | — | — | — |
| `detroit` | Detroit, MI | ✅ | — | — | — | — | — |
| `edmonton` | Edmonton, AB | ✅ | — | — | — | — | — |
| `los-angeles` | Los Angeles, CA | ✅ | — | — | — | — | — |
| `marin-county` | Marin County, CA | ✅ | ✅ | — | — | ◐ | — |
| `mesa` | Mesa, AZ | ✅ | ◐ | ◐ | ◐ | ◐ | — |
| `miami` | Miami (city), FL | ✅ | ✅ | — | — | — | — |
| `miami-dade-county` | Miami-Dade County, FL | ✅ | ✅ | ✅ | — | ✅ | ✅ |
| `montgomery-county-md` | Montgomery County, MD | ✅ | — | — | — | — | — |
| `nashville` | Nashville, TN | ✅ | ✅ | ◐ \* | — | — | — |
| `new-orleans` | New Orleans, LA | ✅ | ✅ | — | — | — | ✅ |
| `norfolk` | Norfolk, VA | ✅ | — | — | — | — | — |
| `nyc` | New York City, NY | ✅ | ✅ | — | — | ✅ | ✅ |
| `philadelphia` | Philadelphia, PA | — | ✅ | — | — | — | ✅ |
| `raleigh` | Raleigh, NC | ✅ | ✅ | ✅ | ✅ | — | ✅ |
| `san-antonio` | San Antonio, TX | ◐ | — | — | — | — | — |
| `san-francisco` | San Francisco, CA | ✅ | — | — | — | — | — |
| `seattle` | Seattle, WA | ✅ | — | — | — | — | — |
| `washington-dc` | Washington, DC | — | — | — | — | — | ✅ |

\* Nashville phones come from `description_phone` (a number written in the permit text).

Want another city? Open an issue with a link to its open-data permit dataset.

### Examples: new construction and contractor leads

**Roofing and solar leads in Texas from the last 7 days, contractor known:**

```json
{
  "states": ["TX"],
  "lastNDays": 7,
  "trades": ["roofing", "solar"],
  "requireContact": "contractor"
}
```

**Daily schedule: only new HOT/WARM permits over $50k in Raleigh and Nashville:**

```json
{
  "cities": ["raleigh", "nashville"],
  "lastNDays": 7,
  "minProjectValue": 50000,
  "minLeadGrade": "WARM",
  "onlyNew": true,
  "stateKey": "nc-tn-daily"
}
```

**Most active pool and ADU contractors in Los Angeles, Anaheim and Marin over 90 days:**

```json
{
  "cities": ["los-angeles", "anaheim", "marin-county"],
  "lastNDays": 90,
  "trades": ["pool", "adu"],
  "outputMode": "contractors",
  "maxResultsPerCity": 5000
}
```

### Good to know

- **Freshness:** cities publish with a 1-7 day lag (NYC and Montgomery County tend to be the slowest). For daily
  schedules use `lastNDays: 7` with `onlyNew: true`: you get each permit once, as soon as it appears.
- **Newest first:** results are newest-first per city, and `maxResultsPerCity` stops at that many matches.
- **Filters run after download** for most fields, so very narrow filters over long date ranges scan more rows. The
  run summary shows rows scanned per city.
- **Owner-builder permits** are flagged with `owner_builder: true` and can be excluded.
- **Run summary:** every run writes `RUN_SUMMARY` to the key-value store with matched/scanned counts and any source
  errors per city.
- **Health check:** run with `healthCheck: true` to see how recent each source's newest permit is.

### Data and responsible use

All data comes from official government open-data portals (public records). A permit shows that work was
approved, not that it has started or that a contractor is still needed. Names and phone numbers are as published
by the city and are not verified. If you contact people from this data, you are responsible for following
telemarketing, email and privacy rules that apply to you (for example TCPA, CAN-SPAM, do-not-call lists and
state privacy laws).

# Actor input Schema

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

Pick one or more jurisdictions. Leave empty (and leave States empty) to search all of them.

## `states` (type: `array`):

Adds every supported jurisdiction in these states, e.g. TX adds Austin, Dallas, San Antonio and Collin County.

## `lastNDays` (type: `integer`):

Ignored when 'Issued from' is set. Cities publish with a 1-7 day lag, so 7+ days is safest for daily schedules.

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

First issue date to include (YYYY-MM-DD). Overrides 'last N days'.

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

Last issue date to include (YYYY-MM-DD). Defaults to today.

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

Keep permits whose work matches any of these trades. Leave empty for all permits.

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

Keep permits whose type, description or contractor contains any of these words (case-insensitive).

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

Keep permits in these ZIP codes. A prefix works too: '787' matches all of 787xx.

## `minProjectValue` (type: `integer`):

Drops permits below this value, and permits from cities that do not publish a value.

## `propertyType` (type: `string`):

Keep only residential or only commercial permits. Permits whose city does not say are dropped by these two options.

## `requireContact` (type: `string`):

Only keep permits that name a contractor, or that have a phone number.

## `excludeOwnerBuilder` (type: `boolean`):

Skip permits where the owner is doing the work themselves.

## `excludeClosed` (type: `boolean`):

Skip permits whose status says the work is already finished, closed, expired or cancelled.

## `minLeadGrade` (type: `string`):

HOT = score 60+, WARM = 35+. The score weighs freshness, project value, contractor phone/email and permit status.

## `outputMode` (type: `string`):

Permits: one row per permit. Contractors: one row per contractor with permit count, value and trades. Both: permits plus contractor rows.

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

Remembers delivered permits between runs, so scheduled runs only return (and charge for) new ones.

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

Use a different name per schedule, so each schedule remembers its own permits.

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

Newest permits first. Also caps the permits used for the contractor summary.

## `maxResults` (type: `integer`):

0 = no overall limit (the per-city limit still applies).

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

Adds the original row from the city's portal under 'raw'.

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

Your own free Socrata app token for higher rate limits. Not needed for normal use.

## `healthCheck` (type: `boolean`):

Checks that each selected source answers and how recent its newest permit is. Writes the HEALTH record; returns no permits.

## Actor input object example

```json
{
  "cities": [
    "austin",
    "chicago"
  ],
  "lastNDays": 30,
  "propertyType": "any",
  "requireContact": "none",
  "excludeOwnerBuilder": false,
  "excludeClosed": false,
  "minLeadGrade": "COLD",
  "outputMode": "permits",
  "onlyNew": false,
  "stateKey": "default",
  "maxResultsPerCity": 500,
  "maxResults": 0,
  "includeRaw": false,
  "healthCheck": false
}
```

# Actor output Schema

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

One row per permit: trade, lead score, address, value and contractor details where published.

## `contractors` (type: `string`):

One row per contractor per city (filled when Output is 'Contractor summary' or 'Both').

## `allFields` (type: `string`):

Every field of every row in the dataset.

## `runSummary` (type: `string`):

Matched, scanned and skipped counts per city, plus any source that was unavailable.

## `health` (type: `string`):

Written only by health-check runs: newest permit date and status per source.

# 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",
        "chicago"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("highfiddle/building-permit-leads").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",
        "chicago",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("highfiddle/building-permit-leads").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",
    "chicago"
  ]
}' |
apify call highfiddle/building-permit-leads --silent --output-dataset

```

## MCP server setup

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

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/ttW04lxea3DxDwGUh/builds/fVGXB3TYu4RLqctVd/openapi.json
