# Multi-City Building Permits – Open Data API (`civic-data-tools/building-permits`) Actor

Normalized building permits from 16 large US cities via official open-data portals only (Socrata, ArcGIS, CKAN). Filter by city, permit type, valuation, keyword, and issue-date range.

- **URL**: https://apify.com/civic-data-tools/building-permits.md
- **Developed by:** [Civic Data Tools](https://apify.com/civic-data-tools) (community)
- **Categories:** AI, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 permit 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

## Multi-City Building Permits (Open Data API)

One call returns **building permits** from large US cities in a single normalized schema. Every source is an official open-data portal (Socrata/SODA, ArcGIS FeatureServer, or municipal CKAN) — see `sources.md` for the terms review. No HTML scraping, no logins, no Accela session automation.

### Cities (v0.1)

| id | Coverage | Portal |
|---|---|---|
| `seattle` | Seattle, WA | Socrata |
| `nyc` | New York City DOB NOW approved permits | Socrata |
| `chicago` | Chicago, IL | Socrata |
| `los-angeles` | Los Angeles issued permits (2020–present) | Socrata |
| `san-francisco` | San Francisco, CA | Socrata |
| `austin` | Austin, TX issued construction permits | Socrata |
| `new-orleans` | New Orleans, LA (BLDS) | Socrata |
| `cincinnati` | Cincinnati, OH | Socrata |
| `mesa` | Mesa, AZ | Socrata |
| `montgomery-county-md` | Montgomery County, MD (residential + commercial) | Socrata |
| `norfolk` | Norfolk, VA | Socrata |
| `cambridge` | Cambridge, MA (new + alterations) | Socrata |
| `philadelphia` | Philadelphia, PA | ArcGIS |
| `denver` | Denver, CO (residential + commercial) | ArcGIS |
| `boston` | Boston, MA approved building permits | CKAN |
| `honolulu` | Honolulu, HI (**dump through 2025-06-30**) | Socrata |

### Input

`cities`, `keywords` (whole word; `solar*` for prefix), `matchMode`, `permitTypes` (substring on type), `minValuation`, `issuedAfter` / `issuedBefore` (YYYY-MM-DD), `issuedWithinDays` (default 30 when `issuedAfter` omitted), `socrataAppToken` (secret, optional), `maxResultsPerCity` (200), `maxResults` (1000). Every field is documented in `.actor/input_schema.json`.

### Output record

Normalized fields: `permitNumber`, `type`, `status`, `address`, `valuation`, `issuedDate`, `description`, `contractor`, `city`, `sourceUrl`, plus `recordType` (`permit-record`), `source`, `sourceName`, `state`, `matchedKeywords`, `retrievedAt`, and `disclaimer` where required (NYC, Montgomery County).

Errors never crash the run. They are written to the `OUTPUT` record as `AUTH_ERROR`, `RATE_LIMITED`, or `SOURCE_ERROR`.

### Pricing (pay per event)

- `permit-record` (primary): one per record written to the dataset. Proposed price **$0.002** (set via Apify API / Console).
- Keep `apify-actor-start` (synthetic). **Remove `apify-default-dataset-item`** so records aren't charged twice.
- The Actor follows the user's spending limit (`eventChargeLimitReached` stops the run cleanly). Events only (no "+ usage"), so it's eligible for Apify agentic payments once KYC is done.

### Run

```bash
npm install
npm test                 # offline unit tests (all cities mocked)
LIVE=1 npm test          # plus a live smoke test
npm run demo             # recent permits → demo/demo-recent.json
npm start                # Actor locally (input in storage/key_value_stores/default/INPUT.json)
node src/cli.js --keywords "solar,ADU" --cities seattle,austin,los-angeles --min-valuation 10000
```

### Demo results (live, 2026-10-05 PT)

- All cities, last 30 days, 50/city cap: **698 records in about 71 s**. Chicago had one transient network error on that run (retried OK separately). Honolulu correctly returned 0 in the Sept–Oct 2026 window (dump ends 2025-06-30).
- Solar / ADU niche (`solar`, `ADU`, `photovoltaic`), 90-day window: see `demo/demo-solar-adu.json` (LA ADUs, Boston solar/PV short-form permits, Mesa ADUs, Montgomery County solar/ADU work).
- Local Actor run (Apify SDK), keywords `solar,ADU` on Seattle/Austin/LA/Boston: 3 records, `permit-record` charged 3 times (simulated).

### Notes and limits

- Field coverage varies by city (not every portal publishes contractor or valuation).
- Honolulu is a fixed dump ending **2025-06-30**; use an explicit `issuedAfter`/`issuedBefore` in that window.
- **Disclaimers.** Montgomery County and NYC disclaimer text is included in README/listing and attached to those records. Not affiliated with any government.
- Data is provided by the cities "as is"; always verify critical records on the official portal before business use.

### Civic Data Tools (related Actors)

Same publisher (`civic-data-tools`) — useful together for agents monitoring local government:

| Actor | Use when | Store |
|---|---|---|
| **Public Bid & RFP Search** | Open government bids/RFPs plus new federal contract awards (official APIs) | [public-bid-search](https://apify.com/civic-data-tools/public-bid-search) |
| **City Council & County Agenda Search** | Keyword/topic matches in council, board, and commission agendas | [meeting-agenda-search](https://apify.com/civic-data-tools/meeting-agenda-search) |

This Actor covers **issued building permits**. Pair agenda search for upstream land-use votes and bid search for public construction solicitations.

# Actor input Schema

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

Leave empty to query all 16 cities. Each city is an official open-data portal (Socrata, ArcGIS, or CKAN). Example: "seattle", "nyc", "chicago".

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

Optional. Case-insensitive whole-word match on description, type, address, and contractor (simple plurals match; end a keyword with \* for prefix match, e.g. "solar\*"). Leave empty to return everything in range. Example: "solar", "ADU", "data center".

## `matchMode` (type: `string`):

"any" keeps records matching at least one keyword; "all" requires every keyword.

## `permitTypes` (type: `array`):

Optional. Case-insensitive substring match on the normalized type field (e.g. "Building", "Electrical", "Plumbing", "Mechanical", "Demolition"). Leave empty for all types.

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

Optional. Keep only permits whose reported valuation/cost is at least this amount in USD. Records with no valuation are dropped when this is set.

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

Optional ISO date (YYYY-MM-DD). Keep permits issued on or after this day. Defaults to 30 days ago when omitted.

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

Optional ISO date (YYYY-MM-DD). Keep permits issued on or before this day. Defaults to today when omitted.

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

Look-back window used when issuedAfter is omitted. Ignored if issuedAfter is set. Default 30 days.

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

Optional free app token for Socrata portals (most cities) to avoid shared IP rate limits. Get one at the portal's developer settings.

## `maxResultsPerCity` (type: `integer`):

Cap on records kept from each city after filters.

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

Hard cap on records (and charges) per run.

## Actor input object example

```json
{
  "keywords": [
    "solar",
    "ADU"
  ],
  "matchMode": "any",
  "issuedWithinDays": 30,
  "maxResultsPerCity": 200,
  "maxResults": 1000
}
```

# Actor output Schema

## `results` (type: `string`):

Dataset of building permit records matching your filters.

# 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 = {
    "keywords": [
        "solar",
        "ADU"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("civic-data-tools/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 = { "keywords": [
        "solar",
        "ADU",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("civic-data-tools/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 '{
  "keywords": [
    "solar",
    "ADU"
  ]
}' |
apify call civic-data-tools/building-permits --silent --output-dataset

```

## MCP server setup

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