# NYC Building Report — Owner, Value and Every Sale (`alauddinburki/nyc-building-report`) Actor

One row per New York City tax lot: owner, year built, zoning, floor area, current market and assessed value, and every recorded sale since 2016 with the price change. BBL or street address in, one joined property record out. Free public NYC data, no key.

- **URL**: https://apify.com/alauddinburki/nyc-building-report.md
- **Developed by:** [Alauddin burki](https://apify.com/alauddinburki) (community)
- **Categories:** Real estate, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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 Building Report — Owner, Value and Every Sale

Enter a **BBL** or a **street address** and get one row per New York City tax lot: who owns it, when it
was built, what it is zoned for, how much floor area it has, what the city says it is worth, and every
recorded sale since 2016 with the price change between the last two.

This is a **join**, not a scrape of one page. The three public datasets below each answer a third of
the question, and none of them carries an identifier the other two agree on except the BBL:

| Source | What it adds |
|---|---|
| **PLUTO** (`64uk-42ks`) | lot, address, owner, year built, building class, zoning, lot and building area |
| **Property Valuation & Assessment** (`8y4t-faws`) | current roll market and assessed values, tax class, stories, gross floor area |
| **Citywide Annualized Calendar Sales** (`w2pb-icbu`) | every recorded sale since 2016, with date and price |

All three are free New York City Open Data endpoints. No key, no login, no proxy.

### What you get

- **Property** view — address, borough, owner, year built, floor area, market value, assessed value,
  latest arm's-length sale.
- **Sales** view — latest and prior arm's-length sale, the percentage change, and how many recorded
  sales the lot has.
- **Values** view — market and assessed land and total values beside the tax class.

Every row carries the same 45 fields and is deterministic: the same lot on the same day produces the
same row.

```json
{
  "bbl": "1008620001",
  "address": "319 5 AVENUE",
  "ownerName": "FORMAN FIFTH,",
  "yearBuilt": 1917,
  "zoning": "C5-2",
  "buildingAreaSqFt": 27157,
  "marketValueTotal": 11363000,
  "assessedValueTotal": 5113350,
  "taxClass": "4",
  "saleCount": 0,
  "notes": "no_recorded_sales_since_2016"
}
```

### Inputs

| Field | What it does |
|---|---|
| **BBLs** | Ten-digit borough-block-lot numbers, e.g. `1008620001`. The reliable way in. |
| **Street addresses** | Matched against the one address PLUTO records per lot, optionally narrowed by borough. |
| **Borough** | Narrows address matching to MN, BX, BK, QN or SI. Ignored for BBL lookups. |
| **Maximum properties** | Hard row cap. Each property costs three requests. |
| **Time budget** | Stops cleanly and reports what was collected. |

### Two traps, handled

**One sale is stored once per building class.** A single deed can appear as two, three or a dozen rows
with the same date. Counting rows would report a sale history several times too long, so rows are
collapsed by date and the highest recorded price for that date is kept.

**A zero price is not a free building.** Transfers between related parties and estate transfers are
recorded at `0`. Those dates are counted separately (`zeroPriceSaleCount`), excluded from the
arm's-length price and excluded from the percentage change. A `$1` transfer is flagged in
`lowPriceSaleCount`.

**A condo lot sells units, not a building.** 1600 Broadway carries 112 recorded sales on one BBL, all
of them apartments. A building-level price change from those numbers would be nonsense, so for those
lots the change and price-per-square-foot fields are left `null` and `notes` says
`unit_sales_not_building_sales`.

### Limitations

- **One address per lot.** PLUTO records a single primary address. A building known by another street
  number will not be found by address — the Empire State Building's lot reads *319 5 AVENUE*. When a
  match is found under a different address than the one requested, `notes` says
  `address_differs_from_requested` and `matchedAddress` shows what was actually used. BBL lookups do
  not have this problem.
- **Sales start in 2016.** An older sale is simply absent, not zero.
- **Values are the city's, not an appraisal.** `marketValueTotal` is the Department of Finance's
  estimate. Exempt and special lots can be recorded as zero; `notes` marks that with
  `valuation_recorded_as_zero`.
- **No sale history for some lots.** The Empire State Building's lot is the example above: no transfer
  since 2016, so every sale field is `null` and the note says so.
- **New York City only.** This is not a national property database.

### Why this is different

Most free property lookups show **one dataset at a time** — PLUTO *or* sales *or* assessed value — and
leave the join to the buyer, who usually does it by hand in a spreadsheet and double-counts the
duplicate sale rows. This returns the joined answer as one flat row, states what is missing instead of
filling it with a zero, and gives the reason in `notes`. There is no login wall, no key and no
per-lookup web form.

### FAQ

**Do I need an API key?** No. All three sources are public New York City Open Data endpoints.

**What is a BBL?** Borough-block-lot: one digit for the borough (1 Manhattan, 2 Bronx, 3 Brooklyn,
4 Queens, 5 Staten Island), five digits of block and four digits of lot.

**Why is the address different from the one I typed?** PLUTO stores one address per lot. The row shows
the one it matched and flags the difference.

**Why is the latest sale price zero?** That transfer was recorded without a price. Use
`latestArmsLengthPrice` for the last transfer with a real price.

**Why is the change percentage null?** Fewer than two arm's-length sales, or the lot sells units
rather than a building.

**How current is the data?** The sales file covers 2016-01-01 to 2025-12-31 as of 2026-10-25; PLUTO
and the valuation roll are refreshed by the city, and `scrapedAt` records when this run read them.

### Pricing

Pay per result: **$0.002 per property row**. Each stored dataset row is billed, including any diagnostic row returned instead of results.

An Actor-start event is also charged at **$0.0001 per GB of allocated memory (minimum one event)**, even if no rows are returned. At 1 GB or less, 1,000 result rows cost $2.0001 before any applicable Store discount.

### Data source

New York City Open Data: PLUTO, the Property Valuation and Assessment Data (Condensed) roll and the
NYC Citywide Annualized Calendar Sales Update. Public data published by the City of New York.

# Actor input Schema

## `bbls` (type: `array`):

Ten-digit borough-block-lot numbers. Borough is one digit (1 Manhattan, 2 Bronx, 3 Brooklyn, 4 Queens, 5 Staten Island), then block (5 digits) and lot (4 digits). Example: 1008620001.

## `addresses` (type: `array`):

New York City street addresses, one per item, without a borough or ZIP suffix. Matching uses the one address PLUTO records per tax lot, so a building known by a different street number may not match — put the BBL in the field above when you know it.

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

Narrows address matching to one borough. BBL lookups ignore this field.

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

Hard limit on rows. Each property costs three requests, so this also bounds run time.

## `budgetSecs` (type: `integer`):

Stop and report what was collected before the run is killed.

## `paceMs` (type: `integer`):

A short pause keeps the public endpoint happy on long address lists.

## Actor input object example

```json
{
  "bbls": [
    "1008620001"
  ],
  "addresses": [
    "1600 BROADWAY"
  ],
  "borough": "",
  "maxItems": 5,
  "budgetSecs": 240,
  "paceMs": 150
}
```

# Actor output Schema

## `results` (type: `string`):

One row per tax lot, with the 45 fields listed under Views.

# 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 = {
    "bbls": [
        "1008620001"
    ],
    "addresses": [
        "1600 BROADWAY"
    ],
    "borough": "",
    "maxItems": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("alauddinburki/nyc-building-report").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 = {
    "bbls": ["1008620001"],
    "addresses": ["1600 BROADWAY"],
    "borough": "",
    "maxItems": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("alauddinburki/nyc-building-report").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 '{
  "bbls": [
    "1008620001"
  ],
  "addresses": [
    "1600 BROADWAY"
  ],
  "borough": "",
  "maxItems": 5
}' |
apify call alauddinburki/nyc-building-report --silent --output-dataset

```

## MCP server setup

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

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/6vcvendTb3eFzgpeR/builds/MZl08rwamDabQEcNH/openapi.json
