# WA Seattle Building Permits (`j0401/wa-seattle-permits`) Actor

Seattle, WA building permits (public open data, 193k records): the city's construction-permit register - estimated project cost, housing units added/removed + housing-category labels, permit class, and plan-review cycle depth. Filter by type/status/category/cost or aggregate.

- **URL**: https://apify.com/j0401/wa-seattle-permits.md
- **Developed by:** [Wenhao Yang](https://apify.com/j0401) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.10 / 1,000 wa seattle building 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?

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

## WA Seattle Building Permits - The City's Construction-Permit Register

The City of Seattle publishes its **construction-permit register** - every permit the city's construction-permit system has processed since 1986, active and historical - as open data. This actor turns that register into a **charged-per-record lookup and filter tool** that carries the depth the national building-permit aggregators don't: **the housing change each permit creates**, the **estimated project cost**, and **how long the city's plan review actually took**.

**Built for:** construction & development research (where Seattle's housing is being built, at what density, and at what scale), property diligence, contractor / permit-activity lookups by address or zoning, and project-cost benchmarking.

### What it covers

**192,845 permits** - the full register since 1986, live through this week:

| Status | Permits |
|---|---|
| Completed | 133,900 |
| Expired | 13,625 |
| Additional Info Requested | 10,139 |
| Closed | 8,967 |
| Issued | 8,378 |
| Withdrawn | 5,161 |
| ... plus the in-progress pipeline (Reviews In Process / Ready for Issuance / Scheduled) | |

Each permit carries:

- **estimated project cost** - filled on 82% of the file and a *real numeric column*, so you can range-filter it server-side: 70,008 permits at $100k+, 11,040 at $1M+
- **the housing change the permit creates** - housing units / units added / units removed, plus the city's own program labels: **Middle Housing** (19,039), **Large Multifamily** (1,646), Pre-Approved DADU Plans, and the dwelling-unit type (detached single-family, townhouse, accessory dwelling, apartment...) where the city records it
- **permit class** - Residential (134,017) vs Non-Residential (51,913), plus the finer class (Single Family/Duplex, Commercial, Multifamily, Institutional, Industrial)
- **the plan-review cycle depth** - number of plan-review cycles (filled on 94% of the file), and - for the ~33k permits whose application went through a full plan review - **total plan-review days, the review-complete and ready-to-issue dates**. Seattle is the only city feed we've seen that publishes this review-timeline; it's a real signal for how long a permit will sit in review
- the **applied / issued / expires / completed date chain**, permit type (building / demolition / ECA-and-shoreline / roof / grading), zoning designation, street address + **latitude/longitude**, and the work description

### Status & live work

Statuses are the city's own clean words. **Completed** (133,900) is the done pile; the genuinely live lane is the in-progress family - **Reviews In Process**, **Ready for Issuance**, **Scheduled**, **Issued** - plus **Additional Info Requested** (10,139, the city waiting on the applicant).

**Live work** has its own lane: the `recent` mode hands you every permit **issued in the last 30 days**, and the default pull is the **most recently filed** permits, so you're watching the live pipeline (this week's new filings are mostly Addition/Alteration and New work sitting in review) rather than digging through a 193k-row archive.

### Typical questions

- "What's Seattle issuing **this month** - how many permits, and what's the mix?"
- "Every **Middle Housing** / **Large Multifamily** permit in the register - where is the city adding density?"
- "Show recent permits valued over **$1M** still in review."
- "How long did plan review take on that **12-unit building** - how many review cycles?"
- "What's been pulled on **this address / in this zoning**?"
- "Aggregate the register by **housing category** or **permit class**."

### Inputs

| Input | What it does |
|---|---|
| `mode` | `rows` (default) / `recent` (issued last 30 days) / `aggregate` |
| `permitType` | building / demolition / roof / grading / ECA-shoreline |
| `status` | Completed / Issued / Reviews In Process / Ready for Issuance / ... Blank = any |
| `permitClass` / `housingCategory` | Residential/non-residential + the city's housing labels |
| `address` / `zoning` / `contractor` | where the work is (contractor is filled on ~17% of the file) |
| `costMin/To` | estimated project cost range (numeric, filtered server-side) |
| `housingUnitsMin` | minimum housing units carried by the permit (multifamily finder) |
| `issuedFrom/To` / `appliedFrom/To` | date-range filters |
| `planReviewedOnly` | only permits that ran a full plan review (so the review-days fields are populated) |
| `groupBy` | aggregate over type / class / status / housing category |
| `maxResults` | cap records (default 200) |

**Default run = the 200 most recently filed permits** - fast for the daily auto-test. For a targeted query add a filter; for a broad view use `aggregate` (a handful of summary rows, not a 193k-row dump).

### Low cost

**From $0.0001 per record** - billed only for the rows you use, at the platform floor. Cost scales with what you pull, not with the size of the register - and because each record is metered individually there's no per-run charge cap to hit on a big pull.

Seattle's register is a "just download the CSV" dataset that hides real traps: **the filing date is missing on 27% of rows**, and Seattle's engine sorts empty dates *first*, so a naive "newest first" pull hands you a pile of date-less archive rows instead of live work; **the contractor field is filled on only 17% of the file** and there is no honest way to know which rows carry it without counting every column; the **housing density labels and the plan-review timeline each live on only part of the file**, so claiming them as whole-corpus columns over-sells what's there. Normalizing that into a register where a `cost` / `housingUnitsMin` / `recent` / `planReviewedOnly` query returns exactly the permits you mean - newest filings first - is the actual product. Every pull is integrity-checked against the feed's known shape, so a degraded source fails loudly instead of returning bad rows.

### Source

- [City of Seattle Open Data: Building Permits](https://data.seattle.gov/Building-Permits/Building-Permits/76t5-zqzr) - the city's construction-permit register. Reflects the city's record as of each pull; not an endorsement of any contractor or project.

# Actor input Schema

## `mode` (type: `string`):

rows = permits matching your filters (default). recent = permits issued in the last 30 days. aggregate = one count row per group (see groupBy).

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

The permit's mapped type: building, demolition, roof, grading, or ECA/shoreline exemption. Blank = any.

## `status` (type: `string`):

Permit status. Use a source value: Completed, Issued, Expired, Withdrawn, Canceled, Reviews In Process, Ready for Issuance, Additional Info Requested, ... Blank = any. Most of the file is Completed; live work sits in the in-progress family (Reviews In Process / Ready for Issuance / Issued / Scheduled...).

## `permitClass` (type: `string`):

residential / non-residential (the broad split), or free text over the finer class values (e.g. 'Single Family/Duplex', 'Commercial', 'Multifamily').

## `housingCategory` (type: `string`):

The city's housing label where assigned: Middle Housing, Large Multifamily, Single-Family Add/Alt, Commercial Add/Alt, Pre-Approved DADU Plans, Multifamily - Other. Blank = any.

## `address` (type: `string`):

Street number or street name from the permit's address line, e.g. '23RD AVE' or '13519'.

## `contractor` (type: `string`):

Contractor company name (fuzzy). Note: Seattle records the contractor on only ~17% of this register's permits - present when the city has it, not a headline field.

## `zoning` (type: `string`):

Zoning designation on the parcel, e.g. 'SF 5000' or 'LR3' (fuzzy).

## `costMin` (type: `integer`):

Only permits with an estimated project cost at least this amount (the cost column is numeric, so this range-filters on the register). Populated on ~82% of the file.

## `costMax` (type: `integer`):

Only permits with an estimated project cost below this amount.

## `housingUnitsMin` (type: `integer`):

Only permits that carry at least this many housing units (e.g. 5+ = a multifamily-scale project). ~30k permits carry at least one housing unit.

## `issuedFrom` (type: `string`):

Only permits issued on/after this date (YYYY-MM-DD).

## `issuedTo` (type: `string`):

Only permits issued before this date (YYYY-MM-DD).

## `appliedFrom` (type: `string`):

Only permits applied on/after this date.

## `appliedTo` (type: `string`):

Only permits applied before this date.

## `planReviewedOnly` (type: `boolean`):

When on, returns only permits whose application went through plan review, so the review-depth fields (numberReviewCycles, totalDaysPlanReview, planReviewCompleteDate, readyToIssueDate) are populated and meaningful (~33k permits).

## `recentOnly` (type: `boolean`):

When on, returns only permits issued in the last 30 days (the freshest issuances). Same as choosing the 'recent' mode.

## `groupBy` (type: `string`):

Which dimension to aggregate over. status -> the live vs done mix; permitClass -> Residential vs Non-Residential; permitType -> building / demolition / ECA; housingCategory -> the city's housing-program labels.

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

Cap the number of records pushed (0 = up to ~10k per run; each record is metered individually, so there is no per-run charge cap). An aggregate has at most a few hundred groups.

## Actor input object example

```json
{
  "mode": "rows",
  "permitType": "",
  "status": "",
  "permitClass": "",
  "housingCategory": "",
  "address": "",
  "contractor": "",
  "zoning": "",
  "costMin": 0,
  "costMax": 0,
  "housingUnitsMin": 0,
  "issuedFrom": "",
  "issuedTo": "",
  "appliedFrom": "",
  "appliedTo": "",
  "planReviewedOnly": false,
  "recentOnly": false,
  "groupBy": "status",
  "maxResults": 200
}
```

# Actor output Schema

## `recordsUrl` (type: `string`):

Seattle building permit records or aggregates - as JSON

## `datasetUrl` (type: `string`):

No description

## `runUrl` (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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("j0401/wa-seattle-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 = {}

# Run the Actor and wait for it to finish
run = client.actor("j0401/wa-seattle-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 '{}' |
apify call j0401/wa-seattle-permits --silent --output-dataset

```

## MCP server setup

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