# NYC DOB Permits & Violations Scraper (`crawlerbros/nyc-dob-permits-violations-scraper`) Actor

Scrape NYC Department of Buildings (DOB) construction permits and code violations from the public NYC Open Data API. Filter by borough, address, BIN, block/lot, job/permit type, permit status, violation category, and date range.

- **URL**: https://apify.com/crawlerbros/nyc-dob-permits-violations-scraper.md
- **Developed by:** [Crawler Bros](https://apify.com/crawlerbros) (community)
- **Categories:** Real estate, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## NYC DOB Permits & Violations Scraper

Scrape **NYC Department of Buildings (DOB)** construction permit and code violation records straight from the public NYC Open Data API. Search or filter by borough, address, Building Identification Number (BIN), tax block/lot, job/permit type, permit status, violation category, device number, and date range — or look up exact records by job number or violation number. HTTP-only via the public `data.cityofnewyork.us` Socrata API. No login, no cookies, no proxy required.

### What this actor does

- **Four modes:** `permits`, `permitsByJob`, `violations`, `violationsByNumber`
- **Two datasets:** DOB Permit Issuance (~4 million permits) and DOB Violations (~2.5 million violations)
- **Rich filters:** borough, address (house number + street), BIN, block/lot, ZIP code, job type, permit type, permit subtype, work type, permit status, filing status, permittee license type, owner business type, residential-only, self-certified-only, non-profit-owner-only, violation category, violation type code, device number, ECB number, date range (by filing/issuance/expiration/job-start date for permits, or by issue/disposition date for violations), and free-text keyword search
- **Derived fields:** canonical `address`, a computed `bbl` (Borough-Block-Lot) for violations, latitude/longitude for permits, zoning special districts for permits
- **Empty fields are omitted** — a record never contains `null`, `""`, or empty arrays

### Output: per permit (mode = `permits` / `permitsByJob`)

- `recordType: "permit"`, `scrapedAt`
- `borough`, `houseNumber`, `streetName`, `address`, `bin`, `block`, `lot`, `bbl`
- `zipCode`, `communityBoard`, `councilDistrict`, `censusTract`, `ntaName`, `specialDistrict1`, `specialDistrict2`
- `jobNumber`, `jobDocNumber`, `jobType`, `permitType`, `permitSubtype`, `permitSequence`
- `permitStatus`, `filingStatus`, `workType`, `buildingType`
- `residential`, `selfCertified`, `ownerNonProfit` — booleans
- `siteFill`, `oilGas`
- `filingDate`, `issuanceDate`, `expirationDate`, `jobStartDate`, `dobRunDate` — `YYYY-MM-DD`
- `permitteeName`, `permitteeBusinessName`, `permitteePhone`, `permitteeLicenseType`, `permitteeLicenseNumber`, `hicLicense`
- `actAsSuperintendent`, `superintendentName`, `superintendentBusinessName`
- `siteSafetyManagerName`, `siteSafetyManagerBusinessName`
- `ownerBusinessType`, `ownerBusinessName`, `ownerName`, `ownerAddress`, `ownerCity`, `ownerState`, `ownerZipCode`
- `latitude`, `longitude`
- `permitSiNo`, `sourceUrl`

### Output: per violation (mode = `violations` / `violationsByNumber`)

- `recordType: "violation"`, `scrapedAt`
- `isn` — internal DOB BIS violation ID
- `borough`, `bin`, `block`, `lot`, `bbl` (derived — see FAQ)
- `houseNumber`, `street`, `address`
- `issueDate`, `dispositionDate` — `YYYY-MM-DD`
- `dispositionComments`
- `violationTypeCode`, `violationType`, `violationNumber`, `violationCategory`, `number`
- `deviceNumber`, `ecbNumber` (cross-reference to the DOB ECB Violations dataset — present on ~8% of records), `description`
- `sourceUrl`

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `mode` | string | `permits` | `permits` / `permitsByJob` / `violations` / `violationsByNumber` |
| `borough` | string | – | NYC borough (mode=permits / mode=violations) |
| `houseNumber` | string | – | Street/house number, combine with `streetName` |
| `streetName` | string | – | Street name (case-insensitive) |
| `bin` | string | – | Building Identification Number |
| `block` | string | – | Tax block number, combine with `lot` |
| `lot` | string | – | Tax lot number, combine with `block` |
| `zipCode` | string | – | 5-digit ZIP (mode=permits) |
| `jobType` | string | – | DOB job type (mode=permits): A1/A2/A3/DM/NB/SG |
| `permitType` | string | – | DOB permit type (mode=permits): AL/DM/EQ/EW/FO/NB/PL/SG |
| `permitSubtype` | string | – | DOB permit subtype (mode=permits), e.g. BL/FN/SH/SP |
| `workType` | string | – | DOB work type (mode=permits), e.g. BL/PL/MH/SP |
| `permitteeLicenseType` | string | – | Permittee's professional license type (mode=permits), e.g. GC/MP/RA/PE |
| `ownerBusinessType` | string | – | Property owner's business type (mode=permits), e.g. CORPORATION/INDIVIDUAL/NYCHA |
| `permitStatus` | string | – | In Process / Issued / Re-Issued / Revoked (mode=permits) |
| `filingStatus` | string | – | Initial / Renewal (mode=permits) |
| `residentialOnly` | boolean | `false` | Only residential permits (mode=permits) |
| `selfCertifiedOnly` | boolean | `false` | Only self-certified filings — no DOB plan review (mode=permits) |
| `nonProfitOwnerOnly` | boolean | `false` | Only permits where the owner is flagged non-profit (mode=permits) |
| `dateField` | string | `filingDate` | Which permit date column to filter/sort by (mode=permits) |
| `dateFrom` | string | – | `YYYY-MM-DD`, inclusive lower bound |
| `dateTo` | string | – | `YYYY-MM-DD`, inclusive upper bound |
| `keyword` | string | – | Free-text search |
| `jobNumbers` | array | – | DOB job numbers to fetch (mode=permitsByJob) |
| `violationCategory` | string | – | Violation category (mode=violations) |
| `violationTypeCode` | string | – | Violation type code, e.g. elevator/boiler/facade (mode=violations) |
| `deviceNumber` | string | – | Elevator/boiler device number (mode=violations) |
| `ecbNumber` | string | – | Associated ECB violation number (mode=violations) |
| `violationDateField` | string | `issueDate` | Which violation date column `dateFrom`/`dateTo`/sorting apply to: issue date or disposition date (mode=violations) |
| `violationNumbers` | array | – | Exact violation numbers to fetch (mode=violationsByNumber) |
| `sortAscending` | boolean | `false` | Sort oldest-first instead of newest-first |
| `appToken` | string | – | Optional free Socrata app token for higher rate limits |
| `maxItems` | int | `20` | Hard cap on emitted records (1–10000) |

#### Example: permits issued in Manhattan for new buildings

```json
{
  "mode": "permits",
  "borough": "MANHATTAN",
  "jobType": "NB",
  "permitStatus": "ISSUED",
  "maxItems": 100
}
```

#### Example: permit history for a specific job number

```json
{
  "mode": "permitsByJob",
  "jobNumbers": ["340733647"]
}
```

#### Example: active elevator violations in Queens, last 12 months

```json
{
  "mode": "violations",
  "borough": "QUEENS",
  "violationCategory": "V-DOB VIOLATION - ACTIVE",
  "violationTypeCode": "E",
  "dateFrom": "2025-07-01",
  "dateTo": "2026-07-01",
  "maxItems": 200
}
```

#### Example: self-certified corporate-owned permits in Brooklyn

```json
{
  "mode": "permits",
  "borough": "BROOKLYN",
  "ownerBusinessType": "CORPORATION",
  "selfCertifiedOnly": true,
  "maxItems": 100
}
```

#### Example: violations at a specific address

```json
{
  "mode": "violations",
  "houseNumber": "222",
  "streetName": "4TH AVE",
  "borough": "MANHATTAN"
}
```

### Use cases

- **Real estate due diligence** — pull a property's full permit and violation history before acquisition
- **Contractor / permittee research** — track a licensed contractor's active and historical permits
- **Compliance monitoring** — watch for new violations by borough, category, or device type (elevators, boilers)
- **Urban development analysis** — study construction activity trends by borough, job type, or time period
- **PropTech / data products** — enrich property records with NYC DOB permit and violation history

### FAQ

**What is the data source?**
The NYC Department of Buildings' public datasets on [NYC Open Data](https://opendata.cityofnewyork.us/), served via the Socrata Open Data API (SODA): [DOB Permit Issuance](https://data.cityofnewyork.us/City-Government/DOB-Permit-Issuance/ipu4-2q9a) and [DOB Violations](https://data.cityofnewyork.us/Housing-Development/DOB-Violations/3h2n-5cm9).

**How fresh is the data?**
Both datasets are updated regularly by NYC DOB (the Permit Issuance dataset carries a `dobRunDate` field showing the last refresh date per record). This actor always queries the live dataset — there is no caching or staleness beyond NYC's own publishing schedule.

**Is this affiliated with NYC or the Department of Buildings?**
No. This is an independent, third-party actor that queries NYC's public open-data API. It is not affiliated with or endorsed by the City of New York.

**Why does `borough` sometimes not appear on a violation record?**
The Violations dataset stores borough as a legacy internal code. The vast majority of rows use an unambiguous code (1–5, or M/Q/S), which is mapped to a readable borough name. A small number of legacy rows use an ambiguous code that cannot be reliably distinguished between Bronx and Brooklyn, or use garbage/blank values — those records simply omit the `borough` field rather than guess.

**Why is `bbl` present on some violation records but not others?**
DOB Violations doesn't include a Borough-Block-Lot (BBL) column directly, so this actor computes it from the row's borough, block, and lot when all three are present and valid (matching the same 10-digit BBL format the Permit Issuance dataset provides natively). Rows with a missing, ambiguous, or placeholder block/lot don't get a computed BBL.

**Do I need a Socrata app token?**
No — the actor works fully without one. An optional free app token (get one at [data.cityofnewyork.us/profile/app\_tokens](https://data.cityofnewyork.us/profile/app_tokens)) raises the API's rate limit ceiling for very large runs, but isn't required for normal use.

**What date format should I use for `dateFrom`/`dateTo`?**
Always `YYYY-MM-DD` (e.g. `2025-01-31`) regardless of which dataset/mode you're querying — the actor handles the underlying format differences between the two source datasets internally.

**Can I look up a specific permit or violation by ID?**
Yes — use `mode=permitsByJob` with `jobNumbers`, or `mode=violationsByNumber` with `violationNumbers`.

**Does this cover ECB (Environmental Control Board) violations too?**
The DOB Violations dataset used here covers DOB-issued civil penalties. NYC also publishes a separate "DOB ECB Violations" dataset for summonses adjudicated by OATH/ECB, which is out of scope for this actor.

# Actor input Schema

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

What to fetch.

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

Filter to a single NYC borough (mode=permits / mode=violations).

## `houseNumber` (type: `string`):

Street/house number, e.g. `60` (mode=permits / mode=violations). Combine with `streetName` for an exact address lookup.

## `streetName` (type: `string`):

Street name as it appears in DOB records, e.g. `BAY 34 ST` (mode=permits / mode=violations). Case-insensitive.

## `bin` (type: `string`):

NYC Building Identification Number, e.g. `3185434` (mode=permits / mode=violations).

## `block` (type: `string`):

Borough-Block-Lot (BBL) tax block number (mode=permits / mode=violations). Combine with `lot`.

## `lot` (type: `string`):

Borough-Block-Lot (BBL) tax lot number (mode=permits / mode=violations). Combine with `block`.

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

Filter permits to a single 5-digit NYC ZIP code, e.g. `10013`.

## `jobType` (type: `string`):

Filter by DOB job type classification.

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

Filter by DOB permit type.

## `permitSubtype` (type: `string`):

Filter by the more specific permit subtype (subset of equipment-work / construction-equipment permit types).

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

Filter by the DOB-designated work type covered by the permit.

## `permitteeLicenseType` (type: `string`):

Filter by the professional license type of the person the permit was issued to.

## `ownerBusinessType` (type: `string`):

Filter by the property owner's business type.

## `selfCertifiedOnly` (type: `boolean`):

Only emit permits where the filing was self-certified by the applicant (no DOB plan review).

## `nonProfitOwnerOnly` (type: `boolean`):

Only emit permits where the property owner is flagged as a non-profit organization.

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

Filter by current permit status.

## `filingStatus` (type: `string`):

Filter by whether the permit filing was an initial filing or a renewal.

## `residentialOnly` (type: `boolean`):

Only emit permits flagged as residential work.

## `dateField` (type: `string`):

Which permit date column `dateFrom`/`dateTo`/sorting apply to.

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

Drop records before this date. For permits, applies to the field chosen in `dateField`; for violations, applies to the field chosen in `violationDateField`.

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

Drop records after this date. For permits, applies to the field chosen in `dateField`; for violations, applies to the field chosen in `violationDateField`.

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

Full-text search. For permits, matches across permittee/owner names and other text fields (mode=permits). For violations, matches the violation description (mode=violations).

## `jobNumbers` (type: `array`):

DOB job numbers to fetch all permits for, e.g. `340733647`.

## `violationCategory` (type: `string`):

Filter by DOB violation category/status.

## `violationTypeCode` (type: `string`):

Filter by DOB violation type code (e.g. elevator, boiler, facade, construction).

## `deviceNumber` (type: `string`):

Filter to a specific equipment device number (elevators, boilers), e.g. `5P182`.

## `ecbNumber` (type: `string`):

Filter to violations linked to a specific Environmental Control Board (ECB) violation number, e.g. `34254160K`. Only ~8% of violations have an associated ECB number.

## `violationDateField` (type: `string`):

Which violation date column `dateFrom`/`dateTo`/sorting apply to.

## `violationNumbers` (type: `array`):

Exact violation numbers to fetch, e.g. `1391F/03`.

## `sortAscending` (type: `boolean`):

By default results are sorted newest-first by date. Enable to sort oldest-first instead.

## `appToken` (type: `string`):

Optional free Socrata app token to raise API rate limits. Get one at https://data.cityofnewyork.us/profile/app\_tokens. Not required - the actor works without it.

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

Hard cap on emitted records.

## Actor input object example

```json
{
  "mode": "permits",
  "borough": "MANHATTAN",
  "jobType": "",
  "permitType": "",
  "permitSubtype": "",
  "workType": "",
  "permitteeLicenseType": "",
  "ownerBusinessType": "",
  "selfCertifiedOnly": false,
  "nonProfitOwnerOnly": false,
  "permitStatus": "",
  "filingStatus": "",
  "residentialOnly": false,
  "dateField": "filingDate",
  "jobNumbers": [],
  "violationCategory": "",
  "violationTypeCode": "",
  "violationDateField": "issueDate",
  "violationNumbers": [],
  "sortAscending": false,
  "maxItems": 20
}
```

# Actor output Schema

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

Dataset containing all scraped DOB permit and violation records.

# 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 = {
    "mode": "permits",
    "borough": "MANHATTAN",
    "jobType": "",
    "permitType": "",
    "permitSubtype": "",
    "workType": "",
    "permitteeLicenseType": "",
    "ownerBusinessType": "",
    "selfCertifiedOnly": false,
    "nonProfitOwnerOnly": false,
    "permitStatus": "",
    "filingStatus": "",
    "residentialOnly": false,
    "dateField": "filingDate",
    "jobNumbers": [],
    "violationCategory": "",
    "violationTypeCode": "",
    "violationDateField": "issueDate",
    "violationNumbers": [],
    "sortAscending": false,
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("crawlerbros/nyc-dob-permits-violations-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 = {
    "mode": "permits",
    "borough": "MANHATTAN",
    "jobType": "",
    "permitType": "",
    "permitSubtype": "",
    "workType": "",
    "permitteeLicenseType": "",
    "ownerBusinessType": "",
    "selfCertifiedOnly": False,
    "nonProfitOwnerOnly": False,
    "permitStatus": "",
    "filingStatus": "",
    "residentialOnly": False,
    "dateField": "filingDate",
    "jobNumbers": [],
    "violationCategory": "",
    "violationTypeCode": "",
    "violationDateField": "issueDate",
    "violationNumbers": [],
    "sortAscending": False,
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("crawlerbros/nyc-dob-permits-violations-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "mode": "permits",
  "borough": "MANHATTAN",
  "jobType": "",
  "permitType": "",
  "permitSubtype": "",
  "workType": "",
  "permitteeLicenseType": "",
  "ownerBusinessType": "",
  "selfCertifiedOnly": false,
  "nonProfitOwnerOnly": false,
  "permitStatus": "",
  "filingStatus": "",
  "residentialOnly": false,
  "dateField": "filingDate",
  "jobNumbers": [],
  "violationCategory": "",
  "violationTypeCode": "",
  "violationDateField": "issueDate",
  "violationNumbers": [],
  "sortAscending": false,
  "maxItems": 20
}' |
apify call crawlerbros/nyc-dob-permits-violations-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=crawlerbros/nyc-dob-permits-violations-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/fdEgnqNnfzuV1D4aq/builds/jzX0uM0n0bVgfcZKT/openapi.json
