# NYC Property Tax Abatements - BBL & Program Detail (`j0401/nyc-property-abatements`) Actor

New York City property tax abatement detail (3.25M rows): BBL, condo, tax year, program (J51, 421G, ICAP, SOLAR, CONDO, COOP, MCI), application status, abated amounts and all 101 published columns. Filter by BBL, borough, year or program; aggregate counts and dollar sums; join PLUTO.

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

## Pricing

from $0.03 / 1,000 nyc property abatement 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 Property Tax Abatements

New York City property tax abatement detail from the Department of Finance - every published abatement application record for a tax lot: the BBL, condo number, tax year, abatement program, application status, the abated amounts and the assessed-value inputs behind them.

### Low cost

Abatement records: **from $0.00005 per record, down to $0.00003 at Gold.**

### What you get

- `bbl`, `borough`, `boroughName`, `block`, `lot` - the 10-digit Borough-Block-Lot key, derived from the source parcel id
- `condoId` - condo billing number where the record belongs to a condo
- `taxYear`, `period` - the tax year and the filing period code
- `program` - the abatement program code: across the corpus `J51`, `421G`, `ICAP`, `SOLAR`, `CONDO`, `COOP`, `MCI`, `CERP`, `BBP`, `GCCA`, `ICIP`, `CONED`, `GRNRF`. The newest snapshot carries **11 of these - no `421G` and no `ICIP` records**; those appear in older snapshots only.
- `programSubcode`, `applicationStatus` - the program sub-code and the source application status
- `abatementAmount` - the applied abatement in USD for that record
- `eligibleAbatement`, `installCost`, `netTaxes`, `assessedValue` - the other money columns lifted out of the detail
- `tenant` - the tenant named on commercial/lease records (published on 508 of the 133,509 newest-snapshot rows; a tenant search usually returns little)
- `extractDate` - the snapshot date this record was published in
- **All 101 published detail columns** are kept with their source names (`yrbegdt`, `yrenddt`, `leasearea`, `no_years`, `bldgsqft`, `partpct`, `commpct`, `baseyrvalu`, `abt_pct1`, `remain_tax`, `mcicost`, ...), so nothing in the source is dropped
- In `mode=profile` also: `plutoMatched`, `ownerName`, `plutoAddress`, `yearBuilt`, `residentialUnits`, `plutoAssessedTotal`, `plutoBuildingClass`, `plutoZip`

### Examples

The 50 largest abatements in the newest snapshot:

```json
{ "mode": "rows", "maxResults": 50 }
```

One property by BBL:

```json
{ "bbl": "4004200001" }
```

The newest snapshot that covers a tax year:

```json
{ "taxYear": "2026", "maxResults": 200 }
```

J-51 records, biggest first:

```json
{ "program": "J51", "maxResults": 200 }
```

Rows above a dollar threshold:

```json
{ "taxYear": "2026", "minAbatement": 100000, "maxResults": 500 }
```

One historical snapshot by its extract date:

```json
{ "extractDate": "2026-02-20", "maxResults": 200 }
```

Every snapshot ever published (see the warning below before using this):

```json
{ "includeHistory": true, "maxResults": 200 }
```

One property's abatement history across every snapshot - a year-by-year curve, newest extract first:

```json
{ "mode": "history", "bbl": "1009991001", "maxResults": 100 }
```

In `mode=history` each record appears once per snapshot it was published in, so the same case can show up in several extracts; every row is metered. Use `mode=rows` for a single snapshot.

Abatement dollars by program:

```json
{ "mode": "aggregate", "groupBy": "program", "metric": "abatementAmount" }
```

Records by program:

```json
{ "mode": "aggregate", "groupBy": "program" }
```

Abatement rows enriched with the PLUTO owner profile (owner, address, year built, residential units, building class) by exact BBL:

```json
{ "mode": "profile", "taxYear": "2026", "maxResults": 100 }
```

### Coverage and honest limits

- 🔴 **The city appends a snapshot to this dataset every cycle and never removes the old ones.** At build time there are **23 snapshots (2020-06-26 through 2026-08-15)**, which is why the raw table holds 3,246,890 rows. **The default scope is the newest snapshot alone: 133,509 records covering tax year 2027, on 119,259 distinct BBLs.**
- 🔴 **`includeHistory=true` reads all 23 snapshots.** The same tax record then appears once per snapshot, so record counts and dollar sums multiply - use it only to compare snapshots, never to measure how much abatement exists.
- In the newest snapshot the applied-abatement column totals **$1,577,253,604**. By program: ICAP $686.2M (5,857 records) / COOP $518.1M (5,850) / CONDO $204.8M (64,842) / SOLAR $72.8M (27,525) / J51 $31.2M (27,825) / BBP $26.9M (876) / CERP $9.6M (339) / MCI $7.8M (222) / GCCA $3.0M (169) / CONED $16.8M (3) / GRNRF $5,600 (1).
- **One row is an abatement record, not a property and not a dollar owed to you.** A single property can appear on several rows in the same tax year (one per application sequence / lease / sub-record). Use `bbl` plus `taxYear` plus `program` to see the set.
- 🔴 **This dataset covers property tax abatements only.** Exemption-only programs (STAR, veterans, senior citizen, clergy and similar) are published separately by the city and are not in this corpus.
- `tccode` is the **abatement program**, not a tax class - the source column name is misleading and we surface it as `program`.
- `applicationStatus` is a source code (`A`, `D`, `K`, `P`, `PR`, ...) and is **blank on 64,845 of the 133,509 newest-snapshot rows**; blank means the source published no status, not that the abatement failed. `A` (approved) covers 67,868.
- `cyenddt` is blank on **every** row of the newest snapshot (133,509), `cybegdt` on 133,089 of them (99.7%) and `tcsubcode` on 65,307 (49%); they are passed through unchanged.
- `period` has a single value (`2Q`) across the newest snapshot, so the `period` filter and `groupBy=period` are no-ops at the default scope; use `includeHistory` to compare periods across snapshots.
- **Money fields are decimal strings** in the output (as the source publishes them), so arithmetic in a consumer must parse them first.
- In `mode=history` every row carries `mode="history"` and its own `sourceUpdatedAt` (the snapshot the row came from).
- In `mode=profile`, PLUTO is joined on the exact 10-digit BBL. **PLUTO lists tax lots, not every abatement record**, so condo billing lots and special parcels can come back `plutoMatched=false`; the abatement row itself is still complete. Most rows do match (measured 43 of the first 50 and 160 of the first 200); the exceptions are condo/coop billing lots and special parcels that PLUTO does not list. Filter by `borough` or `bbl` for a specific property. PLUTO covers ~858,284 lots and is refreshed on its own schedule.
- `sourceUpdatedAt` is the snapshot date the record came from. The dataset's own refresh stamp is not used because it moves even when the underlying extract does not.

### Related

Also available in this account: **NYC Property Valuation & Assessment** (tax-lot assessed and market values) and **NYC HPD Violations** (housing-code violations by building).

# Actor input Schema

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

rows = abatement detail records (default). aggregate = one row per group with a count and/or dollar sum. profile = rows plus the PLUTO owner profile. history = one BBL's abatements across every published snapshot, newest extract first - a year-by-year abatement curve (bbl is required). Each record appears once per snapshot it was published in, and every row is metered.

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

10-digit Borough-Block-Lot, e.g. "4004200001".

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

Borough name or code: Manhattan/1, Bronx/2, Brooklyn/3, Queens/4, Staten Island/5.

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

Tax block within the borough, e.g. "835" (zero padding is not required).

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

Tax lot within the block, e.g. "17".

## `taxYear` (type: `string`):

Four-digit tax year, e.g. "2026" (the source covers 2021-2027).

## `program` (type: `string`):

Program code, e.g. J51, 421G, ICAP, SOLAR, CONDO, COOP, MCI, CERP, BBP, GCCA, ICIP, CONED, GRNRF. 421G and ICIP exist only in older snapshots (the newest snapshot has 11 programs) - add extractDate or includeHistory to reach them.

## `applicationStatus` (type: `string`):

Source application status code, e.g. "A" (approved), "R", "D". Blank status is common and is not an error.

## `period` (type: `string`):

Filing period code, e.g. "2Q".

## `condoId` (type: `string`):

Condo billing number, e.g. "101858".

## `tenant` (type: `string`):

Tenant-name prefix where the source records one (commercial/lease records only).

## `minAbatement` (type: `number`):

Only rows whose applied abatement amount is at least this many dollars.

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

Dimension for mode=aggregate (blank = taxYear). Every group is returned.

## `metric` (type: `string`):

What to aggregate (blank = count). abatementAmount sums the applied abatement in USD; eligibleAbatement and installCost sum their source columns.

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

Cap on records pushed in rows/profile mode (0 = default 50; up to 5,000 per run). Each record is metered individually, so there is no per-run charge cap.

## `extractDate` (type: `string`):

Read one historical snapshot by its extract date (YYYY-MM-DD), e.g. "2026-02-20". Leave blank for the newest snapshot.

## `includeHistory` (type: `boolean`):

When true, read all 23 published snapshots (3.25M rows). Each tax record then appears once per snapshot, so record counts and dollar sums multiply - only use it to compare snapshots. Cannot be combined with extractDate.

## Actor input object example

```json
{
  "mode": "",
  "groupBy": "",
  "metric": "",
  "maxResults": 50,
  "includeHistory": false
}
```

# Actor output Schema

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

NYC property tax abatement records - 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-property-abatements").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-property-abatements").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-property-abatements --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,j0401/nyc-property-abatements"
        }
    }
}
```

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/YFcNwaMJaJkw20pPP/builds/X6jL9rhuM6yJK5Jg4/openapi.json
