# US Building Permits Scraper (`usta/us-building-permits-scraper`) Actor

4-city bundle: building permits from Chicago, Los Angeles, Boston and Washington DC. Pick one city and a date range; get one row per permit with type, address, status, valuation and the public record link.

- **URL**: https://apify.com/usta/us-building-permits-scraper.md
- **Developed by:** [US Tech Automations](https://apify.com/usta) (community)
- **Categories:** Real estate, Business, Lead generation
- **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?

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

## Permit Leads Across 4 Cities, One Shared Format

Proptech and multi-market contractor-supply analysts get building permits from four major-city portals under one shared set of column names, ready to benchmark without writing four separate parsers.

### Introduction

Pick one city and a date range. Get back one row per building permit the city
issued in that range, with the link to the city's own public record on every
single row.

Four cities are supported, and only four: Chicago, Los Angeles, Boston and
Washington DC. **This is not a nationwide permit feed.** If a project needs
Houston or Phoenix, this Actor is the wrong tool.

Each run reads the city's own open-data portal live, at the moment the run
starts. Nothing is served from a stored copy. The four portals behind it are
the City of Chicago data portal, the City of Los Angeles data portal, Analyze
Boston and Open Data DC. All four are free and public. What this Actor adds is
one shared set of column names across four portals that otherwise disagree about
almost everything, a date filter that works the same way in all four, and a
record link on every row so any number can be checked at the source.

Every row carries these fields:

| Field | What it holds |
| --- | --- |
| `city` | Which of the four cities the row came from |
| `permit_number` | The city's own permit number |
| `permit_type` | The city's permit type wording, unchanged |
| `permit_subtype` | Review type, occupancy type or permit subtype, where the city publishes one |
| `work_class` | Work type or category, where the city publishes one |
| `status` | The city's status wording, where the city publishes one |
| `issue_date` | Date the permit was issued, as YYYY-MM-DD |
| `address` | Street address as the city published it |
| `zip` | Postal code, where the city publishes one |
| `ward` | Ward or council district, where the city publishes one |
| `valuation_usd` | Declared or reported job value in US dollars, as a number |
| `fee_paid_usd` | Permit fees in US dollars, as a number |
| `square_feet` | Floor area, where the city publishes one |
| `work_description` | The city's description of the work |
| `latitude`, `longitude` | Point location, where the city publishes one |
| `attribution` | The credit or notice the publishing city requires |
| `source_dataset` | The portal and dataset the row was read from |
| `source_url` | Link to that exact record on the city's public API |
| `fetched_at` | UTC time the row was read, as an ISO timestamp |

A field the city does not publish arrives as `null`. It is never guessed and
never filled in from somewhere else.

### Tutorial

1. Choose **City** from the dropdown.
2. Set **Issued on or after** and **Issued on or before**. Both days are
   included in the results.
3. Set **Maximum rows**. This is a hard ceiling on rows returned, so it is also
   a hard ceiling on what the run can cost. It starts at 100, so a run left on
   the default costs at most $0.30.
4. Start the run.
5. Open the **Permits** output to browse rows, or **Download permits as CSV**
   to take them away. The **Run status** output holds the row count, the oldest
   and newest issue date actually returned, the source, the required credit
   line, and any warnings.

To sweep a long period without one enormous run, run month by month and raise
**Maximum rows** only as far as needed.

### Pricing

**$0.003 per row, charged from the first row. There is no start fee.**

Billing is per result. One row pushed is one charged result. A run that returns
40 rows is charged for 40 rows, which is $0.12. A run that returns 100 rows
costs $0.30. A run that returns 1,000 rows costs $3.00.

Three kinds of run push no rows, and a run that pushes no rows costs nothing:

- A run that fails because a city portal is down. Status is `UNKNOWN`, the exact
  error is reported, and no rows are pushed.
- A run rejected for bad input. Status is `INVALID_INPUT` and no rows are pushed.
- A run where the city genuinely issued no permits in that date range. Zero rows
  pushed is zero charged.

Because nothing is charged except a pushed row, **Maximum rows** is a real
spending cap. Set it to 100 and a run cannot cost more than $0.30.

Apify handles billing and account charges. Apify platform usage is separate from
the per-row price.

### Input and output examples

Input, which is also `example.json`:

```json
{
  "city": "chicago",
  "issuedAfter": "2026-08-01",
  "issuedBefore": "2026-08-31",
  "maxItems": 100
}
```

One output row, trimmed for length. The full three-row sample is in
`sample-output.json`:

```json
{
  "city": "chicago",
  "permit_number": "101076242",
  "permit_type": "PERMIT - RENOVATION/ALTERATION",
  "permit_subtype": "STANDARD PLAN REVIEW",
  "status": "ACTIVE",
  "issue_date": "2026-08-31",
  "address": "636 N ST LOUIS AVE",
  "zip": null,
  "ward": "27",
  "valuation_usd": 114880.0,
  "fee_paid_usd": 3099.11,
  "work_description": "SPR 2019 CBRC: INTERIOR AND EXTERIOR RENOVATIONS ...",
  "latitude": 41.892969033692374,
  "longitude": -87.71394557854016,
  "source_dataset": "data.cityofchicago.org/resource/ydr8-5enu",
  "source_url": "https://data.cityofchicago.org/resource/ydr8-5enu.json?id=3431525",
  "fetched_at": "2026-09-15T02:31:02Z"
}
```

Opening that `source_url` returns that one permit from the City of Chicago's own
API. Every row in every city works the same way.

### Limitations

Read this part before paying for anything.

- **Four cities, not the United States.** Chicago, Los Angeles, Boston and
  Washington DC. Nothing else.
- **Each city's history starts where its own portal starts.** On 14 September
  2026 the portals held issue dates from: Chicago 3 January 2006, Los Angeles
  1 January 2020, Boston 26 September 2006, and Washington DC calendar year 2009
  onward. Asking for dates before a city's start returns fewer rows, not an
  error.
- **Freshness is the city's, not this Actor's.** Portals lag by days, and each
  city lags differently. On 14 September 2026 the newest issue date held was
  13 September for Chicago, 12 September for Los Angeles and 3 September for
  Boston. A permit issued yesterday may not be on the portal yet. `fetched_at`
  records when the portal was read, not when the city last updated it.
- **No names of people.** Applicant, owner, contractor and contact-name columns
  are removed before rows are built, in every city that publishes them. This
  Actor returns permits, not people, and it is not a contact list.
- **Washington DC has no work description.** The District puts phone numbers,
  email addresses and contact names inside that one free-text field, so the
  whole field is dropped. `work_description` is always `null` for Washington DC.
- **Fields differ between cities because the cities differ.** Chicago publishes
  no postal code on this dataset. Washington DC publishes fees but no job
  valuation, and no work description. Those arrive as `null`, not as a
  substitute value.
- **`maxItems` cuts the newest rows first.** Results come back newest issue date
  first. Hitting the ceiling means older permits in the range were not returned,
  and the run status says so in its warnings.
- **These records are the city's, and cities revise them.** A permit can be
  amended, voided or reissued after it appears. This reports what the portal
  held at `fetched_at`, and makes no claim the city's own table is complete or
  correct.
- **This is not a licence to the underlying data.** The charge is for the
  collection and the shared schema. Each city's own terms still apply to what
  is done with its rows, and each portal remains free to use directly.

### Recommendations

Start with one month in one city at the default 100-row ceiling. That run costs
at most $0.30, and the rows show exactly what that city does and does not
publish before more is spent.

Filter on `permit_type` after the fact rather than expecting the same wording in
two cities. The type wording is each city's own and it does not match across
portals: Chicago writes `PERMIT - NEW CONSTRUCTION`, Los Angeles writes
`Bldg-New`, and Boston writes `Short Form Bldg Permit`.

Treat `valuation_usd` as the value declared to the city, which is what it is. It
is not an appraisal and it is not a construction cost estimate.

Keep the `attribution` field with the rows. Chicago and Washington DC both
require a notice to travel with any copy of their data, and that field carries
the exact wording each one requires.

For a repeating pull, schedule one run per city per week over the last seven
days, rather than one large backfill run.

### FAQ

**Does this cover my city?**
Only if it is Chicago, Los Angeles, Boston or Washington DC.

**Can I get contractor or owner contact details?**
No. Person-name and contact columns are stripped before rows are built, in
every city. This Actor is not a contact database.

**Is the data live?**
Each run reads the city portal at the moment it starts. How current that portal
is remains the city's decision, and the Limitations section gives the lag seen
on 14 September 2026.

**What happens if a city portal is down?**
The run reports status `UNKNOWN` with the exact error from that portal, pushes
no rows and charges nothing. Nothing is invented and nothing stale is
substituted.

**Why is a field empty?**
Because that city does not publish it on that dataset, or left it blank on that
record. Empty fields are never filled in with a guess.

**Can I verify a row?**
Yes. Open its `source_url`. It returns that one record from the city's own
public API.

**Can I get this data for free?**
Yes. All four portals are free and public. Going direct means four different
APIs, four sets of column names, four date-filter syntaxes and no shared schema.
That work is what the per-row price covers.

**Is there support?**
Use this Actor's Issues tab with the run ID and a redacted example. There is no
promise of a reply from a person and no service-level agreement.

### Source credit

- Chicago: City of Chicago, dataset `ydr8-5enu`. The City's required data notice
  travels on every Chicago row in the `attribution` field.
- Los Angeles: City of Los Angeles, dataset `pi9x-tg5x`, Building Permits Issued
  from 2020 to Present.
- Boston: City of Boston, Analyze Boston approved building permits, under the
  Open Data Commons Public Domain Dedication and License (PDDL) v1.0.
- Washington DC: Open Data DC, Building Permits, under Creative Commons
  Attribution 4.0 International (CC BY 4.0). The required credit travels on
  every Washington DC row in the `attribution` field.

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

## `city` (type: `string`):

Which city's permit records to read. Four cities are supported; this Actor is not nationwide.

## `issuedAfter` (type: `string`):

First issue date to include, written as YYYY-MM-DD. This day is included.

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

Last issue date to include, written as YYYY-MM-DD. This day is included.

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

Hard ceiling on rows returned, and therefore on the cost of the run. Every row bills at the listed price from row one; there is no start fee.

## Actor input object example

```json
{
  "city": "chicago",
  "issuedAfter": "2026-08-01",
  "issuedBefore": "2026-08-31",
  "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 = {
    "city": "chicago",
    "issuedAfter": "2026-08-01",
    "issuedBefore": "2026-08-31",
    "maxItems": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("usta/us-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 = {
    "city": "chicago",
    "issuedAfter": "2026-08-01",
    "issuedBefore": "2026-08-31",
    "maxItems": 100,
}

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

```

## MCP server setup

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