# Building Permits & Code Violations: 10 US Cities (`maniginam/city-building-permits-code-violations`) Actor

Get new building permits and code violations from Chicago, New York, Los Angeles, San Francisco, Austin, Seattle, Baton Rouge, New Orleans, Cincinnati, and Orlando open data in one clean format.  Filter by keyword, ZIP code, and project cost, and pay only for new records on repeat runs.

- **URL**: https://apify.com/maniginam/city-building-permits-code-violations.md
- **Developed by:** [ManiGinaM](https://apify.com/maniginam) (community)
- **Categories:** Lead generation, Real estate
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 permit or violation records

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

Get new **building permits** and **code violations** from ten US cities in one clean, consistent format: **Chicago, New York, Los Angeles, San Francisco, Austin, Seattle, Baton Rouge, New Orleans, Cincinnati, and Orlando**.  The Actor reads each city's official open data portal, turns every permit, violation, and complaint into the same record with the date, status, category, description, address, ZIP code, coordinates, estimated project cost, and contractor, and links back to the source.

Filter by keyword, ZIP code, and minimum project cost, and turn on **new records only** so a daily schedule pays only for permits and violations you have not received before.

### Who is it for?

- **Contractors, roofers, solar installers, and home service companies** looking for new projects and property owners who need work done.
- **Building material suppliers and equipment rental firms** tracking where construction is starting.
- **Real estate investors and wholesalers** looking for distressed properties with open code violations.
- **Property managers, insurers, and lenders** watching permits and violations on addresses they care about.
- **Analysts, journalists, and researchers** comparing construction activity and code enforcement across cities.

### What data does it return?

One row per building permit or code violation that matches your filters, in the same fields for every city:

- City, record type (permit or violation), record number, and date issued or opened
- Status, category, and description of the work or the violation
- Street address, ZIP code, latitude, and longitude
- Estimated project cost and contractor, where the city publishes them
- A link to the record on the city's site, where available, and to the source dataset

Owner name and apartment number fields are never read, so they are left out on purpose.  Descriptions are kept as the city wrote them.

### How to use

1. Open the Actor and go to the **Input** tab.
2. Pick the **Cities** and **Record types** you want, or leave them empty for all ten cities and both types.
3. Set **Days to look back**, for example 7 for the last week.
4. Optionally add **Keywords** such as roof or solar, **ZIP codes**, and a **Minimum project cost** to narrow the list.
5. To run it on a schedule, turn on **New records only** and give the schedule a **Monitor name**.
6. Click **Start**, then open the **Output** tab to view, filter, or download the records as CSV, Excel, JSON, or HTML.
7. Add a daily schedule under **Schedules** to receive new permits and violations every day.

### Input

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `cities` | array | all | chicago, new-york, los-angeles, san-francisco, austin, and seattle. |
| `recordTypes` | array | both | permit for building permits and violation for code violations and complaints. |
| `daysBack` | integer | 7 | Calendar days of records to read, today included, up to 90. |
| `keywords` | array | none | Keep only records whose category or description contains one of these words. |
| `zipCodes` | array | all | Keep only records in these 5-digit ZIP codes. |
| `minEstimatedCost` | integer | 0 | Keep only permits with at least this estimated project cost in dollars.  Violations are not affected. |
| `onlyNewRecords` | boolean | false | Skip records that an earlier run with the same monitor name already delivered, free of charge. |
| `monitorName` | string | default | Keeps a separate memory of delivered records per name. |
| `maxResultsPerSource` | integer | 500 | Stop reading one city dataset after this many records, up to 50,000. |
| `maxResults` | integer | 5000 | Stop after this many records in total, up to 200,000. |

Example input for a daily schedule of roofing and solar permits worth at least $10,000 in Austin and Seattle:

```json
{
  "cities": ["austin", "seattle"],
  "recordTypes": ["permit"],
  "daysBack": 3,
  "keywords": ["roof", "solar"],
  "minEstimatedCost": 10000,
  "onlyNewRecords": true,
  "monitorName": "roof-solar-austin-seattle",
  "maxResults": 1000
}
```

### Output

Each record looks like this (a fictional example):

```json
{
  "city": "Austin, TX",
  "recordType": "permit",
  "recordId": "2026-999001 BP",
  "date": "2026-11-02",
  "status": "Active",
  "category": "Building Permit",
  "description": "Residential reroof and new rooftop solar panels",
  "address": "100 Example St",
  "zipCode": "78701",
  "latitude": 30.2672,
  "longitude": -97.7431,
  "estimatedCost": 24500.0,
  "contractor": "Example Roofing LLC",
  "recordUrl": "https://abc.austintexas.gov/web/permit/public-search-other?t_detail=1&t_selected_folderrsn=99999999",
  "datasetUrl": "https://data.austintexas.gov/d/3syk-w9eu",
  "checkedAt": "2026-11-03T14:05:12Z",
  "note": null
}
```

Fields a city does not publish are null, for example estimated cost on violations.  When a run finds nothing to deliver, for example because every record was already delivered or none matched, it adds one free row with only `checkedAt` and a `note` explaining why.

Every run also saves a free `RUN_SUMMARY` record in the default key-value store with the datasets read, datasets that could not be read, rows scanned, records matched, records skipped as already delivered, records published per dataset, and why the run ended.

### Cities and datasets

| City | Building permits | Code violations |
| --- | --- | --- |
| Chicago, IL | Building Permits | Building Violations |
| New York, NY | DOB NOW Build Approved Permits | HPD Housing Maintenance Code Violations |
| Los Angeles, CA | Building and Safety Building Permits Issued | Building and Safety Code Enforcement Cases, open and closed |
| San Francisco, CA | Building Permits | DBI Notices of Violation |
| Austin, TX | Issued Construction Permits | Austin Code Complaint Cases |
| Seattle, WA | Building Permits | Code Complaints and Violations |
| Baton Rouge, LA | EBR Building Permits | Not published |
| New Orleans, LA | Permits | Code Enforcement All Violations |
| Cincinnati, OH | Cincinnati Building Permits | Code Enforcement, City of Cincinnati cases only |
| Orlando, FL | Permit Applications, issued permits only | Code Enforcement Cases |

Each record's `datasetUrl` links to the exact public dataset.  Rows a city publishes more than once for the same record are merged into one record, so you are charged once per permit or violation.

### New records only

With **New records only** on, the Actor remembers every record it delivers in a named key-value store called `city-building-permits-code-violations-monitor`, under the monitor name you choose.  Later runs with the same monitor name skip those records and do not charge for them, so a daily schedule with a few days of overlap never pays twice for the same permit.  Use a different monitor name for each schedule with different filters.

### How it works

The Actor queries each city's official Socrata open data API for records dated in your range, newest first, a page at a time.  It applies your filters, merges duplicate rows, and delivers the matching records in one schema.  It reads one page at a time with a short pause and identifies itself with a contact address.  A city that cannot be reached is noted in the summary while the other cities still deliver.

### Pricing

This Actor uses pay per event pricing: **$1.00 per 1,000 records**, charged as the `permit-or-violation` event for each record delivered, plus a tiny Actor start fee.  Run notes, the summary, filtered out records, and records skipped by **New records only** are free.  Set **Max records in total** to cap what a run can cost.

### API and integrations

You can start the Actor and read its results from your own code with the [Apify API](https://docs.apify.com/api/v2), the Apify Python and JavaScript clients, or the Apify CLI.  Copy ready-made calls from the **API** tab on the Actor page.

Send new records to a CRM, Google Sheets, Slack, Zapier, Make, or a webhook from the **Integrations** tab, for example to alert your crew about every new roofing permit in your ZIP codes.

### Related Actors

- [Louisiana Building Permits](https://apify.com/maniginam/louisiana-building-permits): building permits from Baton Rouge and New Orleans.
- [Website Contact Enrichment](https://apify.com/maniginam/website-contact-enrichment): emails, phones, and social links from contractor websites.
- [SEC Form D Funding Leads](https://apify.com/maniginam/sec-form-d-funding-leads): companies that just raised money, from new SEC filings.
- [Career Site Job Search](https://apify.com/maniginam/career-site-job-search): open jobs from company career sites.

### Is it legal to use building permit and code violation data?

Building permits and code enforcement records are public records that these cities publish on their official open data portals for anyone to use.  The Actor reads only those public datasets and leaves out the owner name and apartment number fields.  You are responsible for how you use the records, including following anti-spam, privacy, and telemarketing laws such as CAN-SPAM and the TCPA when you contact anyone.  This Actor is not affiliated with or endorsed by any of these cities.

### FAQ

**How fresh is the data?**  Most cities update their datasets daily.  Some publish a record a few days or weeks after it is issued, so a run with a larger **Days to look back** and **New records only** on catches late records without paying twice.

**Why did a run return no records?**  Either no record matched your filters, every match was already delivered with **New records only** on, or the city had not published records for your range yet.  The free note row and the `RUN_SUMMARY` record say which.

**Does it include owner names or phone numbers?**  No.  Owner names are left out on purpose.  Pair contractor names with [Website Contact Enrichment](https://apify.com/maniginam/website-contact-enrichment) to find business contact details.

**How is it different from the Louisiana Building Permits Actor?**  That Actor goes deeper on Baton Rouge and New Orleans permits.  This one covers ten cities across the US, including Baton Rouge and New Orleans, adds code violations and complaints, and returns one schema for every city.

**Can you add my city?**  Yes, see below.

### Feedback and custom requests

Found a bug or a city dataset that changed?  Open an issue on the **Issues tab** and it will be fixed quickly.  For another city, extra fields, or a custom request such as a different filter or output, open an issue as well.

# Changelog

This Actor's version history is a separate document: https://apify.com/maniginam/city-building-permits-code-violations/changelog.md

# Actor input Schema

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

Read these cities.  Leave empty for all ten.  Baton Rouge publishes building permits only.

## `recordTypes` (type: `array`):

Building permits issued, code violations or complaints opened, or both.  Leave empty for both.

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

Read records dated within this many calendar days, today included, newest first.  Cities publish with a delay of one day to a few weeks, so a larger window finds more.

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

Keep only records whose category or description contains one of these words, such as roof, solar, or demolition.  Case does not matter.  Leave empty for every record.

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

Keep only records in these 5-digit ZIP codes, such as 78701.  Leave empty for every ZIP code.

## `minEstimatedCost` (type: `integer`):

Keep only permits whose estimated project cost is at least this many dollars, and drop permits with no cost.  Violations are not affected.  Use 0 for no minimum.

## `onlyNewRecords` (type: `boolean`):

Skip records an earlier run of this monitor already delivered, so a scheduled run is charged only for new ones.

## `monitorName` (type: `string`):

Name of the history used by new records only.  Use a different name for each separate watch list.

## `maxResultsPerSource` (type: `integer`):

Stop reading a city dataset after this many records, so one busy city cannot fill the run.

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

Stop the run after this many records across every city.

## Actor input object example

```json
{
  "cities": [
    "chicago",
    "austin",
    "new-orleans"
  ],
  "recordTypes": [
    "permit",
    "violation"
  ],
  "daysBack": 7,
  "minEstimatedCost": 0,
  "onlyNewRecords": false,
  "monitorName": "default",
  "maxResultsPerSource": 20,
  "maxResults": 200
}
```

# 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": [
        "chicago",
        "austin",
        "new-orleans"
    ],
    "recordTypes": [
        "permit",
        "violation"
    ],
    "daysBack": 7,
    "maxResultsPerSource": 20,
    "maxResults": 200
};

// Run the Actor and wait for it to finish
const run = await client.actor("maniginam/city-building-permits-code-violations").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",
        "austin",
        "new-orleans",
    ],
    "recordTypes": [
        "permit",
        "violation",
    ],
    "daysBack": 7,
    "maxResultsPerSource": 20,
    "maxResults": 200,
}

# Run the Actor and wait for it to finish
run = client.actor("maniginam/city-building-permits-code-violations").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",
    "austin",
    "new-orleans"
  ],
  "recordTypes": [
    "permit",
    "violation"
  ],
  "daysBack": 7,
  "maxResultsPerSource": 20,
  "maxResults": 200
}' |
apify call maniginam/city-building-permits-code-violations --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,maniginam/city-building-permits-code-violations"
        }
    }
}
```

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/d4gQDNVNgaWy6aESA/builds/Bqdxkpv4DqzChzh0k/openapi.json
