# US Building Permits API — 10 Cities, Construction Lead Data (`oneshotventure/permits-search`) Actor

Search recently issued building permits across 10 US cities from official open-data portals: NYC, LA, Chicago, SF, Seattle, Austin, Boston, Philadelphia, New Orleans and San Jose. Permit type, description, address, issue date and status as clean JSON. Built for contractors, suppliers and lead gen.

- **URL**: https://apify.com/oneshotventure/permits-search.md
- **Developed by:** [Nick](https://apify.com/oneshotventure) (community)
- **Categories:** Lead generation, Real estate, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$2.00 / 1,000 results

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

## US Building Permits API — 10 Cities, Construction Lead Data

Search **recently issued building permits across 10 major US cities** from the cities' own official
open-data portals. Permit type, work description, address, issue date and status, normalized into one
schema — so a Seattle permit and a Philadelphia permit land in the same table.

### What does this Actor do?

Every large US city publishes its permit data, and every one of them publishes it differently. New
York calls the permit number `work_permit`; Chicago calls it `permit_`; San Jose shouts it as
`FOLDERNUMBER`. The issue date is `issued_date`, `issue_date`, `issueddate`, `permitissuedate` or
`ISSUEDATE` depending on which city you ask. There is no standard, and that is the actual work this
Actor does: **a hand-written field mapper per city**, so you get one shape instead of ten.

The original portal record is always kept intact in `raw`, so nothing is lost in translation — if a
city publishes a valuation, a contractor name or coordinates that the shared schema has no slot for,
it is still there.

#### Why this instead of a building permits scraper

If you searched for a "building permits scraper", this Actor does that job through the official
route. It reads each city's public open-data API rather than scraping a permit-search web page, so it
cannot be blocked, does not need a browser, and does not break when a city redesigns its site. Cities
publish this data specifically for reuse. That also means no CAPTCHA-solving and no
terms-restricted portals.

### Who is it for?

- **Roofing, HVAC, solar and remodeling contractors** looking for properties with active permitted
  work.
- **Building-materials suppliers and subcontractors** who want to reach a general contractor early in
  a job.
- **Real-estate investors and agents** watching renovation and new-construction activity by
  neighbourhood.
- **Proptech and market-research teams** building a permit dataset across several metros at once.
- **AI agent builders** who need a construction-activity tool with a stable output schema.

### Use cases

- Pull this week's residential remodel permits in two cities and route them to a sales team.
- Track new-construction starts in a metro to time a supplier outreach campaign.
- Filter by keyword (`solar`, `roof`, `pool`) to find only the work types you actually sell into.
- Build a multi-city permit time series for market analysis, keeping each city's original record.
- Give an agent a tool that answers "what construction was permitted at this address recently?"

### What you get

One row per permit. Any value a city does not publish is `null` — the field is always present, and
the untouched source record stays in `raw`.

| Field | Type | Description |
|---|---|---|
| `city` | string | Which city the record came from: `nyc`, `chicago`, `sf`, `austin`, `seattle`, `la`, `nola`, `boston`, `philly`, `sanjose` |
| `permitId` | string | The city's own permit or job-filing number |
| `permitType` | string | Permit or work type as the city classifies it |
| `description` | string | Description of the permitted work |
| `address` | string | Street address, assembled from the city's separate address components |
| `issuedDate` | string | ISO 8601 issue date |
| `status` | string | Permit status as the city publishes it |
| `valuation` | number | null | Broadest project valuation the city publishes, when available |
| `contractor` | string | null | Contractor business name, with person-name fallback where available |
| `applicant` | string | null | Applicant business or organization name, with person-name fallback where available |
| `raw` | object | The complete original record from the city's portal, unmodified |
| `newSinceLastRun` | boolean | Whether this permit is newer than the saved watermark |
| `retrievedAt` | string | ISO 8601 timestamp of when this row was fetched |

#### Sample output record

```json
{
  "city": "seattle",
  "permitId": "7125741-CN",
  "permitType": "Residential",
  "description": "Establish use as housing and construct 4-unit townhouse…",
  "address": "1024 5TH AVE W",
  "issuedDate": "2026-08-21T00:00:00.000Z",
  "status": "Issued",
  "valuation": 524637,
  "contractor": null,
  "applicant": null,
  "raw": { "permitnum": "7125741-CN", "…": "…" },
  "newSinceLastRun": true,
  "retrievedAt": "2026-08-22T14:40:11.204Z"
}
```

### City field coverage

Sources are official city open-data portals. Records can omit fields, so a missing source value is
emitted as `null` while the original record remains available in `raw`.

| City | Valuation | Contractor | Applicant |
| --- | --- | --- | --- |
| Boston | `declared_valuation` | null | `applicant` |
| Philadelphia | null | null | null |
| San Jose | `PERMITVALUATION` | `CONTRACTOR` | `APPLICANT` |
| New York City | `estimated_job_costs` | null | `applicant_business_name`, fallback `applicant_first_name` + `applicant_last_name` |
| Chicago | null | null | null |
| San Francisco | `estimated_cost` | null | null |
| Austin | `total_valuation_remodel` | `contractor_company_name`, fallback `contractor_full_name` | `applicant_org`, fallback `applicant_full_name` |
| Seattle | `estprojectcost` when published | `contractorcompanyname` when published | null |
| Los Angeles | null | null | null |
| New Orleans | null | null | null |

### How to use it

#### Search two cities for a work type

```json
{
  "cities": ["seattle", "austin"],
  "keywords": ["residential", "remodel"],
  "maxResults": 20
}
```

#### Everything issued since a date

```json
{
  "cities": ["nyc", "chicago", "philly"],
  "issuedSince": "2026-08-01T00:00:00.000Z",
  "maxResults": 500
}
```

Keywords are matched case-insensitively against the permit description and type fields.

### Input parameters

| Input | Type | Description |
|---|---|---|
| `cities` | array | Cities to search. Allowed values: `nyc`, `chicago`, `sf`, `austin`, `seattle`, `la`, `nola`, `boston`, `philly`, `sanjose`. Unknown values fail the run with a clear error. Default: `["nyc"]` |
| `issuedSince` | string | ISO 8601 start time. Defaults to the previous successful run's watermark |
| `keywords` | array | Case-insensitive matches against permit description and type fields |
| `maxResults` | integer | Maximum records per run (1–1000). Default: `20` |

### How monitoring works (first run and scheduling)

The first run establishes a baseline and returns the current window of records. To get only-new
records on every subsequent run, **schedule this Actor as a saved Task** so runs share the same
storage — the watermark lives in the task's key-value store. A fresh unsaved run starts a fresh
baseline.

### Honest limitations

- **Ten cities, not the whole country.** There is no national US permit database; each city must be
  integrated individually. The supported list is exactly the ten above.
- **Field coverage varies by city**, as the table shows. Where a city does not publish a value into
  its open dataset, the normalized field is `null` and the original record stays in `raw`.
- **San Jose publishes a rolling last-30-days dataset**, so historical San Jose permits are not
  available from that source.
- **Field coverage varies by city.** Valuation, contractor and applicant are normalized only where
  the source publishes them; the city coverage table identifies each available source field.
- **Portals update on their own schedules.** Some refresh continuously, others in daily batches, so
  "issued yesterday" does not always mean "available this morning".

### Reliability

Every record carries `retrievedAt` and keeps the untouched source record in `raw`, so you can check
any row against the city's own portal yourself. Unknown city codes fail the run immediately with a
clear error rather than silently returning nothing.

Maintained against the upstream portals. If a city changes its dataset schema or moves an endpoint,
report it through the Issues tab and it gets fixed.

### Integrations

Connect this Actor to Make, Zapier, n8n, Slack, Google Sheets, Airtable or any HTTP endpoint through
Apify integrations. A scheduled run can drop new permits straight into a CRM or a spreadsheet, or fire
a webhook so a downstream job starts the moment the data lands. Datasets export as JSON, CSV, Excel,
XML, RSS or HTML.

### API usage

```bash
curl -X POST "https://api.apify.com/v2/acts/oneshotventure~permits-search/runs?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"cities": ["seattle", "austin"], "keywords": ["residential"], "maxResults": 20}'
```

Python, JavaScript, PHP and CLI clients are documented under
[Apify API clients](https://docs.apify.com/api/client).

### Use with AI agents (MCP)

This Actor is callable from any MCP-compatible client — Claude, Cursor, VS Code or your own agent —
through the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp). The input schema is
fully described and every record uses one stable, flat JSON shape, so an agent can call it and read
the result without a parsing step. Because it reads official city open-data APIs rather than a
rendered page, the answer an agent gets is the same one the source publishes.

### Frequently asked questions

#### Where can I get building permit data for free?

From the cities themselves — each of the ten supported cities publishes permits as open data for
public reuse. This Actor is the normalization, filtering and scheduling layer that turns ten
incompatible datasets into one.

#### How do I find new construction leads in my city?

Select your city, set `keywords` to the work types you sell into, and schedule the Actor as a saved
Task. Each run returns the permits issued since the last one.

#### Which cities are supported?

Ten: New York City, Los Angeles, Chicago, San Francisco, Seattle, Austin, Boston, Philadelphia, New
Orleans and San Jose. Passing any other city code fails the run with a clear error rather than
returning nothing.

#### How fresh is the permit data?

Each run reads the city portal live at that moment, so the data is as fresh as the portal itself.
Cities refresh on their own schedules — some continuously, some in daily batches.

#### Can I filter permits by keyword or work type?

Yes. `keywords` is matched case-insensitively against both the permit description and the permit type
fields, so `solar`, `roof` or `demolition` all work.

#### Does this include contractor names and phone numbers?

Where a city publishes one, `contractor` is included in the normalized schema. Coverage varies by
city, and this Actor does not append phone numbers or emails from any third-party source.

#### Does it cover the whole United States?

No. Permits are issued city by city and there is no national register. Ten cities are supported
today; more are added as official open-data endpoints are validated.

### Related actors

- [App Store Review Monitor](https://apify.com/oneshotventure/app-review-watch) — new iOS reviews for
  your app or a competitor's.
- [Business Entity Search API](https://apify.com/oneshotventure/entity-search) — verify a
  contractor's company registration against official registries.
- [US Product Recalls Monitor](https://apify.com/oneshotventure/recalls-monitor) — CPSC and FDA
  recalls, including building products.

### Disclaimer

Unofficial independent tool. Not affiliated with or endorsed by any city government. Data is
retrieved from official public open-data portals.

# Actor input Schema

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

Cities to search. Allowed values: nyc, chicago, sf, austin, seattle, la, nola, boston, philly, sanjose. Unknown values fail the run with a clear error.

## `issuedSince` (type: `string`):

ISO-8601 start time. Defaults to the previous successful run watermark.

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

Case-insensitive matches against permit description and type fields.

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

Maximum records per run (1-1000).

## Actor input object example

```json
{
  "cities": [
    "nyc"
  ],
  "keywords": [],
  "maxResults": 20
}
```

# Actor output Schema

## `records` (type: `string`):

Building permit records in the default dataset.

## `runSummary` (type: `string`):

Per-city diagnostics stored as RUN\_SUMMARY in the key-value store.

# 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": [
        "nyc"
    ],
    "maxResults": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("oneshotventure/permits-search").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": ["nyc"],
    "maxResults": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("oneshotventure/permits-search").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": [
    "nyc"
  ],
  "maxResults": 20
}' |
apify call oneshotventure/permits-search --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,oneshotventure/permits-search"
        }
    }
}

```

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/Vs1xcdPw1wf7NlAEQ/builds/5pJtiaFFlXyBhLLYU/openapi.json
