# Chicago Building Permits Scraper (`usta/chicago-building-permits-scraper`) Actor

Building permits issued in Chicago over the last 1 to 90 days. One row per permit: address, ward, permit type, reported cost, fees paid, contractor and the City record link. Chicago only.

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

## Pricing

$3.00 / 1,000 result rows

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

## Chicago Permit Leads With Contractor & Fee Data

Contractor-insurance sales reps get one row per Chicago building permit with the contractor's name, role and fee already split into numeric fields, ready to prospect.

Building permits issued in the City of Chicago, pulled live from the City's own
open-data portal every time the Actor runs. One row per permit, with the address,
the ward, the permit type, the reported job cost, the fees paid, the contractor
businesses named on the permit, and a link back to the City's own record.

### Introduction

Chicago publishes every building permit it issues. The table is large, the column
names are the Department of Buildings' own, and fifteen contact slots sit beside
each permit holding a mix of businesses and private individuals. This Actor turns
that into a clean table a person can actually use: pick how many recent days to
cover, narrow by permit type, ward or job cost, and get back one row per permit.

Nothing is stored between runs and nothing is cached. Every run reads the City
portal at `data.cityofchicago.org`, dataset `ydr8-5enu`, over the public JSON API
with no key. Every row carries the exact City record link it came from, so any
row can be checked against the source.

Coverage read from the portal on 2026-09-14: 847,141 permits, the oldest issued
2006-01-03 and the newest issued 2026-09-13. This Actor reads the most recent 1
to 90 days of that table.

Chicago only. No other city, no county, no state, no nationwide option.

### Tutorial

1. Set **Days back** to how many recent days to cover, 1 to 90, counting back
   from today in Chicago and including today. 7 covers the last seven days.
2. Leave **Permit type** on "Any permit type", or pick one of the eleven types
   the City uses, such as renovation and alteration, new construction, signs, or
   wrecking and demolition.
3. Leave **Ward** empty for the whole city, or set a ward from 1 to 50.
4. Leave **Minimum reported cost** empty for every permit, or set a dollar figure
   to keep only the larger jobs.
5. Set **Maximum rows** to cap the run. That cap is also the cost cap, because
   billing is per row.
6. Start the run. Download the result as JSON, CSV or Excel from the dataset.

A run also writes a short report to the key-value store under `OUTPUT`: the row
count, how many of those rows name a contractor, the oldest and newest issue date
in the result, and the newest issue date the City currently holds.

### Pricing

$0.003 per row. Every row is billed, starting with the first row of the run.
There is no start fee and no charge for starting or stopping a run.

- 100 rows costs $0.30
- 1,000 rows costs $3.00
- 10,000 rows costs $30.00

A run that returns no rows costs nothing, because nothing is billed except rows.
A run that cannot reach the City portal returns no rows and so costs nothing. Bad
input is refused before anything is fetched, and also costs nothing.

**Maximum rows** is the spending control. Set it to 100 on a first run and look
at what comes back before asking for more.

### Input and output examples

Input:

```json
{
  "daysBack": 7,
  "permitType": "PERMIT - RENOVATION/ALTERATION",
  "ward": 27,
  "minCost": 10000,
  "maxItems": 100
}
```

One output row, shortened:

```json
{
  "city": "chicago",
  "permit_number": "B200471466",
  "record_id": "N2892074",
  "permit_type": "PERMIT – EXPRESS PERMIT PROGRAM",
  "review_type": "EXPRESS PERMIT PROGRAM",
  "work_type": "Masonry Work",
  "permit_status": "ACTIVE",
  "permit_milestone": "INSPECTION ELIGIBLE",
  "issue_date": "2026-09-13",
  "application_start_date": "2026-08-06",
  "processing_days": 38.0,
  "address": "6501 S RHODES AVE",
  "ward": "20",
  "community_area": "42",
  "census_tract": "420700",
  "parcel_pin": "2022219001",
  "latitude": 41.77658147442843,
  "longitude": -87.61178994510531,
  "reported_cost_usd": 15000.0,
  "fee_paid_usd": 375.0,
  "fee_unpaid_usd": 0.0,
  "fee_waived_usd": 0.0,
  "total_fee_usd": 375.0,
  "work_description": "TUCKPOINTING AND UNIT MASONRY REPAIR / REPLACEMENT.",
  "contractor_name": "FIX IT CHICAGO INC",
  "contractor_role": "GENERAL CONTRACTOR",
  "contractor_city": "OAK PARK",
  "contractor_state": "IL",
  "contractor_zip": "60304",
  "contractor_count": 2,
  "contractors": [
    {
      "role": "MASON CONTRACTOR",
      "name": "BENSON & SON CONSTRUCTION CO.",
      "city": "CHICAGO",
      "state": "IL",
      "zip": "60628"
    },
    {
      "role": "GENERAL CONTRACTOR",
      "name": "FIX IT CHICAGO INC",
      "city": "OAK PARK",
      "state": "IL",
      "zip": "60304"
    }
  ],
  "source_dataset": "data.cityofchicago.org/resource/ydr8-5enu",
  "source_url": "https://data.cityofchicago.org/resource/ydr8-5enu.json?id=N2892074",
  "fetched_at": "2026-09-15T02:54:02Z"
}
```

Every row carries all of these fields, plus an `attribution` field holding the
City of Chicago notice reproduced at the bottom of this page. A field the City
left blank comes back as `null` rather than being dropped, so every row has the
same shape and a CSV export has the same columns on every line.

#### The fields, in plain terms

| Field | What it is |
| --- | --- |
| `permit_number` | The permit number printed on the permit |
| `record_id` | The City's internal row id, used to build `source_url` |
| `permit_type` | One of eleven City permit types |
| `review_type` | The review track the permit went through |
| `work_type` | The kind of work, such as "Masonry Work" |
| `permit_status` | The City's status, such as `ACTIVE` |
| `permit_milestone` | Where the permit has reached, such as `INSPECTION ELIGIBLE` |
| `issue_date` | The day the permit was issued |
| `application_start_date` | The day the application was opened |
| `processing_days` | Days the City took between those two dates |
| `address` | Street number, direction and street name, joined |
| `ward` | Chicago ward, 1 to 50 |
| `community_area` | Community area number |
| `census_tract` | Census tract number |
| `parcel_pin` | Property index number of the parcel |
| `latitude`, `longitude` | Location, as published by the City |
| `reported_cost_usd` | Job cost declared on the application |
| `fee_paid_usd` | Permit fees paid |
| `fee_unpaid_usd` | Permit fees still owed |
| `fee_waived_usd` | Permit fees waived |
| `total_fee_usd` | Total fee on the permit |
| `work_description` | The City's own description of the work |
| `contractor_name` | The general contractor, or the first contractor listed |
| `contractor_role` | That contractor's role, such as `MASONRY CONTRACTOR` |
| `contractor_city`, `contractor_state`, `contractor_zip` | Where that business is based |
| `contractor_count` | How many contractor businesses the permit names |
| `contractors` | Every contractor on the permit, each with role, name and location |
| `attribution` | The City of Chicago notice |
| `source_dataset` | The dataset the row came from |
| `source_url` | The City API link that returns this one record |
| `fetched_at` | When the row was read, UTC |

### Recommendations

Start with **Days back** of 7, no permit type, no ward, and **Maximum rows** of
100\. That run costs at most $0.30 and shows exactly what Chicago publishes before
more is spent.

For remodel and renovation work, set **Permit type** to renovation and alteration
and **Minimum reported cost** to a figure that matches the jobs worth chasing.
For new build, use new construction. For demolition and site work, use wrecking
and demolition. For ward-level canvassing, set one ward at a time.

To keep a rolling file, run once a day with **Days back** of 3. The overlap
covers permits the City publishes a day or two late, and duplicates are easy to
drop on `record_id`, which is unique per permit record.

### Limitations

Read this before paying.

- **Chicago only.** No other city is available in this Actor, under any input.
- **The City publishes on a delay.** A permit appears in the portal some time
  after it is issued. When this page was written, the newest permit the portal
  held was dated the previous day. A window of 1 or 2 days can come back empty
  for that reason alone. Each run reports the newest issue date the City holds,
  so the current delay is visible in the run's own `OUTPUT` report.
- **Only contractor businesses are in the output.** Chicago names up to fifteen
  contacts per permit, most of them private individuals: owners, owner-occupiers,
  applicants, tenants, architects and engineers. Only contacts whose role names
  them as a contractor are kept. A role that begins with OWNER is dropped even
  when it contains the word CONTRACTOR, because "OWNER AS GENERAL CONTRACTOR"
  means a homeowner doing their own work. Most permits still name a contractor
  somewhere: in a sample of 1,000 permits issued between 2026-09-02 and
  2026-09-13, read on 2026-09-14, 975 had a contractor and 25 did not. That share
  is not guaranteed for any other window, so each run's `OUTPUT` report says how
  many rows in that run named one.
- **No phone numbers and no email addresses.** The City does not publish them in
  this dataset, so they are not in the output and cannot be added.
- **No property ZIP code.** This dataset has no ZIP column for the permit
  address. The ZIP fields in the output belong to the contractor's business
  address. Ward, community area, census tract, parcel number and coordinates are
  the location fields available.
- **Minimum reported cost drops blanks.** When that filter is set, permits where
  the City left the reported cost empty are not returned, because an empty value
  cannot be compared to a number. Leave the filter empty to keep them.
- **The data is the City's, and so is its accuracy.** Rows are a reshaped copy of
  what Chicago published at the moment of the run. Nothing is corrected, filled
  in, enriched or guessed. If the City's table is wrong or out of date, the row
  is too. The `source_url` on every row exists so any row can be checked.
- **90 days is the furthest back a run reaches.** The portal itself holds records
  from 2006, but this Actor is built for newly issued permits.
- **This is not a licence to the data.** The charge is for the collection and the
  shared schema. The City of Chicago's own Terms of Use still govern what is done
  with the rows, and the portal remains open to use directly.

### FAQ

**Is an API key needed?** No. The City portal is public and this Actor uses no
key of any kind.

**How is a row billed?** Per row returned to the dataset, at $0.003 each, from
the first row. Runs that return nothing are not billed.

**What does "Days back" of 1 mean?** Today only, in Chicago time. Because the
City publishes on a delay, that will often be zero rows. 7 is a safer starting
point.

**Why do some rows have no contractor?** Because the permit did not name one.
Many small Chicago permits list only the owner, and owner names are not returned.

**Can the date range be set to specific dates?** No. The window is the most
recent 1 to 90 days, ending today.

**Are duplicate rows possible?** Not within one run. Each permit record appears
once, keyed on the City's own record id.

**What happens if the City portal is down?** The run returns no rows, reports
status `UNKNOWN` with the exact error from the portal, and is not billed. Rows
are never invented or served from a cache.

**Can this be checked against the City?** Yes. Open the `source_url` on any row.
It returns that one permit record from the City's own API.

### Source and credit

Data source: City of Chicago, "Building Permits", dataset `ydr8-5enu`, published
at `data.cityofchicago.org`.

The City of Chicago Terms of Use at
`https://www.cityofchicago.org/city/en/narr/foia/data_disclaimer.html` allow a
secondary or derivative application built on this data, on the condition that the
City's disclaimer travels with it. That disclaimer, word for word, is below, and
it is also attached to every row in the `attribution` field:

> 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.

### Refunds and support

Refunds: if a run returns zero rows for a valid input, email operations@ustechautomations.com within 7 days and we refund that run. Support: same address, replies within 2 business days.

# Actor input Schema

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

How many recent days to cover, counting back from today in Chicago and including today. 7 means the last seven days. Chicago usually publishes a permit a few days after it is issued, so a window of 1 or 2 days is often empty.

## `permitType` (type: `string`):

Leave as Any permit type to get them all. The names are the City's own spellings.

## `ward` (type: `integer`):

Keep only permits in one Chicago ward, 1 to 50. Leave empty for the whole city.

## `minCost` (type: `integer`):

Keep only permits whose reported job cost is at least this many US dollars. Permits where the City left the reported cost blank are left out when this is set, because a blank cannot be compared. Leave empty to keep every permit.

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

Hard ceiling on rows returned, and therefore on the cost of the run. Every row returned is billed, starting at the first row. A run that returns no rows costs nothing.

## Actor input object example

```json
{
  "daysBack": 7,
  "permitType": "",
  "maxItems": 100
}
```

# Actor output Schema

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

No description

## `csv` (type: `string`):

No description

## `report` (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 = {
    "daysBack": 7,
    "permitType": "",
    "maxItems": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("usta/chicago-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 = {
    "daysBack": 7,
    "permitType": "",
    "maxItems": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("usta/chicago-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 '{
  "daysBack": 7,
  "permitType": "",
  "maxItems": 100
}' |
apify call usta/chicago-building-permits-scraper --silent --output-dataset

```

## MCP server setup

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