# US Building Permits Scraper (NYC, Chicago, LA) (`knotty_mistveil/us-building-permits`) Actor

Building permits issued in New York City, Chicago and Los Angeles from the three official city open-data portals: address, work, cost, owner and contractor.

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

## Pricing

$3.00 / 1,000 permit returneds

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 Scraper (NYC, Chicago, LA)

Pulls building permits **as they are issued** from the three official city open-data portals — **NYC Open Data** (Department of Buildings, DOB NOW), the **City of Chicago Data Portal** (Department of Buildings) and the **Los Angeles Open Data Portal** (LADBS) — through their public Socrata SODA APIs. No API key, no login, no browser automation. Three very different schemas come out as **one flat JSON record per permit**: address, permit type, work description, declared job cost, coordinates, and — in New York City and Chicago — the property owner and the licensed contractor who pulled the permit. Filter by city, issue-date window, permit type, work-description keyword, minimum job cost, borough or community area, and whether a contractor is named. The three cities together issue roughly **1,100 to 1,300 permits per weekday**, so size `maxItems` to your window — see [Limits and freshness](#limits-and-freshness).

### What you get

One record per issued permit. Every record carries the same 30 keys whichever city it came from; a field a city does not publish is `null` rather than missing.

| Field | Description |
| --- | --- |
| `id` | `<city>:<permit number>`. New York City appends the tracking number and work type, because DOB NOW publishes one row per work permit, issuance and work type: `nyc:S01436796-I1-SL:361968132:Solar`. |
| `source` | `nyc-open-data`, `chicago-data-portal` or `la-open-data`. |
| `city` | `nyc`, `chicago` or `la` — the same values the `cities` input takes. |
| `cityName` | `New York City`, `Chicago`, `Los Angeles`. |
| `url` | Public link to this one permit on the city's portal (the Socrata data explorer, pre-filtered to the permit number). |
| `permitNumber` | The permit number as the city prints it: `S01436796-I1-SL` (NYC work permit), `101078712` / `B200474019` (Chicago), `26014-20000-02398` (LA). |
| `permitType` | NYC work type (`Solar`, `Plumbing`, `General Construction`, `Sidewalk Shed`); Chicago permit type (`PERMIT - NEW CONSTRUCTION`, `PERMIT - RENOVATION/ALTERATION`); LA permit type (`Bldg-New`, `Bldg-Alter/Repair`, `Swimming-Pool/Spa`). |
| `permitSubtype` | NYC filing reason (`Initial Permit`, `Renewal Permit with Changes`); Chicago review type (`STANDARD PLAN REVIEW`, `EXPRESS PERMIT PROGRAM`); LA permit sub-type (`1 or 2 Family Dwelling`, `Apartment`, `Commercial`). |
| `status` | `Permit Issued` / `Signed-off` (NYC), `ACTIVE` / `COMPLETE` (Chicago), `Issued` / `Permit Finaled` (LA). |
| `workType` | NYC work type; Chicago work type; LA permit group (`Building`, `Grading`, `Sign`). |
| `workDescription` | The scope of work in the city's own words. Populated on 100% of records in all three cities (measured over 3,875 permits, 1–8 September 2026). |
| `issueDate`, `filingDate`, `expirationDate` | ISO `YYYY-MM-DD`. See the per-city gaps under [Limits and freshness](#limits-and-freshness): NYC has no filing date, Chicago and LA no expiration date. |
| `estimatedCost` | The declared job cost / valuation in US dollars. NYC estimated job cost, Chicago reported cost, LA valuation. |
| `address` | One line, always including the city and state: `102 MAPLE PARKWAY, Staten Island, NY 10303`. |
| `borough` | NYC borough (`Manhattan`, `Brooklyn`, `Queens`, `Bronx`, `Staten Island`). `null` in Chicago and LA. |
| `communityArea` | The city's own neighbourhood unit: NYC Neighborhood Tabulation Area (`Williamsburg`), Chicago community area resolved from its number to its name (`Avondale`, `Logan Square`), LA community plan area (`Hollywood`). |
| `zip`, `latitude`, `longitude` | Property location. Chicago publishes no property ZIP (only contact ZIPs), so `zip` is `null` there. |
| `ownerName`, `ownerBusinessName`, `ownerPhone` | Property owner. NYC publishes both a person and a business name; Chicago publishes one owner-contact name, also copied into `ownerBusinessName` when it looks like an organisation; LA publishes neither. **No city publishes a phone number in these datasets, so `ownerPhone` is always `null`** — the field exists so the schema stays stable. |
| `contractorBusinessName`, `contractorName` | The firm that pulled the permit and, in NYC, the named permittee. |
| `contractorLicenseType`, `contractorLicenseNumber` | NYC permittee licence type (`GC`, `PL`, `EW`) and number — verifiable against the DOB licence register. Chicago reports the contact type it published instead (`CONTRACTOR-GENERAL CONTRACTOR`) and no number. |
| `extra` | An object of city-specific fields: NYC BIN/BBL/block/lot, community board, council district, filing representative, contractor business address; Chicago ward, community-area number, permit milestone, the full fee breakdown and **every contact slot** (`extra.contacts`, up to 15 per permit, with type, name, city, state, ZIP); LA zone, assessor parcel number, use code, neighbourhood council, council district, square footage, solar/EV flags. Any column a city adds later also lands here rather than being dropped. |
| `fetchedAt` | ISO timestamp of the run that produced the record. |

The dataset has three views: **Overview** (date, city, permit, type, work, address, area, cost, link), **Owners and contractors** (the lead-generation columns) and **Locations** (address, ZIP, coordinates).

**Measured field coverage** over one full unfiltered week (3,875 permits issued 1–8 September 2026):

| | New York City (2,280) | Chicago (537) | Los Angeles (1,058) |
| --- | --- | --- | --- |
| `contractorBusinessName` | 100% | 97.4% | 0% (not published) |
| `contractorLicenseNumber` | 100% | 0% (not published) | 0% (not published) |
| `ownerName` | 98.8% | 97.6% | 0% (not published) |
| `estimatedCost` | 100% | 87.3% | 100% |
| `latitude` / `longitude` | 99.6% | 98.9% | 100% |
| `communityArea` | 99.6% | 99.3% | 99.9% |
| `zip` | 100% | 0% (not published) | 99.9% |

### Input

| Input | Type | Default | Description |
| --- | --- | --- | --- |
| `cities` | string\[] | all three | `nyc`, `chicago`, `la`. Records are taken **city by city in rotation**, so `maxItems` splits roughly evenly between the cities you select. |
| `daysBack` | integer | `7` | Look-back window on the permit **issue date**, counted back from the run. Issue dates are calendar dates, so 7 means today plus the seven previous days. Ignored when `dateFrom`/`dateTo` are set. Keep it at 7 or more: NYC runs about two business days behind, so a shorter window can return no NYC permits at all. |
| `dateFrom` | string | – | Earliest issue date, `YYYY-MM-DD`, inclusive. Overrides `daysBack`. |
| `dateTo` | string | today | Latest issue date, `YYYY-MM-DD`, inclusive. |
| `permitTypes` | string\[] | all | Case-insensitive substrings matched against `permitType`, `permitSubtype` and `workType`; any match keeps the permit. Each city has its own vocabulary, and short substrings such as `"new"` or `"demolition"` cut across all three. |
| `workKeywords` | string\[] | none | Case-insensitive substrings matched against `workDescription`, `workType` and `permitType`. This is the trade filter: `"roof"`, `"solar"`, `"kitchen"`, `"pool"`, `"adu"`, `"hvac"`, `"sprinkler"`. |
| `minValuation` | integer | `0` | Keep only permits whose `estimatedCost` is at least this many US dollars. Permits with no published cost are dropped when this is above 0 — that removes about 13% of Chicago permits. |
| `boroughsOrAreas` | string\[] | all | Case-insensitive substrings matched against `borough`, `communityArea`, `zip`, ward, council district and community board. `"Brooklyn"`, `"Logan Square"`, `"Hollywood"`, `"11215"`. |
| `contractorsOnly` | boolean | `false` | Keep only permits that name a contractor or permittee business. **Los Angeles publishes none, so switching this on removes every LA permit** — the run logs a warning when you do it with `la` selected. |
| `socrataAppToken` | string (secret) | – | Optional free Socrata app token, sent as `X-App-Token`. Not required; it only lifts the shared per-IP throttle. One token works on all three portals. |
| `maxItems` | integer | `500` | Stop after this many permits. You are charged per permit delivered, so this is also the cost cap. Permits come newest-first within each city, so a value below the window's volume drops the **oldest** permits in each city. |

Roofing, solar and HVAC leads over $20,000 in the last fortnight, contractor named:

```json
{
  "daysBack": 14,
  "workKeywords": ["roof", "solar", "hvac", "boiler"],
  "minValuation": 20000,
  "contractorsOnly": true,
  "cities": ["nyc", "chicago"],
  "maxItems": 1000
}
```

New residential construction in Brooklyn and Queens:

```json
{
  "cities": ["nyc"],
  "daysBack": 30,
  "permitTypes": ["General Construction", "Foundation"],
  "boroughsOrAreas": ["Brooklyn", "Queens"],
  "minValuation": 100000,
  "maxItems": 2000
}
```

The complete unfiltered feed, scheduled daily (the overlapping window absorbs each city's publication lag, and 8000 comfortably covers what a week produces):

```json
{ "daysBack": 7, "maxItems": 8000 }
```

An unknown city, a malformed date or a negative `minValuation` fails fast: the run stops with the validation message as its status before a single request is made.

### Output example

A real New York City record from a live run on 2026-09-08, `extra` trimmed:

```json
{
  "id": "nyc:S01436796-I1-SL:361968132:Solar",
  "source": "nyc-open-data",
  "city": "nyc",
  "cityName": "New York City",
  "url": "https://data.cityofnewyork.us/d/rbx6-tga4/explore/query/SELECT%20*%20WHERE%20work_permit%20%3D%20'S01436796-I1-SL'/page/filter",
  "permitNumber": "S01436796-I1-SL",
  "permitType": "Solar",
  "permitSubtype": "Initial Permit",
  "status": "Permit Issued",
  "workType": "Solar",
  "workDescription": "FILING HEREWITH TAX ABATEMENT APPLICATION FOR INSTALLATION OF ROOF-MOUNTED SOLAR PANELS. NO CHANGE IN USE, EGRESS, OR OCCUPANCY.",
  "issueDate": "2026-09-03",
  "filingDate": null,
  "expirationDate": "2026-10-01",
  "estimatedCost": 18000,
  "address": "102 MAPLE PARKWAY, Staten Island, NY 10303",
  "borough": "Staten Island",
  "communityArea": "Mariner's Harbor-Arlington-Graniteville",
  "zip": "10303",
  "latitude": 40.631048,
  "longitude": -74.156477,
  "ownerName": "Adeel Ahmad",
  "ownerBusinessName": "Not Applicable",
  "ownerPhone": null,
  "contractorBusinessName": "SUNRUN INSTALLATION SVC",
  "contractorName": "PAUL JOYCE",
  "contractorLicenseType": "GC",
  "contractorLicenseNumber": "613329",
  "extra": {
    "jobFilingNumber": "S01436796-I1",
    "sequenceNumber": "1",
    "trackingNumber": "361968132",
    "approvedDate": "2026-08-31",
    "bin": "5171438",
    "block": "1218",
    "lot": "38",
    "bbl": "5012180038",
    "communityBoard": "501",
    "councilDistrict": "49",
    "workOnFloor": "Roof",
    "contractorBusinessAddress": "775 FIERO LN STE 200",
    "filingRepresentativeName": "Lisa Marie V Stavola",
    "filingRepresentativeBusinessName": "Sunrun"
  },
  "fetchedAt": "2026-09-08T02:25:56.441Z"
}
```

A Chicago permit from the same run (fields common to both cities omitted; note the contact array, which is where Chicago hides everyone involved):

```json
{
  "id": "chicago:B200474019",
  "source": "chicago-data-portal",
  "city": "chicago",
  "url": "https://data.cityofchicago.org/d/ydr8-5enu/explore/query/SELECT%20*%20WHERE%20permit_%20%3D%20'B200474019'/page/filter",
  "permitNumber": "B200474019",
  "permitType": "PERMIT – EXPRESS PERMIT PROGRAM",
  "permitSubtype": "EXPRESS PERMIT PROGRAM",
  "status": "ACTIVE",
  "workType": "Storm Water Management Plan",
  "issueDate": "2026-09-06",
  "filingDate": "2026-09-06",
  "expirationDate": null,
  "estimatedCost": null,
  "address": "5275 S ARCHER AVE, Chicago, IL",
  "communityArea": "Garfield Ridge",
  "zip": null,
  "ownerName": "CASIMIRO ROBERTO",
  "extra": {
    "permitMilestone": "POST CONSTRUCTION FILING",
    "totalFee": 3000,
    "communityAreaNumber": 56,
    "ward": "23",
    "contacts": [
      { "type": "OWNER", "name": "CASIMIRO ROBERTO", "city": "CHICAGO", "state": "IL", "zipcode": "60632" },
      { "type": "WEB APPLICANT", "name": "GARCIA JUSTIN", "city": "ROSEMONT", "state": "IL", "zipcode": "60018" },
      { "type": "PROFESSIONAL ENGINEER", "name": "MCGOVERN SEAN H", "city": "CHICAGO", "state": "IL", "zipcode": "60657" }
    ]
  }
}
```

And a Los Angeles permit — no owner or contractor, but a full valuation, parcel number and zoning:

```json
{
  "id": "la:26014-20000-02398",
  "source": "la-open-data",
  "city": "la",
  "url": "https://data.lacity.org/d/pi9x-tg5x/explore/query/SELECT%20*%20WHERE%20permit_nbr%20%3D%20'26014-20000-02398'/page/filter",
  "permitNumber": "26014-20000-02398",
  "permitType": "Bldg-Alter/Repair",
  "permitSubtype": "1 or 2 Family Dwelling",
  "status": "Issued",
  "workType": "Building",
  "workDescription": "ePlanConvert portion of the front porch to part of the (E) SFD and enlarge (E) kitchen. All work per engineer.",
  "issueDate": "2026-09-05",
  "filingDate": "2026-06-29",
  "estimatedCost": 45000,
  "address": "6029 N NEDDY AVE, Los Angeles, CA 91367",
  "communityArea": "Canoga Park-West Hills-Winnetka-Woodland Hills",
  "zip": "91367",
  "latitude": 34.18066,
  "longitude": -118.65271,
  "contractorBusinessName": null,
  "extra": {
    "useDescription": "Dwelling - Single Family",
    "zone": "RE11-1",
    "assessorParcelNumber": "2045004004",
    "neighbourhoodCouncil": "WOODLAND HILLS-WARNER CENTER NC",
    "councilDistrict": "3",
    "businessUnit": "Plan Check at Counter",
    "solar": "N"
  }
}
```

The run also writes a `SUMMARY` record to the key-value store with the rows scanned, kept and filtered per city, the newest issue date each city had reached, the resolved window, and how many permits each filter removed.

### Pricing

Pay per result: **US$0.003 per permit delivered** (US$3 per 1,000 permits). You are charged only for records pushed to the dataset; a run that returns nothing costs nothing. A full unfiltered week across all three cities is about 3,900 permits, so about US$11.60. Runs on the Apify free plan are limited by the platform's free-tier usage allowance.

### Data source and licence

All three sources are Socrata SODA 2.1 endpoints and all were read on 8 September 2026.

- **New York City** — *DOB NOW: Build – Approved Permits*, NYC Department of Buildings: `GET https://data.cityofnewyork.us/resource/rbx6-tga4.json`. Dataset page: <https://data.cityofnewyork.us/d/rbx6-tga4>. Published under NYC Open Data, whose enabling law (New York City Local Law 11 of 2012, Admin. Code § 23-502(d)) requires datasets to be available "without registration requirement, license requirement, or usage restrictions". Terms of use: <https://www.nyc.gov/home/terms-of-use.page>. Commercial reuse is permitted; the City warrants nothing about completeness or accuracy. Attribute to the NYC Department of Buildings via NYC Open Data.
- **Chicago** — *Building Permits*, Chicago Department of Buildings: `GET https://data.cityofchicago.org/resource/ydr8-5enu.json`. Dataset page: <https://data.cityofchicago.org/d/ydr8-5enu>. Licence field on the dataset: "See Terms of Use", attribution "City of Chicago". Data Terms of Use: <https://www.chicago.gov/city/en/narr/foia/data_disclaimer.html>. Commercial reuse is permitted, but the terms **require** any secondary or derivative application built on the data to display this disclaimer where it can be accessed: *"This site provides applications using data that has been modified for use from its original source, www.cityofchicago.org, the official website of the City of Chicago. The City of Chicago makes no claims as to the content, accuracy, timeliness, or completeness of any of the data provided at this site. The data provided at this site is subject to change at any time. It is understood that the data provided at this site is being used at one's own risk."* If you publish anything on top of these records, reproduce that notice.
- **Los Angeles** — *Building and Safety – Building Permits Issued from 2020 to Present*, Los Angeles Department of Building and Safety: `GET https://data.lacity.org/resource/pi9x-tg5x.json`. Dataset page: <https://data.lacity.org/d/pi9x-tg5x>. Terms of use: <https://data.lacity.org/terms-of-use>. The terms place no licence or restriction on reuse or redistribution of the published datasets; everything is supplied "as is" without warranty. Attribute to LADBS via the Los Angeles Open Data Portal.
- **Socrata app tokens** — optional, free, and only relevant to rate limits: <https://dev.socrata.com/docs/app-tokens.html>.

#### Personal data

These records are **public records that name real people**. `ownerName`, `ownerBusinessName`, `contractorName`, `contractorBusinessName`, `contractorLicenseNumber`, `extra.filingRepresentativeName`, `extra.contractorBusinessAddress` and every entry in `extra.contacts` come straight from the permit application. Each city publishes them lawfully under its own open-data programme — building permits are public documents — but *your* use of them is your responsibility:

- **No phone numbers or email addresses are included.** None of the three datasets publishes them, so `ownerPhone` is always `null` and there is no contact-email field at all. If you enrich these leads with phone numbers from elsewhere, US telemarketing law applies to the calls and texts you then make: the **TCPA** (47 U.S.C. § 227) and its FCC rules require prior express written consent for autodialled or pre-recorded marketing calls and for marketing texts, and require you to scrub against the National Do Not Call Registry. A permit record is not consent.
- Commercial email to these people is covered by **CAN-SPAM** (15 U.S.C. § 7701 et seq.): accurate headers and subject lines, a physical postal address, and a working opt-out honoured within ten business days.
- Individual owner names in NYC and Chicago identify natural persons. Californian residents in the LA feed are not affected here — LADBS publishes no names — but if you combine this output with other sources, the **CCPA/CPRA** may apply to the combined profile.
- Do not republish the owner and contractor names as a standalone directory; that is not what the cities publish them for, and it is the fastest route to a complaint.

### Limits and freshness

**Volumes, measured 8 September 2026 by exhausting the window.** One unfiltered run over 1–8 September 2026 returned **3,875 permits** — NYC 2,280, Chicago 537, LA 1,058 — in **26 seconds** and six API requests. Row counts from the portals' own `count(*)` matched the run exactly (2,282 / 537 / 1,058 rows scanned), so the window is read in full. Over the whole of August 2026 the three cities issued 15,067 (NYC), 3,044 (Chicago) and 5,566 (LA) permits — about **1,100 to 1,300 per weekday** combined. Weekends and public holidays are near-empty in all three cities: on 22–23 August 2026 Chicago issued 10 and 8 permits against a weekday average of about 140.

**Freshness and publication lag, measured 8 September 2026** (the Monday was Labor Day):

| City | Newest issue date available | Lag | Committed refresh |
| --- | --- | --- | --- |
| Chicago | 2026-09-06 (Sunday) | same day | daily |
| Los Angeles | 2026-09-05 (Saturday) | ~1 business day | weekly (observed daily) |
| New York City | 2026-09-03 (Thursday) | ~2 business days | daily |

**This is why `daysBack` defaults to 7 and why the cities are read round-robin rather than merged newest-first.** A strict newest-first merge would spend the whole of `maxItems` on Chicago and LA and could return no NYC permits at all. Taking one permit from each selected city in rotation guarantees every city is represented; within a city, permits still come newest-first, so a `maxItems` below the window's volume drops that city's **oldest** permits. A `daysBack` of 3 is genuinely likely to return zero NYC permits — that is the source's lag, not a defect in the Actor.

**Which NYC dataset, and what is left out.** New York City runs two permit systems. This Actor reads **DOB NOW: Build – Approved Permits** (`rbx6-tga4`), the current one. The legacy BIS feed, *DOB Permit Issuance* (`ipu4-2q9a`), is still refreshed but is now a small tail: it recorded **459** permits issued in August 2026 against DOB NOW's **15,067**, i.e. about **3%** of NYC issuances, and it stores every date as free text (`04/21/2014`), which no SoQL date filter can select on. It is not read. If you need pre-DOB-NOW history, query `ipu4-2q9a` directly.

**Fields no city publishes, so no filter can recover them.**

- **Los Angeles publishes no owner and no contractor** in its permit dataset — LADBS removed those columns when it replaced the old "Building and Safety Permit Information" dataset. `ownerName`, `contractorBusinessName` and the licence fields are `null` on 100% of LA records, and `contractorsOnly: true` therefore removes every LA permit. The run warns you when both are set together.
- **No phone numbers anywhere.** `ownerPhone` is always `null`.
- **NYC has no filing date** in DOB NOW; the approval date is in `extra.approvedDate` instead. **Chicago and LA publish no expiration date**; LA's `extra.statusDate` is the nearest equivalent.
- **Chicago publishes no property ZIP** (the ZIPs in `extra.contacts` belong to the contacts, not the building) and no reported cost on about 13% of permits.
- **Chicago does not say whether a contact is a person or a company.** `ownerName` is always the raw owner-contact name; it is copied into `ownerBusinessName` only when the string carries a company marker (LLC, Inc, Corp, Trust, Group, `&`, and so on). That is a documented heuristic, not source data.

**Rate limits and paging.** The SODA API is read in pages of 1,000 rows ordered `issue date DESC, :id`, with a 500 ms pause between pages of the same city and a ceiling of 200 pages per city. Without an app token Socrata throttles by IP from a shared pool and answers `429` when you exceed it; `fetchWithRetry` honours `Retry-After` and backs off exponentially on 429 and 5xx. A default run needs one to three requests per city, so the throttle is not reachable; supply `socrataAppToken` only if you are pulling months of history in a tight schedule. An invalid token makes the portals answer `403` — the run then fails with the HTTP error rather than silently returning nothing.

**Duplicate rows.** DOB NOW carries a handful of byte-identical duplicate rows (14 groups in the 64,327 permits issued 1 May – 8 September 2026, about 0.02%). They collapse into one record and the count appears as `filteredOut.duplicate` in the `SUMMARY`. The week-long run above dropped exactly 2.

**Scope.** These are *building* permits. LADBS publishes electrical and mechanical permits in separate datasets that this Actor does not read; Chicago's and NYC's electrical, plumbing and elevator permits are inside the same datasets and are returned. Dates are the cities' own calendar dates with no timezone conversion. Costs are the applicant's declared figure, not an appraisal, and are not inflation-adjusted.

### Use cases

- **Contractor lead generation.** A daily feed of new roofing, solar, HVAC, pool or kitchen jobs in your ZIP codes, filtered by minimum job cost, with the property address and — in NYC and Chicago — the owner and the general contractor already on the job.
- **Construction market intelligence.** Track permit volume and declared value by city, community area and permit type; spot which firms are pulling the most permits, where, and at what size, using `contractorBusinessName` and `contractorLicenseNumber`.
- **Real-estate and proptech data.** Attach live renovation, addition and new-build activity to a property (NYC BBL/BIN, Chicago PIN, LA assessor parcel number are all in `extra`) for valuation models, off-market sourcing or insurance underwriting.

### Support

Open an issue on the Actor's Issues tab on Apify Store. Replies within 14 days. Bug reports that name the run id and the input used are fixed fastest.

# Actor input Schema

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

Which city open-data portals to read. Default: all three. Records are interleaved city by city, so Max results splits roughly evenly between the cities you select.

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

Look-back window on the permit ISSUE date, counted back from the run. Because issue dates are calendar dates, 7 means today plus the seven previous days. Ignored when Date from / Date to are set. Keep this at 7 or more: New York City publishes about two business days behind, so a shorter window can return no NYC permits at all.

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

Earliest issue date, YYYY-MM-DD (inclusive). Overrides Days back.

## `dateTo` (type: `string`):

Latest issue date, YYYY-MM-DD (inclusive). Defaults to the day the run starts.

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

Case-insensitive substrings matched against permitType, permitSubtype and workType; a permit is kept when any term matches. Each city uses its own vocabulary: NYC "Plumbing", "General Construction", "Solar", "Sidewalk Shed"; Chicago "PERMIT - NEW CONSTRUCTION", "PERMIT - RENOVATION/ALTERATION", "PERMIT - SIGNS"; Los Angeles "Bldg-New", "Bldg-Addition", "Bldg-Alter/Repair", "Swimming-Pool/Spa". Short substrings such as "new" or "demolition" match across all three. Empty = every type.

## `workKeywords` (type: `array`):

Case-insensitive substrings matched against workDescription, workType and permitType; a permit is kept when any term matches. Use it for trade targeting: "roof", "solar", "kitchen", "pool", "adu", "hvac", "sprinkler". Empty = no keyword filter.

## `minValuation` (type: `integer`):

Keep only permits whose declared job cost (estimatedCost, in US dollars) is at least this much. Permits with no published cost are dropped when this is set above 0. 0 = no minimum.

## `boroughsOrAreas` (type: `array`):

Case-insensitive substrings matched against borough (NYC), communityArea (NYC neighbourhood, Chicago community area, LA community plan area), zip, ward, council district and community board. Examples: "Brooklyn", "Manhattan", "Logan Square", "Hollywood", "11215". Empty = the whole of every selected city.

## `contractorsOnly` (type: `boolean`):

Keep only permits that name a contractor or permittee business. NYC and Chicago publish one; Los Angeles does not, so switching this on removes every Los Angeles permit.

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

Optional free Socrata application token, sent as the X-App-Token header. Not required: the Actor works without one. A token lifts the shared per-IP throttle, which matters only for very large windows. Register at https://evergreen.data.socrata.com/profile/app\_tokens (one token works on all three portals).

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

Stop after this many permits. You are charged per permit delivered, so this is also the cost cap. Permits are taken city by city in rotation and newest-first within each city, so a value below the window's volume drops the OLDEST permits in each city. All three cities together issue roughly 1,100 to 1,300 permits per weekday, so a gap-free 7-day window needs about 8000.

## Actor input object example

```json
{
  "cities": [
    "nyc",
    "chicago",
    "la"
  ],
  "daysBack": 7,
  "permitTypes": [],
  "workKeywords": [],
  "minValuation": 0,
  "boroughsOrAreas": [],
  "contractorsOnly": false,
  "maxItems": 500
}
```

# Actor output Schema

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

All building permits returned by the run, one JSON object each. Append ?format=csv or ?format=xlsx to download a spreadsheet.

## `summary` (type: `string`):

Counts and newest issue date per city, the date window used, how many permits each filter removed, and the dataset each city was read from, written at the end of the run.

# 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",
        "chicago",
        "la"
    ],
    "daysBack": 7,
    "permitTypes": [],
    "workKeywords": [],
    "minValuation": 0,
    "boroughsOrAreas": [],
    "maxItems": 500
};

// Run the Actor and wait for it to finish
const run = await client.actor("knotty_mistveil/us-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 = {
    "cities": [
        "nyc",
        "chicago",
        "la",
    ],
    "daysBack": 7,
    "permitTypes": [],
    "workKeywords": [],
    "minValuation": 0,
    "boroughsOrAreas": [],
    "maxItems": 500,
}

# Run the Actor and wait for it to finish
run = client.actor("knotty_mistveil/us-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 '{
  "cities": [
    "nyc",
    "chicago",
    "la"
  ],
  "daysBack": 7,
  "permitTypes": [],
  "workKeywords": [],
  "minValuation": 0,
  "boroughsOrAreas": [],
  "maxItems": 500
}' |
apify call knotty_mistveil/us-building-permits --silent --output-dataset

```

## MCP server setup

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