# US Building Permits - Contractor Leads with Delta Mode (`wellbuilt_zythem/us-building-permits`) Actor

Building permits from 9 US cities via official open-data APIs: contractor name, licence and phone, job value, GPS, trade tagged (roofing, solar, HVAC, electrical, plumbing) and a lead score. Delta mode delivers and charges only the permits that are new since your last run.

- **URL**: https://apify.com/wellbuilt\_zythem/us-building-permits.md
- **Developed by:** [Carlos Escanez](https://apify.com/wellbuilt_zythem) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 us building permits - contractor leads with delta modes

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## US Building Permits — contractor leads from 9 cities, only the new ones

Every building permit a city issues is a job that just got funded, with the contractor doing it printed on the record. This actor reads those records straight from the official open-data APIs of nine US cities, normalises them into one schema, tags the trade (roofing, solar, HVAC, electrical, plumbing, remodel...), scores the lead, and, in delta mode, delivers **only the permits you have not received before**, so a daily schedule becomes a feed of fresh leads you pay for once.

No website scraping, no proxies, no login: the data comes from Socrata APIs the cities maintain themselves, so it does not break when a portal changes its layout.

### See it run

Default input, no changes: permits issued in the last 7 days across all nine cities that have a contractor on file. About 5,000 rows.

Delta feed for a roofing sales team in Texas and Florida:

```json
{
  "cities": ["austin", "orlando"],
  "trades": ["roofing", "solar"],
  "onlyNew": true,
  "stateStoreName": "roofing-tx-fl"
}
```

Run it every morning. The first run returns the last 7 days; every run after that returns only permits issued since the previous run, and charges only for those.

### Two modes

**Window mode** (default): every permit issued in a date range, `lastNDays` or `dateFrom`/`dateTo`. Newest first. Use it for a one-off pull or a market study.

**Delta mode** (`onlyNew: true`): the actor keeps, per city, the highest issue date delivered and the ids delivered in the three days before it, in a key-value store on your account named `stateStoreName`. Next run it asks the city only for permits from that date on, drops the ones already delivered, and advances the mark. Cities publish with a lag of one to three days, which is why the three-day grace window exists. Different store names give you independent feeds (one per client, one per trade). `resetState: true` starts a feed over.

One thing to know about delta mode: a permit that your filters skip is not remembered, so widening `trades`, `minValuation` or `keyword` later will not bring back permits older than the feed's current position. Keep the filters stable while a feed runs, and use a new store name for a new set of filters.

### What every row carries, normalised across cities

- **Permit**: `permitNumber`, `permitType`, `workClass`, `description`, `status`, `appliedDate`, `issuedDate` (YYYY-MM-DD), `valuation` (USD, absent when the city has none), `squareFootage`.
- **Site**: `address`, `city`, `state`, `zip`, `latitude`, `longitude`.
- **Contractor**: `contractorName`, `contractorLicense`, `contractorPhone` (E.164, `+1...`), `contractorPhoneRaw`, `contractorAddress`, `contractorTrade` (what the city calls it).
- **Trade**: `trade`, one of `roofing`, `solar`, `hvac`, `electrical`, `plumbing`, `remodel`, `new_construction`, `demolition`, `pool`, `fence`, `other`. Classified from permit type, work class and description; measured 62–99 % of rows land in a named trade depending on the city, the rest are `other`.
- **Lead score** `leadScore` 0–100: contractor on file +30, phone +15, licence +10, job value over 10k +10 / over 50k +20, issued in the last 7 days +15.
- **Flags to filter on**: `hasContractor`, `hasPhone`, `hasLicense`.
- **Provenance**: `source` (the agency), `sourceCode`, `sourceUrl` (the permit's own page where the city offers one, otherwise the dataset), `scrapedAt`.

No owner names. Owners are private individuals; contractors are licensed businesses whose details the city publishes on purpose.

Example row (Austin):

```json
{
  "id": "AUSTIN-2026-115314PP",
  "city": "Austin", "state": "TX",
  "permitNumber": "2026-115314 PP",
  "permitType": "Plumbing Permit", "workClass": "Repair", "trade": "plumbing",
  "description": "Replacing angle stop supply water valve with new compression valves x6",
  "status": "Active", "appliedDate": "2026-09-02", "issuedDate": "2026-09-12",
  "address": "2704 Acopio Bnd", "zip": "78745",
  "latitude": 30.19335061, "longitude": -97.82913434,
  "contractorName": "Lugo's Plumbing Service",
  "contractorPhone": "+15125074677",
  "contractorAddress": "Elgin 78621",
  "contractorTrade": "Plumbing Contractor",
  "hasContractor": true, "hasPhone": true, "hasLicense": false,
  "leadScore": 80,
  "sourceUrl": "https://abc.austintexas.gov/web/permit/public-search-other?t_detail=1&t_selected_folderrsn=13766025"
}
```

### Who runs this on a loop

- **Roofing, solar and HVAC companies** calling the contractor who just pulled a permit next door, or the neighbourhood around a fresh re-roof.
- **Suppliers and distributors** (materials, equipment, financing, insurance) who sell to contractors and want to know who is active this week and where.
- **Lead-gen agencies** reselling fresh permit feeds by metro and trade.
- **PropTech and analytics teams** tracking construction activity by zip code.
- **AI agents**: the input is flat and the output is one row per permit, so it works as a tool without glue code.

### Coverage, said plainly

Measured on 13 September 2026 over the previous 30 days. "Contractor" means a contractor name is on the record.

| City | Permits / month | Contractor | Phone | Licence | Job value | GPS |
|---|---:|---:|---:|---:|---:|---:|
| New York City (DOB NOW) | 13,985 | 100 % | 0 % | 100 % | 87 % | 100 % |
| Austin, TX | 4,967 | 91 % | 89 % | 0 % | 7 % | 57 % |
| Chicago, IL | 2,866 | 97 % | 0 % | 0 % | 87 % | 99 % |
| Orlando, FL | 2,343 | 95 % | 58 % | 0 % | 91 % | 0 % |
| Seattle, WA | 1,907 | 52 % | 0 % | 0 % | 98 % | 100 % |
| New Orleans, LA | 1,566 | 80 % | 0 % | 77 % | 60 % | 0 % |
| Cincinnati, OH | 790 | 98 % | 0 % | 0 % | 93 % | 98 % |
| Baton Rouge, LA | 592 | 100 % | 0 % | 0 % | 70 % | 100 % |
| Marin County, CA | 362 | 98 % | 0 % | 9 % | 99 % | 99 % |
| **Total** | **29,378** | | | | | |

What that table does not hide:

- Only Austin and Orlando publish the contractor's phone. Everywhere else you get the business name (and in New York and New Orleans the licence number), which is enough to look the company up.
- Seattle's building permits rarely name the contractor; its trade permits (electrical, plumbing, mechanical) do, and both are included. `onlyWithContractor` (on by default) drops the rest at no cost.
- Chicago records list up to 15 contacts per permit; the actor picks the first one typed as a contractor and ignores owners, architects and "owner acting as general contractor".
- Orlando and New Orleans do not geocode their records; Chicago does not publish the site zip.
- Trade classification is keyword based. Fire sprinklers, signs, elevators and scaffolding land in `other`.

### Reliability, said plainly

- Sources are government APIs with stable schemas. The actor is tested against all nine live before every release; a city that renames a column fails the test, not your run.
- A city that is down is reported in `failedCities` in the run summary and skipped; the other cities still deliver. In delta mode nothing is delivered from a city that failed mid-read, so its watermark never advances on a partial read.
- Socrata's public rate limit is shared. For very large pulls (tens of thousands of rows) pass a free `socrataAppToken` and you get your own quota.
- The run summary (`RUN_SUMMARY` in the key-value store) lists rows read and delivered per city and the delta watermark, so you can audit every run.

### Pricing

Pay per permit delivered. Filtered-out rows cost nothing, and in delta mode rows you already received cost nothing. A daily roofing-and-solar feed for Austin and Orlando is a few dozen rows a day.

### Data source

Official open-data portals of the City of Orlando, City of Austin, City of New Orleans, City of Seattle, City of Cincinnati, City of Baton Rouge / East Baton Rouge Parish, County of Marin, NYC Department of Buildings (DOB NOW) and City of Chicago, all published under open-data terms. Building permits are public records in every one of these jurisdictions.

# Actor input Schema

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

Leave empty for all nine. Each city comes from its own official open-data API, so it never breaks because a website changed its layout.

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

Keep only permits for these trades, classified from the permit type and description. Leave empty for all.

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

Permits issued in this many days before today. Ignored when "Issued from" is set, and in delta mode once a first run has stored its watermark.

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

Optional exact start date, overrides "last N days".

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

Optional exact end date, inclusive.

## `onlyWithContractor` (type: `boolean`):

Drop permits pulled by the owner or with no contractor on file. Filtered-out rows cost nothing.

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

Keep permits with a declared job value at or above this. 0 keeps everything, including permits with no value declared.

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

Case-insensitive match on the permit description, type or work class, e.g. "re-roof" or "generator".

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

Remembers what each run delivered (per city, in a key-value store on your account) and next time returns only new permits. Schedule the actor daily and you get a clean feed of fresh leads, paying only for the new ones. Keep the filters below stable while a feed runs: a permit skipped by a filter is not remembered, so widening the filters later will not bring back permits older than the feed's current position. To change filters, use a new store name or tick "Reset delta state".

## `stateStoreName` (type: `string`):

Name of the key-value store that keeps the delta watermark. Use different names to run independent feeds (one per client, one per trade).

## `resetState` (type: `boolean`):

Forget what was delivered before and start the feed again from "last N days".

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

Stop after this many permits across all cities. 0 = no limit.

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

Free token from any Socrata portal. Without it the run shares the public rate limit; with it you get your own. Only needed for very large pulls.

## Actor input object example

```json
{
  "cities": [],
  "trades": [],
  "lastNDays": 7,
  "onlyWithContractor": true,
  "minValuation": 0,
  "onlyNew": false,
  "stateStoreName": "us-permits-seen",
  "resetState": false,
  "maxResults": 0
}
```

# 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 = {
    "cities": [],
    "trades": []
};

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

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

```

## MCP server setup

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