# Building Permits Scraper - 12 US Cities Open Data (`darknezz/building-permits-scraper`) Actor

Scrape building & construction permits from 12 US cities' open-data portals (Socrata + ArcGIS) into one clean schema: permit number, type, status, issue date, address, work description, valuation, contractor & applicant. Free JSON APIs, no key. Filter by city, date & permit type.

- **URL**: https://apify.com/darknezz/building-permits-scraper.md
- **Developed by:** [Oaida Adrian](https://apify.com/darknezz) (community)
- **Categories:** Real estate, Lead generation
- **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 extracteds

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/platform/actors/running/actors-in-store#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

## Building Permits Scraper — 12 US Cities Open Data

Pull **building & construction permits from 12 US cities into one clean, normalized schema** — no API key, no account, no anti-bot. Every city publishes permits on its own open-data portal with its own column names; this actor translates all of them into the *same* record, so you can query a dozen jurisdictions as one feed.

Built for **contractors, roofers, solar & HVAC installers, remodelers, suppliers and real-estate investors** who want fresh permit leads and market intelligence — and for analysts tracking construction activity.

### Why this actor

- **No API keys, no login, no proxy budget** — every source is an official government open-data API (Socrata or ArcGIS FeatureServer).
- **12 cities, one schema** — `city`, `permit_number`, `permit_type`, `status`, `issue_date`, `application_date`, `address`, `work_description`, `valuation`, `contractor_name`, `applicant`, `latitude`, `longitude`, `source`, `source_type`, `source_url`, `scraped_at`.
- **Server-side filtering** — date and permit-type filters are applied by the source APIs, so runs are fast and cheap.
- **Honest nulls** — not every city publishes every field (e.g. Chicago has no status column, Cincinnati has no coordinates). Missing values come back as `null`, never faked.
- **Pay per permit** — a single low per-result price, plus a one-time start fee.

### Supported cities

| City | Source | Portal |
|---|---|---|
| Chicago, IL | Socrata | data.cityofchicago.org |
| New York, NY | Socrata | data.cityofnewyork.us (DOB NOW) |
| Los Angeles, CA | Socrata | data.lacity.org |
| San Francisco, CA | Socrata | data.sfgov.org |
| Austin, TX | Socrata | data.austintexas.gov |
| Seattle, WA | Socrata | data.seattle.gov |
| Cincinnati, OH | Socrata | data.cincinnati-oh.gov |
| Mesa, AZ | Socrata | citydata.mesaaz.gov |
| Montgomery County, MD | Socrata | data.montgomerycountymd.gov |
| Baton Rouge, LA | Socrata | data.brla.gov |
| Washington, DC | ArcGIS | maps2.dcgis.dc.gov |
| West Sacramento, CA | ArcGIS | gis.cityofwestsacramento.org |

### Input

```json
{
  "cities": ["austin", "los_angeles", "baton_rouge"],
  "dateFrom": "2025-01-01",
  "permitType": "roof",
  "maxItems": 500
}
```

| Field | Type | Description |
|---|---|---|
| `cities` | array | City keys to scrape (multi-select). Leave empty to scrape **all** supported cities. |
| `dateFrom` | date | Only permits issued on/after this date (`YYYY-MM-DD`). Applied server-side per city. |
| `permitType` | string | Keyword filter matched against permit type **and** work description — e.g. `roof`, `solar`, `pool`, `electrical`, `demolition`, `new construction`. |
| `maxItems` | integer | Max permits **per city** (3 cities × 500 = up to 1,500 rows). Default 1,000. |

### Output (one item per permit)

```json
{
  "city": "Austin, TX",
  "cityKey": "austin",
  "permit_number": "2026-104132 BP",
  "permit_type": "Building Permit - New",
  "status": "Active",
  "issue_date": "2026-07-10",
  "application_date": "2026-04-20",
  "address": "4102 CAT HOLLOW DR AUSTIN TX 78731",
  "work_description": "New construction in-ground pool and spa.",
  "valuation": 0.0,
  "contractor_name": "LOHR Homes",
  "applicant": "Gil Lohr",
  "latitude": 30.3623778,
  "longitude": -97.76570347,
  "source": "data.austintexas.gov",
  "source_type": "socrata",
  "source_url": "https://data.austintexas.gov/d/3syk-w9eu",
  "scraped_at": "2026-07-10T21:56:20Z"
}
```

### Use cases

- 📈 **Lead generation** — a roofing company pulls every roofing permit issued since January across Austin, LA and Baton Rouge: a ready-made call list.
- 🏗️ **Market intelligence** — track construction activity and valuations by city, type and time.
- 🕵️ **Property research** — enrich addresses with permit history for due diligence.
- ⏰ **Automation** — run on a schedule (e.g. weekly) to watch new permits in your territory, pipe results into a CRM or a Telegram alert.

### Run it from your code

**Python (Apify SDK):**

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR-APIFY-TOKEN")

run_input = {
    "cities": ["austin", "baton_rouge"],
    "dateFrom": "2026-01-01",
    "permitType": "roof",
}

run = client.actor("darknezz/building-permits-scraper").call(run_input=run_input)
for permit in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(permit["permit_number"], permit["address"], permit["contractor_name"])
```

**cURL (one-liner, sync):**

```bash
curl -X POST "https://api.apify.com/v2/acts/darknezz~building-permits-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"cities":["austin"],"dateFrom":"2026-06-01","permitType":"solar"}'
```

**Worked example** — a real run for `permitType: "pool"` in Austin returns rows such as `2026-104132 BP` — "New construction in-ground pool and spa." at 4102 Cat Hollow Dr, contractor LOHR Homes, valuation $0.0 (city-reported), issued 2026-07-10. Every row carries the source portal URL so you can verify it in one click.

### FAQ

**Do I need an API key?** No. All 12 sources are public government open-data APIs.

**Why is a field `null`?** That city does not publish it in a machine-readable form (e.g. Cincinnati has no coordinates, Chicago no status column). Nothing is invented.

**How fresh is the data?** Each city refreshes its own portal on its own schedule — most are daily. `scraped_at` on every row tells you exactly when the record was pulled.

**Can I add a city?** Yes — the actor is built as a config table. Point a new city at its Socrata dataset or ArcGIS FeatureServer, list the date field and map columns; no fetch-layer changes needed.

**Which permit types can I filter by?** Any text that appears in the city's permit-type or work-description columns — `roof`, `solar`, `pool`, `electrical`, `demolition`, `new construction`, `alteration`, etc. Filtering is a keyword match, so combine it with `dateFrom` to scope tightly.

**Is this a consumer report?** No. This data is aggregated from public government records for market research and lead generation. It must not be used for any FCRA-covered purpose (credit, employment, insurance, or tenant screening).

### Limitations

- **Not every city is covered** — the actor ships with 12 cities; other jurisdictions need a config entry (see "Can I add a city?").
- **Some old ArcGIS servers are slow** — a few cities respond best with lighter query parameters; the actor automatically falls back to a simpler query when a server rejects the first form.
- **Placeholder permits** — some cities pre-issue "not yet issued" placeholder rows; these are detected and skipped.
- **Empty results are free** — if your filter matches nothing, the run succeeds with zero items and you are charged nothing beyond the start fee.

### Pricing

Pay per permit extracted (`permit-extracted` event) + a one-time start fee. No charge for empty results.

# Actor input Schema

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

Cities to scrape. Leave empty to scrape all supported cities.

## `dateFrom` (type: `string`):

Only permits issued on or after this date (YYYY-MM-DD). Applied server-side per city.

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

Keyword filter matched against permit type AND work description, e.g. roof, solar, pool, electrical, demolition, new construction.

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

Maximum permits to return per city (3 cities x 500 = up to 1,500 rows).

## Actor input object example

```json
{
  "cities": [
    "chicago",
    "austin"
  ],
  "dateFrom": "2026-01-01",
  "permitType": "roof",
  "maxItems": 1000
}
```

# Actor output Schema

## `results` (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"
    ],
    "dateFrom": "2026-01-01",
    "permitType": "roof"
};

// Run the Actor and wait for it to finish
const run = await client.actor("darknezz/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",
        "austin",
    ],
    "dateFrom": "2026-01-01",
    "permitType": "roof",
}

# Run the Actor and wait for it to finish
run = client.actor("darknezz/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",
    "austin"
  ],
  "dateFrom": "2026-01-01",
  "permitType": "roof"
}' |
apify call darknezz/building-permits-scraper --silent --output-dataset

```

## MCP server setup

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