# NYC ACRIS Deed Transfer Intel - LLC, Cash & Out-of-State Buyers (`malonestar/acris-deed-transfer-intel`) Actor

NYC deed-transfer intel from ACRIS: each deed joined across Master, Legals & Parties into one row with BBL, price, buyer/seller names, plus LLC-buyer, out-of-state-buyer and cash-sale flags. Delta mode emits only new deeds. For agents farming just-sold, investors and title/proptech teams.

- **URL**: https://apify.com/malonestar/acris-deed-transfer-intel.md
- **Developed by:** [Kyle Maloney](https://apify.com/malonestar) (community)
- **Categories:** Real estate, Lead generation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $8.25 / 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.
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 ACRIS Deed Transfer Intel - LLC, Cash & Out-of-State Buyer Flags

A **NYC deed-transfer intelligence feed** built on the city's official ACRIS property records: who is buying, where, and with what money. Every recorded deed is joined across three ACRIS datasets into one row carrying the **BBL**, **sale price**, **buyer and seller names and full mailing addresses**, the parcel's **property type**, and analytics flags no raw ACRIS dump gives you:

- **`llc_buyer`** - the grantee is an entity (LLC / LP / LLP / TRUST / CORP / INC / HOLDINGS / PARTNERS), not a person
- **`out_of_state_buyer`** - a grantee mailing address is outside New York
- **`cash_sale`** - no mortgage was recorded against the same parcel around the deed date
- **`non_market_transfer`** - the deed recorded $0 consideration (intra-family, trust or estate transfer, not a sale)

Runs on the keyless NYC Open Data (Socrata) ACRIS datasets - no login, no API key, no anti-bot games.

### What changed in v1.1 (2026-08-01)

**The borough filter was measuring the wrong thing.** v1.0 selected deeds on `Master.recorded_borough`, which is the ACRIS *recording office* code, not the borough the property is in. Measured live over 1,500 consecutive deeds: **recorded\_borough matches the property's borough on only 19.8% of deeds (297/1,500)**, and 94% of all ACRIS documents are recorded under borough `1`. The practical effect on a Brooklyn run was that the actor could see only **154** of an estimated **3,841** Brooklyn deeds in a 60-day window, and **33 of every 100 rows it returned were not in Brooklyn** (15 Manhattan, 4 Bronx, 14 Queens on the 2026-08-01 run).

v1.1 selects on the **property borough from ACRIS Legals**. On the same input the run is now **100/100 Brooklyn**, and 98 of those 100 deeds were recorded under borough `1` - i.e. invisible to v1.0. The old signal is still published, as `recorded_borough`.

Also in v1.1: cross-run delta memory actually persists (it never did - see FAQ), every row states which sources answered, buyer and seller **mailing addresses** are emitted instead of being fetched and discarded, condo **unit** numbers are included, multi-parcel deeds report every parcel, and property-type codes are decoded from the authoritative published table.

### Who it is for

- **Real-estate agents** farming just-sold lists: new owners are the warmest listing / refi / insurance leads in the market, and the buyer's mailing address is now on the row.
- **Investors & analysts** tracking institutional activity: filter `llc_buyer=true` + `cash_sale=true` + `non_market_transfer=false` to see where entity cash is landing, block by block.
- **Title, lending & proptech teams** feeding transfer events into their own pipelines - `mode: "delta"` gives a genuine incremental feed.
- **Journalists & researchers** studying out-of-state and entity ownership of NYC housing.

### Example input

```json
{
  "boroughs": ["3"],
  "docTypes": ["DEED"],
  "lookbackDays": 60,
  "mode": "inventory",
  "maxResults": 100,
  "cashSaleToleranceDays": 45
}
```

`boroughs` selects on the **property** borough (1 Manhattan, 2 Bronx, 3 Brooklyn, 4 Queens, 5 Staten Island); leave it empty for all five. Set `mode` to `"delta"` on a schedule to receive only newly recorded deeds each run.

### Output fields

Every field below is emitted on every row. `null` always means **not checked** - never "checked and negative".
| Field | Description |
| --- | --- |
| `document_id` | ACRIS document identifier shared across Master, Legals and Parties. |
| `recorded_datetime` | Date the deed was recorded (YYYY-MM-DD). |
| `doc_type` | ACRIS document type code (e.g. DEED). |
| `borough` | PROPERTY borough code from ACRIS Legals: 1 Manhattan, 2 Bronx, 3 Brooklyn, 4 Queens, 5 Staten Island. This is what the boroughs input selects on. Not the same as recorded\_borough. |
| `block` | Tax block from Legals. |
| `lot` | Tax lot from Legals. |
| `bbl` | 10-digit Borough-Block-Lot key (joinable to PLUTO and DOF datasets). |
| `street_address` | Street number + name from the first Legals record. Does NOT include the unit - use street\_address\_full for mail. |
| `price_usd` | Deed consideration (document\_amt) in USD. 0 is what ACRIS publishes for a non-market transfer (see non\_market\_transfer); null means the field was not published. |
| `seller_names` | Grantor (party\_type 1) names. |
| `buyer_names` | Grantee (party\_type 2) names. |
| `buyer_mailing_state` | Mailing-address state of the first grantee. |
| `llc_buyer` | True when any grantee name matches an entity pattern (LLC, LP, LLP, TRUST, CORP, INC, HOLDINGS, PARTNERS...). NULL when the Parties source did not answer or no grantee was recorded - null means not checked, false means checked and negative. |
| `out_of_state_buyer` | True when any grantee mailing state is not NY. NULL when the Parties source did not answer or no grantee state was recorded. |
| `cash_sale` | True when no mortgage (MTGE) was recorded against the same BBL within the tolerance window (default +/-45 days). NULL when the BBL is unknown or the mortgage lookup did not answer for that parcel - see mortgage\_status. Note 56% of deeds in the default window are $0 non-market transfers, which are cash by definition; combine with non\_market\_transfer and price\_usd. |
| `source_url` | ACRIS document view link for the deed. |
| `borough_name` | Property borough name decoded from the Legals borough code. |
| `recorded_borough` | Master.recorded\_borough: the ACRIS recording-office code, NOT the property borough. Measured live 2026-08-01 it matches the property borough on only 19.8% of deeds, so it is reported for reference and never used to select rows. |
| `recorded_borough_name` | Name for recorded\_borough. |
| `borough_filter_basis` | Which borough the boroughs input was applied to. Always "property\_legals" from v1.1 onwards. |
| `unit` | Condo / apartment unit from Legals. Populated on 31.2% of Legals rows; null when the record carries none. |
| `street_address_full` | street\_address plus the unit when one is recorded. Use this for mail; street\_address alone is undeliverable for a condo. |
| `property_type` | ACRIS Legals property\_type code (PA, SC, D1, D2, D3, AP, CR...). |
| `property_type_desc` | Decoded property\_type. Null when ACRIS publishes a code outside the documented set. |
| `parcel_count` | Number of Legals (parcel) records on this document. 8.1% of sampled deeds cover more than one parcel. Null when the Legals leg did not answer. |
| `additional_bbls` | BBLs of the second and subsequent parcels on a multi-parcel deed. Empty array when single-parcel; null when the Legals leg did not answer. |
| `easement` | Legals easement flag. Null when not recorded. |
| `air_rights` | Legals air-rights flag. Null when not recorded. |
| `subterranean_rights` | Legals subterranean-rights flag. Null when not recorded. |
| `partial_lot` | Legals partial-lot indicator (E = entire lot, P = partial). |
| `document_date` | Date on the instrument itself, which precedes the recording date. |
| `crfn` | City Register File Number. |
| `percent_transferred` | Master.percent\_trans. 100 for a full transfer. Null when not published. |
| `partial_interest_transfer` | True when percent\_transferred is below 100. Null when percent\_transferred is unknown. |
| `non_market_transfer` | True when the recorded consideration is exactly $0 -- typically an intra-family, trust or estate transfer rather than a sale. 56% of deeds in the default window are $0. Null when the amount was not published. |
| `buyer_count` | Number of grantee (party\_type 2) records. Null when the Parties leg did not answer. |
| `seller_count` | Number of grantor (party\_type 1) records. Null when the Parties leg did not answer. |
| `buyer_mailing_states` | Every distinct grantee mailing state, deduplicated. buyer\_mailing\_state reports only the first grantee and can disagree with out\_of\_state\_buyer; this field does not. |
| `seller_mailing_states` | Every distinct grantor mailing state, deduplicated. |
| `buyer_mailing_address` | First grantee address\_1. Populated on 100% of sampled deeds. |
| `buyer_mailing_address_2` | First grantee address\_2. Populated on 8.1% of party records. |
| `buyer_mailing_city` | First grantee city. Populated on 100% of sampled deeds. |
| `buyer_mailing_zip` | First grantee ZIP. Populated on 99.7% of party records. |
| `buyer_mailing_country` | First grantee country code. |
| `buyer_contacts` | Every grantee as {name, address\_1, address\_2, city, state, zip, country} - the full mailing block ACRIS publishes. Null when the Parties leg did not answer. |
| `seller_contacts` | Every grantor as {name, address\_1, address\_2, city, state, zip, country}. The grantor address is often the seller forwarding address. Null when the Parties leg did not answer. |
| `other_party_names` | Names recorded with party\_type 3 (neither grantor nor grantee). |
| `master_status` | "ok" or "partial" - whether every ACRIS Master page was retrieved. |
| `legals_status` | "ok" (joined), "no\_legals\_recorded" (upstream answered, nothing recorded) or "unavailable" (the request failed). "unavailable" means the property fields on this row are unknown, not absent. |
| `parties_status` | "ok", "no\_parties\_recorded" or "unavailable". When "unavailable", llc\_buyer and out\_of\_state\_buyer are null rather than false. |
| `mortgage_status` | "ok", "partial" (candidate discovery was capped or a batch failed for this parcel), "unavailable" or "not\_applicable" (no BBL). cash\_sale is null unless this reads "ok". |
| `partial_data` | True when any contributing source did not fully answer for this row. |
| `data_through_date` | ACRIS good\_through\_date for the document - how current the source record is. |
| `delta_baseline_status` | "ok" (a real cross-run baseline was loaded), "seeded\_this\_run" (first run for this scope; the window is emitted as the initial inventory) or "not\_applicable" (inventory mode). |
| `run_mode` | "inventory" or "delta". |
| `window_start` | First recording date included, counted back from the dataset maximum. |
| `window_end` | Last recording date included = the newest recording in ACRIS Master. |

#### Field population, measured live on the default input (100 rows, 2026-08-01)

Contact and identity fields, because a lead list whose contact column is thin but advertised without qualification is a wrong answer:

| Field | Populated |
|---|---|
| `buyer_names` | 100% |
| `buyer_mailing_address` | 100% |
| `buyer_mailing_city` | 100% |
| `buyer_mailing_state` | 100% |
| `buyer_mailing_zip` | 100% |
| `buyer_mailing_country` | 100% |
| `buyer_contacts` (full block per grantee) | 100% |
| `seller_names` / `seller_contacts` | 100% |
| `street_address` / `street_address_full` | 100% |
| `bbl`, `block`, `lot`, `property_type` | 100% |
| `buyer_mailing_address_2` | **14%** - ACRIS publishes a second address line on only a minority of party records |
| `unit` | **28%** - only condo / co-op / unit-level parcels carry one |

Zero of the 57 declared fields were empty across the run. ACRIS does **not** publish phone numbers or email addresses for deed parties, so this actor never claims to: the contactable identity it gives you is a name plus a postal mailing address.

### Data sources (official NYC Open Data, all keyless)

- ACRIS Real Property **Master** (`bnx9-e6tj`, 17,036,716 rows) - document type, recording date, consideration, CRFN, percent transferred
- ACRIS Real Property **Legals** (`8h5j-fqxa`, 22,688,577 rows) - borough / block / lot, street number & name, unit, property type
- ACRIS Real Property **Parties** (`636b-3b5g`, 46,456,160 rows) - grantors, grantees, full mailing addresses
- ACRIS **Property Types Codes** (`94g4-w6xz`, 48 codes) - the authoritative `property_type` decode

### Live drift assertions

The actor checks the upstream before it produces a single billable row, and fails the run loudly if any of these regress:

- all three ACRIS datasets resolve and still expose every column the join reads
- a **negative control** (`doc_type='ZZZZ_NOT_A_REAL_DOC_TYPE'`) returns exactly 0 rows - proof the `$where` clause is being applied
- `party_type` stays inside its closed vocabulary `{1, 2, 3}`; an unknown value fails rather than being silently bucketed as neither buyer nor seller
- the property-borough vocabulary stays inside `{1..5}`
- the newest recording in ACRIS is within 400 days of today (measured lag on 2026-08-01: 32 days)
- at least 50% of scanned documents join to a Legals record (measured: 100%)
- the property-type code table still publishes at least 40 codes (measured: 48)

Every measured value is written to the run log, so tightening a band later is evidence-based rather than a guess.

### Use as an MCP tool

Callable by AI agents (Claude, Cursor, etc.) via [mcp.apify.com](https://mcp.apify.com). Field-level output descriptions make it a clean chainable tool for "who bought this / what sold near X" workflows. The `<source>_status` fields let an agent tell a verified negative from an unanswered question.

### Honest notes & limitations

- **Recording lag.** ACRIS open-data recordings trail real-world closings by several weeks to ~2-3 months. The lookback window is anchored to the **latest recorded date in the dataset**, never to today, so a 60-day window always returns the freshest available deeds.
- **`cash_sale` is a heuristic**, and it is now a gated one. `true` means no `MTGE` document was recorded against the same BBL within +/- `cashSaleToleranceDays` (default 45) of the deed. It is **`null`, never `true`**, when the mortgage lookup could not be completed for that parcel - check `mortgage_status`. Seller financing and very late mortgage recordings are genuine edge cases.
- **$0 deeds are common.** 39 of the 100 rows in the default run recorded $0 consideration. Those are intra-family, trust or estate transfers, and they are cash by definition - so `cash_sale=true` on its own is not an all-cash *purchase*. Combine with `non_market_transfer = false`.
- **Multi-parcel deeds.** `bbl` is the first parcel; `parcel_count` and `additional_bbls` carry the rest. 8.1% of sampled deeds cover more than one parcel.
- **Addresses are as recorded.** `street_address` is the Legals street number + name; `street_address_full` appends the unit. Neither is USPS-normalised.
- **Staten Island is thin.** 206,662 of 22.7M Legals records, because Richmond County keeps its own index. A borough-5 run can legitimately return very little.
- **No phone or email.** ACRIS does not publish them for deed parties.

### FAQ

**How do I get just-sold leads for my farm area?**
Set `boroughs` to your borough, `mode` to `delta`, and schedule the actor weekly. Each run emits only newly recorded deeds, with buyer names and mailing addresses ready for a CRM.

**Did delta mode really not work before?**
It did not. v1.0 kept its "already seen" memory in the run's **default** key-value store, which Apify creates fresh for every run - eight consecutive runs on 2026-08-01 each had a different store ID. Every delta run therefore reported itself as the first one and re-emitted (and re-billed) the whole window as new. v1.1 uses a **named** store, keyed by your borough / document-type / window combination so two differently-filtered schedules cannot overwrite each other, and every row carries `delta_baseline_status` (`ok`, `seeded_this_run` or `not_applicable`) so you can tell an incremental run from a seeding one.

**How do I track institutional / LLC buyers in NYC?**
Filter `llc_buyer = true`, optionally with `cash_sale = true`, `non_market_transfer = false` and `price_usd > 1000000`. Grantee names are the LLC names as recorded on the deed, and `buyer_contacts` gives the LLC's service address.

**Why is `llc_buyer` sometimes `null` instead of `false`?**
Because the actor did not verify it. `null` means the ACRIS Parties leg did not answer for that document, or no grantee was recorded; `false` means grantees were read and none looked like an entity. `parties_status` tells you which.

**Why is the sale price $0?**
ACRIS records the deed consideration. Transfers between family members, into an owner's own LLC, or via estates commonly record at $0. Use `non_market_transfer` or `price_usd > 0`.

**Does it cover all five boroughs?**
Yes - selection is on the property borough from ACRIS Legals. Note the caveat on Staten Island above.

### Pricing

Pay per result: you are charged only for the deed records actually emitted. A run that fails a drift assertion emits nothing and bills nothing.

### Related actors - the NYC investor shelf

Same BBL keying, built to run together:

| Actor | What it adds |
|---|---|
| [NYC Distressed Property Leads](https://apify.com/malonestar/distressed-property-signal-stacker) | Motivated-seller scoring - open HPD violations, tax-lien-sale notices and executed evictions per parcel |
| [NYC Landlord Registry Lead List](https://apify.com/malonestar/nyc-landlord-registry-lead-list) | Owner / agent contacts rolled up by portfolio size |
| [Parcel Owner Lookup](https://apify.com/malonestar/parcel-owner-lookup) | Address to owner of record for single-parcel checks |

Typical play: spot an LLC or cash buyer here, look up that buyer's wider portfolio in the HPD registry, then screen their other parcels for distress.

# Actor input Schema

## `boroughs` (type: `array`):

Select deeds by the PROPERTY borough recorded in ACRIS Legals: 1 = Manhattan, 2 = Bronx, 3 = Brooklyn, 4 = Queens, 5 = Staten Island. Leave empty for all boroughs. Note Staten Island is thinly represented in ACRIS (206,662 of 22.7M Legals records) because Richmond County keeps its own index.

## `docTypes` (type: `array`):

ACRIS document type codes to include (e.g. DEED, DEEDO, RPTT\&RET). Default DEED covers standard deed transfers. Leave empty to include all recorded document types (not recommended).

## `lookbackDays` (type: `integer`):

How many days of recordings to include, counted back from the LATEST recorded date in the ACRIS dataset (not from today - ACRIS recordings lag by several weeks to ~2-3 months).

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

"inventory" emits every deed in the window on every run. "delta" keeps a cross-run baseline in the NAMED key-value store "acris-deed-transfer-intel-state", scoped to your borough/doc-type/window combination, and emits only documents it has not seen before. The first delta run for a given scope seeds the baseline and emits the full window; every row carries delta\_baseline\_status so you can tell a seeded run from an incremental one.

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

Maximum number of deed records to emit (each emitted record is one billable result). Because borough selection happens after the Legals join, the actor scans up to 20x this many recorded documents to fill a borough-filtered request.

## `cashSaleToleranceDays` (type: `integer`):

A deed is flagged cash\_sale=true when no mortgage (MTGE) is recorded against the same borough-block-lot within this many days of the deed recording date. cash\_sale is null (never true) when the mortgage lookup could not be completed for that parcel - see mortgage\_status.

## Actor input object example

```json
{
  "boroughs": [
    "3"
  ],
  "docTypes": [
    "DEED"
  ],
  "lookbackDays": 60,
  "mode": "inventory",
  "maxResults": 100,
  "cashSaleToleranceDays": 45
}
```

# Actor output Schema

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

The default dataset of joined deed 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 = {
    "boroughs": [
        "3"
    ],
    "docTypes": [
        "DEED"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("malonestar/acris-deed-transfer-intel").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 = {
    "boroughs": ["3"],
    "docTypes": ["DEED"],
}

# Run the Actor and wait for it to finish
run = client.actor("malonestar/acris-deed-transfer-intel").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 '{
  "boroughs": [
    "3"
  ],
  "docTypes": [
    "DEED"
  ]
}' |
apify call malonestar/acris-deed-transfer-intel --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=malonestar/acris-deed-transfer-intel",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

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