# NYC Property Valuation - Assessment & 5-Year Value Trail (`j0401/nyc-property-valuation`) Actor

New York City property assessment records (public open data, 11.7M rows, 2023-2027): every parcel's owner and address, its value through five assessment stages, and the year-over-year movement in market and assessed value.

- **URL**: https://apify.com/j0401/nyc-property-valuation.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.06 / 1,000 nyc property 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 Valuation - Assessment & 5-Year Value Trail

Every property in New York City, its owner, its classification and its full five-year value trail - all on one exact parcel id.

The New York City Department of Finance assesses roughly 1.17 million parcels every year and publishes the result as an open dataset. That dataset is a **five-year stack**: 11.7 million records covering the 2023 through 2027 rolls (the rolls are published *ahead* of the year they cover), about 2.34 million records each. A parcel is one row per roll, and its identity - the PARID, which is the borough-block-lot number the city uses everywhere - is exact, so the same property lines up across all five years without a name match.

That is what makes the movement real rather than inferred: a market value stepping up, an assessment holding while the market rises, a tax class changing.

### Low cost

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

### What you get

| Block | Fields |
|---|---|
| **Identity** | PARID, borough (code and name), block, lot, easement, section/volume, roll section, assessment period |
| **Owner & address** | owner, house number (low and high), street name, ZIP, borough-block-lot (`bbl` joined for you), zoning, community board |
| **Classification** | building class, building and lot dimensions, corner flag, land area, number of buildings, year built and its two alterations, co-op and unit counts, condo number and suffixes |
| **Values** | **five assessment stages**, each with eleven figures - see below |
| **Protest & appeal** | protest 1 / 2 / prior, attorney group 1 / 2 / prior, appointment date and the appointed lot |
| **Floor area** | eleven areas by building use: gross, hotel, office, residential, retail, loft, factory, warehouse, storage, garage, other |
| **Derived** | composed address, borough name, BBL, and on a parcel trail the year-over-year market and assessed movement |

### Five assessment stages, on every record

This is the part a plain "property value" dataset does not have. Each record carries the value at **every stage of the assessment process**:

| Stage | Prefix | What it records |
|---|---|---|
| Prior year | `py` | last year's roll, as the baseline |
| Tentative | `ten` | the roll as first published |
| Current base | `cbn` | the current base assessment |
| Final | `fin` | the finalized assessment |
| Current | `cur` | the roll in force |

Each stage carries eleven figures: market land, market total, assessed land, assessed total, assessed-with-exemption total, transitional land, transitional total, transitional-with-exemption total, taxable total, taxable-with-exemption total, and the tax class for that stage. So a record shows not just what a property is worth but **how its assessment moved from the prior year through to final** - the assessment pipeline itself, not a snapshot of it.

### The five-year value trail

Ask for one parcel and you get every roll it appears on, oldest first, with the movement between consecutive rolls computed on the city's own parcel id:

```json
{ "mode": "parcel", "parid": "1000010010" }
```

```
2023   GOVERNORS ISLAND CORPORATION   market 327,275,000
2024   GOVERNORS ISLAND CORPORATION   market 337,093,000    +9,818,000   +3.0%
2025   GOVERNORS ISLAND CORPORATION   market 347,205,990   +10,112,990   +3.0%
2026   GOVERNORS ISLAND CORPORATION   market 357,622,000   +10,416,010   +3.0%
2027   GOVERNORS ISLAND CORPORATION   market 355,619,560    -2,002,440   -0.6%
```

### An area's movement without shipping a parcel

`mode=trend` returns one row per roll year for whatever scope you filter to - parcel count, summed market value and summed taxable value - so you get a borough's five-year series without pulling a single parcel:

```json
{ "mode": "trend", "boro": "3" }
```

```
2023   354,754 parcels   market 455,535,516,285
2024   356,996 parcels   market 489,980,424,388    +7.6%
2025   359,033 parcels   market 492,928,832,164    +0.6%
2026   361,145 parcels   market 520,387,566,126    +5.6%
2027   362,745 parcels   market 555,310,479,782    +6.7%
```

### Owners

The owner is on every record, and it is the same field a title or lien search keys on - so `ownerName` finds every parcel a given owner holds across the five boroughs, and `mode=parcel` then follows any one of them through the years.

### Modes

- **`rows`** (default) - parcels matching your filters, from the newest roll.
- **`parcel`** - one parcel's full five-year trail with the movement computed between rolls. Takes `parid`.
- **`trend`** - per-roll-year totals for a scope: parcel count and summed market and taxable value, one row per year.
- **`aggregate`** - one count row per group, rolled up by borough, building class, ZIP, tax class, roll section or year.

### Inputs

Filter by `year` (2023-2027, defaults to the newest), `period`, `boro` (1-5), `parid`, `ownerName`, `address`, `zipCode`, `bldgClass`, `zoning`, `rollSection`, `block`, `lot`, and value bounds `minMarketValue` / `maxMarketValue` / `minTaxableValue` / `maxTaxableValue`.

A filter that belongs to another mode is rejected outright rather than silently ignored - so a query you narrowed never comes back unnarrowed.

### Example inputs

**Every parcel owned by a given owner**

```json
{ "mode": "rows", "ownerName": "GOVERNORS ISLAND", "maxResults": 500 }
```

**One parcel's five-year value trail**

```json
{ "mode": "parcel", "parid": "1000010010" }
```

**Brooklyn's five-year movement**

```json
{ "mode": "trend", "boro": "3" }
```

**Manhattan parcels valued over $10M**

```json
{ "mode": "rows", "boro": "1", "minMarketValue": 10000000,
  "maxResults": 500 }
```

**How many parcels by building class**

```json
{ "mode": "aggregate", "groupBy": "bldgClass", "boro": "4" }
```

### Notes on the data

- **`parid` alone is not unique within a year - the roll is per assessment period.** Each parcel appears once for `period` 1 and once for `period` 3, and the two rows are *not* duplicates: about 80% of the columns match, but the `currentBase` and `final` value groups are zero at period 1 and carry the real figures at period 3 - the assessment simply is not final yet. The default path pins **period 3, the finalized roll** (measured on the 2025 roll: 1,169,614 rows against 1,169,614 distinct PARIDs, so the key is exact there). Set `period` explicitly to 1 if you want the earlier, still-moving figures.
- **The rolls publish ahead of the year they cover**, so the newest roll year is the coming year rather than the last one. `year` defaults to whichever is newest, never to a hardcoded year.
- **A value of 0 is meaningful and common** - it is how the city records a parcel that is fully exempt at that stage. It is returned as `0`, not blanked.
- **`maxResults` is capped at one page of 20,000 records.** A larger request is served one page - the run logs a warning saying it was capped, so a short result is never mistaken for the whole set. Narrow the filters to page through a larger scope.
- **The first query of a run can be slow and the rest fast.** This dataset's value differs enormously warm and cold: a query pinned to a year and period measured ~15s cold and ~0.6s warm. That is the source's own cache, not the actor; the default path is built around it.
- **Within one pinned roll and period, PARID is exact** - one row per parcel, verified at 1,169,614 rows against 1,169,614 PARIDs on the 2025 finalized roll. The extra rows in a raw count come from the assessment periods, not from parcels appearing twice.

### Output

One JSON record per parcel row, and the same key set whichever mode produced it - so the output loads into a table or a dataframe without a schema union step. Aggregate and trend records carry their group key, count and dimension in the same shape as a parcel record.

**Pay-per-event** - you are charged per record delivered, never for the query itself. The run stops cleanly if you set a spend limit on your Apify account.

### Example output

**One parcel** - `parid=1000010010`, one row from the newest roll. Every record carries the five assessment stages side by side; the record fields are `""` outside the stage the source has populated yet:

```json
{
  "platform": "nyc-property-valuation",
  "source": "nyc-dof-property-valuation",
  "mode": "rows",
  "recordType": "parcel",
  "groupKey": "", "groupCount": "", "groupBy": "",
  "parid": "1000010010",
  "bbl": "1000010010",
  "year": "2027", "period": "3",
  "boro": "1", "boroughName": "Manhattan",
  "block": "1", "lot": "10", "easement": "",
  "address": "140 CARDER ROAD", "housenumLo": "140", "housenumHi": "", "streetName": "CARDER ROAD",
  "zipCode": "10004", "zoning": "R3-2", "rollSection": "", "rectype": "1", "secvol": "101", "stcode": "14560301010",
  "owner": "GOVERNORS ISLAND CORPORATION",
  "bldgClass": "Y4", "units": "1", "numBldgs": "86", "landArea": "7577714", "grossSqft": "2532066",
  "yrBuilt": "1900", "yrBuiltRange": "0", "yrBuiltFlag": "",
  "yralt1": "0", "yralt1Range": "0", "yralt2": "0", "yralt2Range": "0",
  "corner": "", "bldExt": "N", "bldStory": "3.50", "bldFrt": "0.00", "bldDep": "0.00",
  "lotFrt": "0.00", "lotDep": "0.00", "lotIrreg": "",
  "coopNum": "0", "coopApts": "0", "condoNumber": "", "condoSfx1": "", "condoSfx2": "", "condoSfx3": "",
  "gepsupportRc": "00", "cpbBoro": "1", "cpbDist": "1",
  "currentMarketLand": "227162873", "currentMarketTotal": "355619560",
  "currentAssessedLand": "102223293", "currentAssessedTotal": "160028802", "currentAssessedExemptionTotal": "160028802",
  "currentTransitionalLand": "102223293", "currentTransitionalTotal": "153688728", "currentTransitionalExemptionTotal": "153688728",
  "currentTaxableTotal": "160028802", "currentTaxableExemptionTotal": "160028802", "currentTaxClass": "4",
  "currentBaseMarketLand": "227162873", "currentBaseMarketTotal": "355619560",
  "currentBaseAssessedLand": "102223293", "currentBaseAssessedTotal": "160028802", "currentBaseAssessedExemptionTotal": "160028802",
  "currentBaseTransitionalLand": "102223293", "currentBaseTransitionalTotal": "153688728", "currentBaseTransitionalExemptionTotal": "153688728",
  "currentBaseTaxableTotal": "160028802", "currentBaseTaxableExemptionTotal": "160028802", "currentBaseTaxClass": "4",
  "finalMarketLand": "227162873", "finalMarketTotal": "355619560",
  "finalAssessedLand": "102223293", "finalAssessedTotal": "160028802", "finalAssessedExemptionTotal": "160028802",
  "finalTransitionalLand": "102223293", "finalTransitionalTotal": "153688728", "finalTransitionalExemptionTotal": "153688728",
  "finalTaxableTotal": "160028802", "finalTaxableExemptionTotal": "160028802", "finalTaxClass": "4",
  "tentativeMarketLand": "227855989", "tentativeMarketTotal": "357719878",
  "tentativeAssessedLand": "102535195", "tentativeAssessedTotal": "160973945", "tentativeAssessedExemptionTotal": "160973945",
  "tentativeTransitionalLand": "102535195", "tentativeTransitionalTotal": "154564416", "tentativeTransitionalExemptionTotal": "154564416",
  "tentativeTaxableTotal": "160973945", "tentativeTaxableExemptionTotal": "160973945", "tentativeTaxClass": "4",
  "priorYearMarketLand": "227855989", "priorYearMarketTotal": "357622000",
  "priorYearAssessedLand": "102535195", "priorYearAssessedTotal": "160929900", "priorYearAssessedExemptionTotal": "160929900",
  "priorYearTransitionalLand": "102535195", "priorYearTransitionalTotal": "150417534", "priorYearTransitionalExemptionTotal": "150417534",
  "priorYearTaxableTotal": "160929900", "priorYearTaxableExemptionTotal": "160929900", "priorYearTaxClass": "4",
  "pyTaxFlag": "A", "cbnTaxFlag": "A", "curTaxFlag": "A", "finTaxFlag": "A", "tenTaxFlag": "A",
  "protest1": "", "protest2": "", "protestOld": "",
  "attorneyGroup1": "", "attorneyGroup2": "", "attorneyGroupOld": "",
  "apptBoro": "1", "apptBlock": "1", "apptLot": "10", "apptDate": "2026-03-16T00:00:00.000", "apptEase": "",
  "extraCredit": "2026-05-15T00:00:00.000",
  "reucRef": "", "reucDescription": "", "subidentReuc": "", "subident": "", "ident": "", "valref": "",
  "aptno": "", "newdrop": "0", "noav": "0",
  "uafLand": "0.0000000", "uafBldg": "0.0000000",
  "hotelAreaGross": "0", "officeAreaGross": "0", "residentialAreaGross": "0",
  "retailAreaGross": "0", "loftAreaGross": "0", "factoryAreaGross": "0", "warehouseAreaGross": "0",
  "storageAreaGross": "0", "garageArea": "0", "otherAreaGross": "2532066",
  "prevYear": "", "marketValueChange": "", "marketValueChangePct": "", "assessedValueChange": "", "assessedValueChangePct": "",
  "sourceUpdatedAt": "2026-09-14"
}
```

**`mode=trend`, `boro=3`** - Brooklyn's roll, one row per year:

```
2023   354,754 parcels   market 455,535,516,285
2024   356,996 parcels   market 489,980,424,388    +7.6%
2025   359,033 parcels   market 492,928,832,164    +0.6%
2026   361,145 parcels   market 520,387,566,126    +5.6%
2027   362,745 parcels   market 555,310,479,782    +6.7%
```

# Actor input Schema

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

rows = parcels matching your filters. parcel = one parcel's full 5-year value trail with the year-over-year movement. trend = per-roll-year totals for a scope. aggregate = server-side count by one dimension.

## `year` (type: `string`):

Assessment roll year. The source currently carries 2023-2027 (the rolls publish ahead of the year they cover). Defaults to the newest roll available.

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

3 = the finalized roll (default). 1 = the earlier period, where the current-base and final value groups are still zero.

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

Maximum records to return, up to one page of 20000. Default 50; 0 is treated as the default rather than as all records. A larger request is served one page and the run logs that it was capped.

## `boro` (type: `string`):

NYC borough code: 1 Manhattan, 2 Bronx, 3 Brooklyn, 4 Queens, 5 Staten Island.

## `parid` (type: `string`):

The parcel's PARID, exact match (use it with mode=parcel to follow one property across years).

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

Partial owner-name match. '%' and '\_' are not allowed.

## `address` (type: `string`):

Partial street-name match.

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

ZIP prefix match. Digits and dashes only.

## `bldgClass` (type: `string`):

Partial building-class match (A1, A5, B1, R4 and so on).

## `zoning` (type: `string`):

Partial zoning-district match.

## `rollSection` (type: `string`):

Exact roll section code.

## `block` (type: `integer`):

Exact tax block number.

## `lot` (type: `integer`):

Exact tax lot number.

## `minMarketValue` (type: `number`):

Lower bound on the current market total.

## `maxMarketValue` (type: `number`):

Upper bound on the current market total.

## `minTaxableValue` (type: `number`):

Lower bound on the current taxable total.

## `maxTaxableValue` (type: `number`):

Upper bound on the current taxable total.

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

Dimension for the server-side count. Use only with mode=aggregate - supplying it in another mode is rejected rather than ignored.

## Actor input object example

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

# Actor output Schema

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

New York City property records, parcel histories, year trends 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-property-valuation").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-valuation").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-valuation --silent --output-dataset

```

## MCP server setup

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

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/BcxHJjMZy3fLgorzI/builds/An5vILv2PhHCSiwqZ/openapi.json
