# NY Property Assessments - Parcels, Owners & 5-Year Value Trail (`j0401/ny-property-assessments`) Actor

New York property assessment rolls (public open data, 23.6M records, 2021-2025): every parcel with its owner, mailing address, exemptions and six value columns - plus each parcel's exact year-over-year value movement. Excludes New York City, which files separately.

- **URL**: https://apify.com/j0401/ny-property-assessments.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 ny 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

## NY Property Assessments - Parcels, Owners & 5-Year Value Trail

Every parcel in New York State outside New York City, its owner, its exemptions and its full five-year value trail - all on one exact parcel key.

New York's local assessors file their final assessment rolls with the State Department of Taxation and Finance - 996 local governments, every city and town. Those rolls are published as a **five-year stack**: 23.6 million records covering the 2021 through 2025 rolls, about 4.72 million parcels in each. A parcel is one row per roll year, and its identity - `swisCode` + `printKeyCode` - 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 parcel's market value stepping up, an assessment holding flat while the market rises, a deed changing hands between rolls.

### 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** | roll year, county, municipality (code + name), school district (code + name), SWIS code, print key code |
| **Owner** | primary owner (first / MI / last / suffix) **plus three additional owner slots** |
| **Owner mailing address** | prefix, number, street, suffix, city, state, ZIP, country, PO box |
| **Parcel address** | number, street, suffix, frontage, depth, bank, grid east / north, deed book and page |
| **Classification** | property class code + description, tax class, roll section |
| **Value** | full market value, land assessment, total assessment, **and the county, town and school taxable values separately** |
| **Exemptions** | **10 slots**, each an exemption code and the amount it removes - stated separately for the **county, city and school** levy |
| **Derived** | composed owner name, all owners, parcel and mailing addresses, exemption codes and count, and on a parcel trail the year-over-year value and owner movement |

### 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 source's own key:

```json
{ "mode": "parcel", "swisCode": "460600",
  "printKeyCode": "136.03-01-001.001" }
```

```
2021   TDC Hornell Land, LLC    market 950,000
2022   TDC Hornell LLC          market 673,575   -276,425  -29.1%   owner changed
2023   TDC Hornell LLC          market 684,211    +10,636    +1.6%
2024   TDC Hornell LLC          market 714,286    +30,075    +4.4%
2025   TDC Hornell LLC          market 764,706    +50,420    +7.1%
```

The revaluation, the recovery, and the moment the deed moved - out of the source's own rolls.

### Who owns New York

The owner block is the primary owner **plus three additional owners**, and the mailing address of record is separate from the parcel address - which is exactly what tells a resident owner from an absentee one. In the 2025 roll, **229,960 parcels carry a mailing state other than New York** (229,702 once the 258 rows that spell it `Ny` / `ny` / `nY` are folded in), and the mailing state is populated with 97 distinct values, a mix of US states, Canadian provinces and foreign country codes:

| Owner mailing state | Parcels |
|---|---|
| New York | 4,462,527 |
| New Jersey | 35,001 |
| Florida | 33,783 |
| Pennsylvania | 25,292 |
| Texas | 19,671 |
| Connecticut | 12,026 |
| California | 11,163 |

Filter `mailingState` to any state but `NY` and you have the out-of-state owners of New York property.

The same block finds institutional owners directly. `ownerName` matches a substring across the primary owner and all three additional owner slots, so these are the counts that filter actually returns for the 2025 roll - `State Of New York` (21,455 parcels), `SUFFOLK COUNTY` (7,614), `TOWN OF BROOKHAVEN` (6,004), alongside private owners such as `Smith` (33,572), `Miller` (17,848) and `Brown` (17,567).

### Exemptions, in depth

Exemptions are not a flag here - **10 slots per parcel**, each carrying the exemption code and the amount it removes **stated separately for each taxing jurisdiction** (county, city and school), because each levies against its own value: of the 1,830,495 parcels that carry an exemption anywhere in their ten slots in the 2025 roll, **1,106,674 have a county amount and a school amount that differ**. The most-used exemption codes that year:

| Exemption code | Parcels (any slot) |
|---|---|
| 4185 | 863,801 |
| 4183 | 490,565 |
| 4112 | 149,725 |
| 4180 | 133,419 |
| 4113 | 107,012 |
| 4172 | 81,378 |
| 4422 | 54,115 |
| 1350 | 35,647 |

Cap the assessment and you isolate the parcels whose tax base has been carved down - a search that a value-only dataset cannot express. Each slot's amount is returned per jurisdiction, so the county, city and school figures stay separate rather than being flattened into one.

### Classification

Property class is a NY State code with its description attached - **290 distinct descriptions in the 2025 roll** (293 across all five years), led by `One Family Year-Round Residence` (2,923,898 parcels), `Residential Vacant Land` (288,562), `Two Family Year-Round Residence` (217,493), `Rural Vacant Lots of 10 Acres or less` (110,672), `Rural Residence with Acreage` (93,042), `Apartments` (88,490), `Residential Land Including a Small Improvement` (71,898) and `Mobile Home` (70,249). Roll section separates the taxable roll from the special franchises and utility roll.

### Modes

- **`rows`** (default) - parcels matching your filters, from the newest roll year.
- **`parcel`** - one parcel's full five-year trail, with the value and owner movement computed between rolls. Takes `swisCode` + `printKeyCode`.
- **`trend`** - per-roll-year totals for a scope: parcel count and summed market and assessed value, one row per year, without shipping a single parcel. Scope it to a county, a municipality or a school district and you have that area's five-year movement series.
- **`aggregate`** - one count row per group, rolled up by county, municipality, municipality code, school district, property class, tax class, roll section or roll year.

### Inputs

Filter by `rollYear` (2021-2025, defaults to the newest), `county` (one of the 57), `ownerName`, `address`, `mailingState` / `mailingCity` / `mailingZip` / `mailingCountry`, `municipality` / `municipalityCode` / `swisCode`, `schoolDistrict`, `propertyClass` / `propertyClassDescription`, `taxClass`, `rollSection`, `printKeyCode`, and value bounds `minMarketValue` / `maxMarketValue` / `minAssessment` / `maxAssessment`.

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

**Absentee owners: Florida residents holding New York property**

```json
{ "mode": "rows", "county": "Suffolk", "mailingState": "FL",
  "maxResults": 500 }
```

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

```json
{ "mode": "parcel", "swisCode": "460600",
  "printKeyCode": "136.03-01-001.001" }
```

**A county's five-year market movement**

```json
{ "mode": "trend", "county": "Albany" }
```

**Parcels over $5M in Erie County**

```json
{ "mode": "rows", "county": "Erie", "minMarketValue": 5000000,
  "maxResults": 500 }
```

**How many parcels by property class**

```json
{ "mode": "aggregate", "groupBy": "propertyClass",
  "county": "Westchester" }
```

### Notes on the data

- **New York City is not in this dataset.** The rolls cover every city and town in the state **except New York City**, which files with the city and publishes separately. The 57 counties here are New York's 62 minus the five that make up the city. A "New York State" figure from this actor is the state outside the five boroughs - the dataset is explicit about the boundary, and so is this description.
- **`taxClass` is blank on most rows** - 76% of the table. The field is not populated on every municipality's roll. It is offered as a filter for the rolls that do carry it; the property class code and description cover classification everywhere.
- **The rolls republish on the annual cycle.** The five-year stack grows by one roll year each publication; the 2025 roll is the newest. The 2021 roll is present in full, so the trail is five complete years rather than a rolling window.
- **One row carries a malformed roll year** (`23` instead of a 4-digit year) - a single record straight from the source, passed through as stored.
- **A parcel is keyed exactly**, by `swisCode` + `printKeyCode`, never by owner name - so a change of owner between rolls is visible as a change of owner, not as a failed match.
- **ZIP codes are matched by prefix**, so both the five-digit and the ZIP+4 forms the source stores come back from one filter.

### 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** - `swisCode=010100`, `printKeyCode=40.00-3-1`, one row from the newest roll:

```json
{
  "platform": "ny-property-assessments",
  "source": "ny-orpts-assessment-rolls",
  "mode": "rows",
  "recordType": "parcel",
  "groupKey": "", "groupCount": "", "groupBy": "",
  "rollYear": "2025",
  "county": "Albany",
  "municipalityCode": "010100",
  "municipalityName": "Albany",
  "swisCode": "010100",
  "schoolDistrictCode": "010100",
  "schoolDistrictName": "Albany",
  "printKeyCode": "40.00-3-1",
  "rollSection": "1",
  "taxClass": "N",
  "propertyClass": "330",
  "propertyClassDescription": "Vacant Land Located in Commercial Areas",
  "ownerName": "McHugh Peter",
  "primaryOwnerFirstName": "Peter", "primaryOwnerLastName": "McHugh", "primaryOwnerMI": "", "primaryOwnerSuffix": "",
  "additionalOwner1First": "Marie-Louise", "additionalOwner1LastName": "McHugh", "additionalOwner1MI": "", "additionalOwner1Suffix": "",
  "additionalOwner2First": "", "additionalOwner2LastName": "", "additionalOwner2MI": "", "additionalOwner2Suffix": "",
  "additionalOwner3First": "", "additionalOwner3LastName": "", "additionalOwner3MI": "", "additionalOwner3Suffix": "",
  "allOwners": "McHugh Peter | McHugh Marie-Louise",
  "parcelAddress": "135 Willow St",
  "parcelAddressNumber": "135", "parcelAddressStreet": "Willow", "parcelAddressSuff": "St",
  "front": "182", "depth": "176", "bank": "",
  "gridEast": "617210", "gridNorth": "988020",
  "deedBook": "", "deedPage": "",
  "mailingAddress": "135 Willow St",
  "mailingAddressNumber": "135", "mailingAddressPrefix": "", "mailingAddressStreet": "Willow", "mailingAddressSuff": "St",
  "mailingCity": "Guilderland", "mailingState": "NY", "mailingZip": "12084", "mailingCountry": "", "mailingPoBox": "",
  "fullMarketValue": "20833",
  "assessmentLand": "20000", "assessmentTotal": "20000",
  "countyTaxableValue": "20000", "townTaxableValue": "20000", "schoolTaxable": "20000",
  "exemptionCodes": "", "exemptionCodeCount": "",
  "exemptionCode1": "", "exemptionCode2": "", "exemptionCode3": "", "exemptionCode4": "", "exemptionCode5": "",
  "exemptionCode6": "", "exemptionCode7": "", "exemptionCode8": "", "exemptionCode9": "", "exemptionCode10": "",
  "exemptionAmountCounty1": "", "exemptionAmountCounty2": "", "exemptionAmountCounty3": "", "exemptionAmountCounty4": "", "exemptionAmountCounty5": "",
  "exemptionAmountCounty6": "", "exemptionAmountCounty7": "", "exemptionAmountCounty8": "", "exemptionAmountCounty9": "", "exemptionAmountCounty10": "",
  "exemptionAmountCity1": "", "exemptionAmountCity2": "", "exemptionAmountCity3": "", "exemptionAmountCity4": "", "exemptionAmountCity5": "",
  "exemptionAmountCity6": "", "exemptionAmountCity7": "", "exemptionAmountCity8": "", "exemptionAmountCity9": "", "exemptionAmountCity10": "",
  "exemptionAmountSchool1": "", "exemptionAmountSchool2": "", "exemptionAmountSchool3": "", "exemptionAmountSchool4": "", "exemptionAmountSchool5": "",
  "exemptionAmountSchool6": "", "exemptionAmountSchool7": "", "exemptionAmountSchool8": "", "exemptionAmountSchool9": "", "exemptionAmountSchool10": "",
  "prevRollYear": "",
  "marketValueChange": "", "marketValueChangePct": "",
  "assessmentChange": "", "assessmentChangePct": "",
  "ownerChanged": "",
  "sourceUpdatedAt": "2026-04-24"
}
```

**`mode=aggregate`, `groupBy=propertyClass`, `rollYear=2025`** - one row per property-class description:

```
One Family Year-Round Residence         2,923,898
Residential Vacant Land                   288,562
Two Family Year-Round Residence           217,493
Rural Vacant Lots of 10 Acres or less     110,672
```

# Actor input Schema

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

rows = parcels matching your filters. parcel = one parcel's full 5-year trail with the year-over-year value and owner movement. trend = per-roll-year totals for a scope (county, municipality, school district...). aggregate = server-side count by one dimension.

## `rollYear` (type: `string`):

Assessment roll year. The source currently carries 2021-2025. Defaults to the newest roll available. Leave blank for the default.

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

Maximum records to return. Default 50; 0 is treated as the default rather than as all records.

## `county` (type: `string`):

One of the 57 New York counties on the rolls. New York City's five boroughs file separately and are NOT in this dataset.

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

Partial name match across the primary owner and the three additional owner slots (last and first names). '%' and '\_' are not allowed.

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

Partial street-name match on the parcel address.

## `mailingState` (type: `string`):

Two-letter state on the owner's mailing address. 'NY' is the default for a resident owner; any other value finds owners who live elsewhere.

## `mailingCity` (type: `string`):

Partial city match on the owner's mailing address.

## `mailingZip` (type: `string`):

ZIP prefix match on the owner's mailing address. Digits and dashes only.

## `mailingCountry` (type: `string`):

Partial country match on the owner's mailing address.

## `municipality` (type: `string`):

Partial city or town name.

## `municipalityCode` (type: `string`):

Exact municipality code (6 digits).

## `swisCode` (type: `string`):

Exact SWIS code of the municipality (6 digits).

## `schoolDistrict` (type: `string`):

Partial school district name.

## `propertyClass` (type: `string`):

Exact NY property class code, 1-3 digits (210 = one family year-round residence, 311 = residential vacant land). Note: the aggregate mode's propertyClass dimension groups by the class DESCRIPTION, not this code.

## `propertyClassDescription` (type: `string`):

Partial match on the property class description.

## `taxClass` (type: `string`):

Exact tax class code, used only in municipalities that assess by class (1-8 = the assessment class, H = homestead, N = non-homestead, S = special). The source documents the code but not the individual class meanings, so prefer `propertyClass`/`propertyClassDescription` when you need to identify a property type. The field is blank on 76% of rows.

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

Which section of the assessment roll the parcel sits on (source's own legend): 1 = ordinary taxable property, including partially exempt; 3 = taxable State-owned land; 5 = special franchise (utility property on public rights-of-way); 6 = utilities and non-ceiling railroads; 7 = ceiling railroads; 8 = wholly exempt property; 9 = other. 0 is the source's unset value. Sections 2 and 4 are not in use. Blank = any.

## `printKeyCode` (type: `string`):

The parcel's print key code, exact match (used with swisCode to identify a single parcel).

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

Lower bound on the full market value.

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

Upper bound on the full market value.

## `minAssessment` (type: `number`):

Lower bound on the total assessment.

## `maxAssessment` (type: `number`):

Upper bound on the total assessment. A small cap finds parcels sitting under an exemption.

## `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",
  "maxResults": 50
}
```

# Actor output Schema

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

New York 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/ny-property-assessments").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/ny-property-assessments").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/ny-property-assessments --silent --output-dataset

```

## MCP server setup

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

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/FZMvjKnioA9Q2GRro/builds/vSlRwTJu3J5lV7fw1/openapi.json
