# NYC DOB Permits - Building Permits & Contractor Leads (`j0401/nyc-dob-permits`) Actor

NYC building work permits (public open data, 1M permits, daily): applicant contractor and licence number, type, estimated job cost, owner, work description, filing reason, plus BBL / BIN / block / lot / ZIP / community board and coordinates. Filter by borough, trade, status or date.

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

## Pricing

from $0.06 / 1,000 nyc dob 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.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## NYC DOB Permits - Building Permits & Contractor Leads

Every building work permit issued by the New York City Department of Buildings, **1,001,125** of them, current through this week.

Each permit names the contractor who pulled it, their licence number and licence class, the estimated cost of the job, the property owner, the full scope of work, and the exact tax lot it covers. It is the freshest public record of construction activity in the city - which is why it is the first thing roofing, solar, HVAC, GC and insurance teams pull.

### Low cost

**From $0.00006 per record** Pay-per-event: you are charged per record delivered, and nothing for the query.

### What you get

| Field | Meaning |
|---|---|
| `jobFilingNumber` / `workPermit` / `sequenceNumber` | The job, and the individual work permit under it |
| `workType` | Trade - `General Construction` 209,703 / `Plumbing` 154,206 / `Sidewalk Shed` 110,427 / `Solar` 21,236 and 17 more |
| `permitStatus` | `Permit Issued` 361,957 / `Signed-off` 639,168 |
| `filingReason` | `Initial Permit` 679,994 / `Renewal Permit Without Changes` 258,355 / `Renewal Permit with Changes` 55,713 |
| `applicantBusinessName` / `applicantLicense` / `licenseType` | The contractor - name, licence number, and class (`GC` 717,284 / `P` 163,355 / `F` 68,860) |
| `estimatedJobCosts` | Estimated job value - populated on **99.99%** of permits (text at the source, delivered for you to rank on) |
| `ownerName` / `ownerBusinessName` | Property owner |
| `jobDescription` | The scope of work, in the filer's own words |
| `issuedDate` / `approvedDate` / `expiredDate` | Issue, approval and expiry |
| `houseNumber` / `streetName` / `borough` / `zipCode` | The address |
| `bbl` / `bin` / `block` / `lot` | The tax lot and building |
| `communityBoard` / `councilDistrict` / `censusTract` / `nta` | Administrative geography |
| `latitude` / `longitude` | Coordinates (99.4%) |

### Modes

- **`search`** (default) - permits matching your filters.
- **`permit`** - one work permit number, or **every row under one job filing**.
- **`aggregate`** - one count row per group: by trade, borough, status, filing reason, licence type, ZIP, community board, council district or issue year.

### Examples

**New solar permits in Brooklyn** - `workType=Solar`, `borough=Brooklyn`:

```
2026-09-16  204 SOUTH 1 STREET, Brooklyn       BEST ENERGY POWER 2015 LL   238117
2026-09-16  737 TROY AVENUE, Brooklyn          CENTURION SOLAR ENERGY       22400
2026-09-16  543 CHRISTOPHER AVENUE, Brooklyn   TOM PETERSEN ARCHITECT, LLC  31668
2026-09-15  1475 EAST 89 STREET, Brooklyn      SOLAR CONTRACTING LLC        56000
2026-09-15  527 EAST 38 STREET, Brooklyn       SOLAR PRO INC                33499.98
```

**Big jobs filed this week** - `recentDays=7`, then sort on `estimatedJobCosts`. The city stores the figure as text, so it is delivered on every record for you to rank on rather than filtered for you.

**Everything filed against one property** - `bbl=1010930129` returns every permit ever issued on that tax lot.

**Every permit under one job** - `mode=permit`, `jobFilingNumber=M08004166-I1`. Jobs routinely run past fifty rows (this one is 87; the largest seen is 323), so nothing is cut off - the run reports `truncated` if a job ever exceeded the window.

**Which trades are busiest this year** - `mode=aggregate`, `groupBy=workType`.

**Permit volume by borough** - `mode=aggregate`, `groupBy=borough`:

```
Manhattan      389,755        Bronx           96,800
Brooklyn       261,648        Staten Island   57,804
Queens         195,118
```

### One row is a permit event, not a job

The **1,001,125** rows cover **579,647** distinct job filings - **1.73 rows per job**. A single job raises many permit rows, and one work permit number recurs across trades, so there is no one column that identifies a row on its own.

That distinction matters when you size a market. "A million building permits" would overstate the number of jobs by roughly 70%. Counted as permit events, this file is exactly that; counted as jobs, it is 579,647. `mode=permit` with a `jobFilingNumber` shows the difference directly - you get every row under that one job.

### Source

> **Counts below are a live snapshot** - the feed is refreshed most days, so exact figures move between reads. The order of magnitude and the ratios are stable.

New York City Department of Buildings, published on `data.cityofnewyork.us` as [DOB NOW: Build - Approved Permits](https://data.cityofnewyork.us/d/rbx6-tga4) - public open data, no login and no key. The feed adds roughly **166,000 permits a year**.

Two things about this file are worth knowing before you rely on a field.

**The borough column is spelled two ways.** The city stores each of the five boroughs in both uppercase and title case - `MANHATTAN` on 326,478 rows and `Manhattan` on another 63,277, and the same split across Brooklyn, Queens, the Bronx and Staten Island. Filtering the raw column for Manhattan silently loses about a sixth of Manhattan. Boroughs are normalised here, so `borough=Manhattan` returns all 389,755.

**There is no revoked or expired status.** The feed carries exactly two - `Signed-off` and `Permit Issued`. A permit that was revoked is not marked as such; it is absent or still shows as issued. Treat a permit record as evidence that a permit was filed and issued, not as a statement about the current state of the work.

One more: a permit's `expiredDate` is legitimately in the future, often by a year or more. It is an expiry, not a lapse.

### Output

Every record carries the same key set regardless of mode - the permit fields plus `recordType`, and the aggregate columns (`groupKey`, `groupCount`, `groupBy`) which are `""` outside aggregate mode.

### Related actors

- **NYC DCWP Licenses** - the city's business-licence register, keyed on business unique id.
- **NYC Property Valuation** - assessed values and the five-phase valuation trail for these same tax lots.
- **Chicago Permits** - the same idea for Chicago.

# Actor input Schema

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

search = permits matching your filters (default). permit = one work permit number, or every row under one job filing (needs workPermit or jobFilingNumber). aggregate = one count row per group (see groupBy).

## `keyword` (type: `string`):

Full-text match inside the work description, e.g. 'solar', 'boiler', 'sprinkler'. Blank = any.

## `borough` (type: `string`):

One of the five boroughs. Manhattan 389,755, Brooklyn 261,648, Queens 195,118, Bronx 96,800, Staten Island 57,804. Blank = all.

## `workType` (type: `string`):

Exact trade (21 values). General Construction 209,703, Plumbing 154,206, Sidewalk Shed 110,427, Solar 21,236. Blank = any.

## `permitStatus` (type: `string`):

The feed carries exactly two statuses: Permit Issued 361,957 and Signed-off 639,168. Blank = any.

## `filingReason` (type: `string`):

Initial Permit 679,994, Renewal Permit Without Changes 258,355, Renewal Permit with Changes 55,713. Use Initial Permit to isolate first-time work. Blank = any.

## `licenseType` (type: `string`):

Applicant's licence class. GC (general contractor) 717,284, P (plumbing) 163,355, F (fire suppression) 68,860, S (sign) 26,606, R (rigging) 14,195, PE 4,583. Blank = any.

## `applicantBusiness` (type: `string`):

Applicant business name substring, e.g. 'SOLAR', 'BUILDERS'. Names are passed through exactly as the source records them. Blank = any.

## `ownerName` (type: `string`):

Property owner name substring. Populated on 98.8% of permits. Blank = any.

## `zipCode` (type: `string`):

Five-digit ZIP, e.g. '10022' (22,241 permits), '11201', '10011'. Blank = any.

## `bbl` (type: `string`):

Exact 10-digit BBL, e.g. '1010930129'. Identifies one tax lot, so it pulls every permit ever filed against that property. Blank = any.

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

Permit issue date on or after this date (YYYY-MM-DD) - the daily stream of newly issued work. Blank = any.

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

Permit issue date on or before this date (YYYY-MM-DD). Blank = any.

## `recentDays` (type: `integer`):

Shortcut for fresh permits, e.g. 7 returns permits issued in the last week. Cannot be combined with issuedFrom / issuedTo. 0 = off.

## `workPermit` (type: `string`):

Exact work permit number for mode=permit, e.g. 'S00943521-S6-PL'.

## `jobFilingNumber` (type: `string`):

Exact job filing number for mode=permit, e.g. 'S00943521-S6'. Returns every row raised under that job.

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

Which dimension to aggregate over (mode=aggregate). Blank = workType. Every group is returned - aggregate mode is not cut off by maxResults.

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

Cap the number of records pushed in search mode (0 = default 50; up to 10,000 per run). Each record is metered individually, so there is no per-run charge cap. Aggregate mode returns every group.

## Actor input object example

```json
{
  "mode": "search",
  "keyword": "",
  "borough": "",
  "workType": "",
  "permitStatus": "",
  "filingReason": "",
  "licenseType": "",
  "applicantBusiness": "",
  "ownerName": "",
  "zipCode": "",
  "bbl": "",
  "issuedFrom": "",
  "issuedTo": "",
  "recentDays": 0,
  "workPermit": "",
  "jobFilingNumber": "",
  "groupBy": "",
  "maxResults": 50
}
```

# Actor output Schema

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

NYC DOB 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/nyc-dob-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/nyc-dob-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/nyc-dob-permits --silent --output-dataset

```

## MCP server setup

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