# Building Permits Scraper — Construction Leads, US & Canada (`northbell/building-permits-scraper`) Actor

Building permits from city open data in Chicago, New York, Los Angeles, San Diego County, Seattle, Austin, Calgary and more: work, address, value, contractor company, dates. Keyword, value and new-since-last-run filters. No login.

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

## Pricing

from $1.20 / 1,000 permits

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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 Leads, US & Canada

Get **newly issued building permits** from the official open data of **Chicago, New York City, Los Angeles, Seattle, Austin, Cincinnati, Baton Rouge, New Orleans, Montgomery County (MD), Cambridge (MA), Mesa, San Diego County, Pierce County (WA), Calgary and Edmonton** in one run, one clean row per permit: **permit number and type, the work description, issue and application dates, the address and coordinates, the project value, housing units and floor area where given, the contractor company and the permit's status**. **$1.50 per 1,000 permits**, no login, no API key.

Run it again, or put it on a daily schedule, and every row also tells you **whether the permit is new since the last run** (`isNewSinceLastRun`). Set *What to return* to *Only new* and you pay only for permits you have not seen before. In the week of 15–21 Oct 2026 these lists issued about 7,000 permits (New York City 3,046, Austin 1,166, Chicago 691, Los Angeles 541, Calgary 335, Edmonton 323, New Orleans 300 and the rest), so the useful question is what was issued since yesterday.

**Try it now. The input is already filled in.** Press **Start**: permits issued in the last 7 days in Chicago and Seattle, 20 per city — **40 permits in about 6 seconds, for $0.07** (measured on 28 Oct 2026). Three of the rows:

| `city` | `permitType` | `issuedOn` | `valuation` | `description` |
| --- | --- | --- | --- | --- |
| chicago | PERMIT - NEW CONSTRUCTION | 2026-10-26 | 625,000 | SC 2019 CBC. ERECT A 2 STORY, TWO DWELLING UNIT TYPE VA FRAME APARTMENT BUILDING WITH A BASEMENT … |
| seattle | New | 2026-10-23 | 810,118 | Construct new one family dwelling per plan |
| seattle | Addition/Alteration | 2026-10-23 | 461,717 | Construct addition and substantial alterations in existing one family dwelling per plan. |

Each row also has the address, ZIP, coordinates, contractor company (when the city publishes it) and a link to the permit. Seattle publishes a few days behind and Pierce County about a week behind, so their newest permits are a few days old.

### What you get

| Field | What it is |
|---|---|
| `city`, `cityName`, `region`, `country` | the list the permit came from (`chicago`, `new-york`…), its name, the state or province and `US` / `CA` |
| `isNewSinceLastRun` | `true` / `false` whether the permit is new since your previous run of the same search (`null` on the first run) |
| `permitNumber`, `permitType`, `workType`, `category` | the city's permit number, its type (`PERMIT - RENOVATION/ALTERATION`, `Plumbing Permit`…), the kind of work (`New`, `Remodel`, `Addition/Alteration`…) and `residential` / `commercial` where the city says so |
| `description` | the work description as the city wrote it |
| `status`, `appliedOn`, `issuedOn` | the permit's status and the application and issue dates |
| `address`, `zip`, `latitude`, `longitude` | where the work is |
| `valuation`, `currency` | the stated project value (job value, valuation or construction cost) in `USD` or `CAD` |
| `housingUnits`, `squareFeet`, `fees` | housing units added, floor area and permit fees, where the city gives them |
| `contractor`, `contractorHidden` | the contractor's company name (see *Notes*); `contractorHidden` is `true` when the city listed a contractor whose name looks like a person's, which is not returned |
| `permitUrl` | the city's page for the permit, where one exists (Seattle, Austin, Cincinnati) |
| `source`, `license`, `retrievedAt` | the open data list it came from, its licence, and when the row was collected |

### Run it again and again

- **On a schedule**: save your input as a task and add a **Schedule**: the same cities and keywords every morning. Send each run to Google Sheets, Slack or your CRM with an integration on the task.
- **Watch for changes**: Set **What to return** to *Only permits that are new since the last run*.
- **In bulk**: All 15 cities and counties, keywords and a minimum project value in one run.

### Use it for

- **Construction leads**: roofers, solar installers, pool builders, HVAC and supply companies: a daily list of new permits matching your trade, with the address and project value
- **Market tracking**: new construction and housing units added by city, week by week
- **Competitor watch**: which contractor companies are pulling permits, where and for how much
- **Real estate research**: renovation and addition activity around the properties you follow

### Input

```json
{
  "cities": ["chicago", "seattle", "austin"],
  "issuedWithinDays": 3,
  "keywords": ["solar", "roof"],
  "minValuation": 10000,
  "maxItemsPerCity": 200,
  "output": "new"
}
```

- **cities**: `chicago`, `new-york`, `seattle`, `los-angeles`, `austin`, `cincinnati`, `baton-rouge`, `montgomery-county-md`, `montgomery-county-md-commercial`, `new-orleans`, `cambridge`, `cambridge-new-construction`, `mesa`, `san-diego-county`, `pierce-county-wa`, `calgary`, `edmonton`. Empty means all of them.
- **issuedWithinDays** (default 7, up to 365) — only permits issued in the last N days.
- **keywords**: looked for in the work description and the permit type, by the city's own server. Lines are combined with OR, words on one line with AND.
- **categories**: `residential` and/or `commercial`. Only lists that say which kind a permit is can match (Seattle, Los Angeles, Austin, Baton Rouge, Montgomery County, New Orleans, Cambridge additions, San Diego County, Pierce County, Calgary, Edmonton).
- **minValuation**: permits with no stated value are left out when you set it.
- **maxItemsPerCity** (default 200, up to 5,000) — newest first, per list.

"Is it new" is remembered per search — the same cities, keywords, categories and minimum value — in a named key-value store in your Apify account. A permit counts as new when your previous run did not return it and it was issued no more than 3 days before the newest permit that run returned for that city: cities add permits to their lists a day or two late (New York City publishes some days in two parts), and those still come through. Permits that the previous run left out because of *Maximum permits per city* are remembered as seen and do not come back as new. A permit added more than 3 days late can be missed by *Only new*; a run without it shows everything in the window.

**City of Chicago data disclaimer** (required by the City of Chicago's data terms of use): "This site provides applications using data that has been modified for use from its original source, www.cityofchicago.org, the official website of the City of Chicago. The City of Chicago makes no claims as to the content, accuracy, timeliness, or completeness of any of the data provided at this site. The data provided at this site is subject to change at any time. It is understood that the data provided at this site is being used at one's own risk."

### Pricing

**$1.50 per 1,000 permits**, plus $0.01 per run. Error rows (a city's server not answering) are free. With *Only new*, permits you have already seen are skipped and not charged.

### Notes

- Source: each city's own open data portal (Socrata), read through its public JSON API (`/resource/<dataset>.json`), which needs no login or key. The robots.txt of every portal allows it and asks for one request a second, which the Actor keeps per portal. Each row names its dataset and licence: public domain for Seattle, Austin, Cincinnati, Baton Rouge, Montgomery County, New Orleans (CC0) and Cambridge (ODC-PDDL); the city's open data terms for Chicago, New York City, Los Angeles, Mesa, San Diego County and Pierce County; the Open Government Licence for Calgary and Edmonton.
- **No owners, applicants or contact persons.** The lists also carry owner names, applicant names and contacts' names, phones and emails (Austin, Baton Rouge, New Orleans, Chicago, New York City and others); the Actor never returns them. A contractor is returned only when the name carries a company marker (LLC, Inc., Construction, Roofing, Electric, Builders…); a name that looks like a person's (often a sole proprietor or the owner doing their own work) comes back as `contractor: null` with `contractorHidden: true`, and a person's name written after the company name (`… LLC - name`, `… LLC, name`) is cut off. In a live read of 40 permits per list on 23 Oct 2026, 36 of 493 contractors were hidden this way.
- The address is the location of the permitted work, as the city publishes it. Treat it with the care any property-level data needs: for example, check the laws on unsolicited marketing where you use it.
- Fill rates differ by city. In that read every list had a description and an address; the project value was given on 33 to 40 of 40 permits in most lists but on 1 of 40 in Austin (trade permits carry none) and 28 of 40 in Baton Rouge and New Orleans; coordinates were missing on most Cincinnati and half of Austin permits. Los Angeles publishes about once a week, so its newest permits can be several days old.
- Reading 40 permits from each of the 15 lists took 64 seconds. If a city does not answer, the Actor retries twice, then adds a free error row for that city and goes on with the others.

For newly registered companies from US state registries, see [US New Business Registrations](https://apify.com/northbell/us-business-registrations).

### For AI agents

`{"cities": ["chicago"], "issuedWithinDays": 3, "maxItemsPerCity": 5}` returns up to 5 rows, newest first, each with permit type, work description, address, project value and contractor company. Rows have `type`: `permit` or `error`. Fields a city does not publish are `null`, never missing. Schedule the same input daily with `"output": "new"` to get only permits issued since the previous run.

# Actor input Schema

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

Which permit lists to read, one per line: chicago, new-york, seattle, los-angeles, austin, cincinnati, baton-rouge, montgomery-county-md, montgomery-county-md-commercial, new-orleans, cambridge, cambridge-new-construction, mesa, san-diego-county, pierce-county-wa, calgary, edmonton. Montgomery County, MD and Cambridge, MA publish two lists each. Leave empty for all of them.

## `issuedWithinDays` (type: `integer`):

Only permits issued in the last N days. Most cities add yesterday's permits each morning; Los Angeles updates about once a week and Seattle publishes a few days behind. Run it daily with 3 and "Only new" below for a cheap feed of fresh permits.

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

Words to find in the work description or permit type, one per line: "solar", "roof", "new construction", "pool". A permit that matches any line is returned; words on one line must all appear. Case does not matter. Leave empty to skip.

## `categories` (type: `array`):

Only permits marked residential or commercial, one per line. Lists that do not say which (Chicago, New York City, Cincinnati, Mesa and some others) give no permits when you set this. Leave empty for all.

## `minValuation` (type: `number`):

Only permits whose stated project value (job value, valuation or construction cost, in US or Canadian dollars) is at least this. Permits that state no value are left out when you set this.

## `maxItemsPerCity` (type: `integer`):

How many permits to return from each list, newest first. You pay per permit returned.

## `output` (type: `string`):

With "Only new", permits you already got in an earlier run of the same search are skipped and not charged. A permit counts as new when it was issued on a later day than the newest permit your previous run returned for that city. The first run returns everything, because there is nothing to compare with yet.

## Actor input object example

```json
{
  "cities": [
    "chicago",
    "seattle"
  ],
  "issuedWithinDays": 7,
  "maxItemsPerCity": 20,
  "output": "all"
}
```

# Actor output Schema

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

Permit type, work, address, value and contractor for each 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 = {
    "cities": [
        "chicago",
        "seattle"
    ],
    "issuedWithinDays": 7,
    "maxItemsPerCity": 20
};

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

# Run the Actor and wait for it to finish
run = client.actor("northbell/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": [
    "chicago",
    "seattle"
  ],
  "issuedWithinDays": 7,
  "maxItemsPerCity": 20
}' |
apify call northbell/building-permits-scraper --silent --output-dataset

```

## MCP server setup

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