Distressed Property Leads - NYC, Detroit, Philly & Chicago
Pricing
from $13.75 / 1,000 results
Distressed Property Leads - NYC, Detroit, Philly & Chicago
Motivated-seller leads for New York City, Detroit, Philadelphia and Chicago. Stacks open code violations, blight tickets, tax liens, evictions, vacant-building complaints and scofflaw listings per property, scores distress, and attaches the owner name and mailing address. Keyless government data.
Pricing
from $13.75 / 1,000 results
Rating
0.0
(0)
Developer
Kyle Maloney
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
4 days ago
Last modified
Categories
Share
Distressed Property Leads - NYC, Detroit, Philadelphia, Chicago
Find motivated-seller leads by stacking official municipal distress signals per property and attaching the owner name and mailing address each city publishes. Four metros, all from keyless government sources.
- New York City - open HPD violations, DOF tax-lien-sale notices, marshal evictions
- Detroit - blight tickets, unpaid judgment balances, vacant property registrations
- Philadelphia - open L&I code violations, with the Notice of Violation PDF
- Chicago - building violations, 311 vacant-building complaints, Building Code Scofflaw listings
Every property that shows enough distinct signals gets a distress score, the owner name and mailing address, and comes out ranked highest-distress first - ready for direct mail, skip tracing, cold calling, or acquisition underwriting. Runs default to New York City; see Metros covered below.
Who it is for
- Real estate investors and wholesalers hunting off-market, pre-foreclosure-adjacent deals
- Agents and brokers prospecting listings from owners under financial or regulatory pressure
- Lenders, note buyers and tax-lien investors screening collateral risk by parcel
- PropTech and data teams who want a scored, deduplicated per-BBL feed instead of three raw city dumps
How the score works
distress_score = openC*3 + openB*2 + openA*1 + lien(10 * cycle multiplier) + evictions*5
Lien-sale cycle escalates the weight: 90 Day Notice x1.0, 60 Day x1.2, 30 Day x1.5,
10 Day x1.8, Final Sale x2.0. A parcel with 1 open class-C violation, a 30-Day
lien notice and 2 executed evictions scores 3 + 15 + 10 = 28.
signals_present lists which of violations, lien_sale, evictions the parcel
shows; minSignals controls how many distinct types are required (default 2).
Example input
{"borough": "BRONX","minSignals": 2,"lookbackMonths": 12,"maxResults": 100}
All fields are optional - an empty input runs the Bronx with the defaults above.
borough accepts a name or a code 1-5; anything else fails the run before any row
is emitted or billed.
How complete is the contact data? (measured, not claimed)
Measured on the actor's own prefill (Bronx, 12 months, minSignals 2, 100 results) against the live datasets on 2026-08-01, n = 100 leads:
| Contact / identity field | Populated |
|---|---|
owner_contact (whole object) | 98.0% |
owner_contact.name | 98.0% |
owner_contact.business_address | 98.0% |
owner_contact.type | 98.0% |
owner_contact.corporation | 0.0% - see note below |
owner_corporation (owning LLC) | 98.0% |
owner_full_name (incl. middle initial) | 98.0% |
owner_title | 66.0% |
owner_agent_name | 98.0% |
owner_agent_address | 98.0% |
registration_id | 100.0% |
address / zip / bin / latitude / longitude | 100.0% |
lien_sale_cycle and the other lien_* fields | 38.0% (only parcels on the lien list) |
newest_eviction_date | 73.0% (only parcels with an eviction) |
Mean HPD contacts on file per lead: 4.4.
owner_contact.corporation reads 0.0% because owner_contact is a single
best-ranked contact and the top rank is a natural person (IndividualOwner or
HeadOfficer), who never carries a corporation name. The owning entity is on a
different contact row for the same registration. v1.1 publishes it as
owner_corporation rather than leaving it fetched-and-discarded, and
owner_contacts_all carries up to 12 contacts so nothing is lost. The v1.0 field
is unchanged so existing pipelines keep working.
2.0% of leads have no HPD contact at all: those are buildings that never
registered with HPD (small owner-occupied homes, many commercial parcels). They
are never dropped - they emit with owner_contact: null,
owner_contact_checked: true and owner_contact_found: false.
Output fields
Parcel identity
| Field | Meaning |
|---|---|
bbl | 10-digit Borough-Block-Lot parcel id |
borough | Borough name |
block | Tax block, unpadded |
lot | Tax lot, unpadded |
address | Best available street address across the contributing datasets |
zip | ZIP code |
bin | NYC Building Identification Number |
latitude | WGS84 latitude as published by HPD or DOI |
longitude | WGS84 longitude as published by HPD or DOI |
community_board | NYC community board |
council_district | NYC council district |
census_tract | Census tract as published by HPD |
Distress signals
| Field | Meaning |
|---|---|
distress_score | Weighted score (formula above), sorted descending |
signals_present | Which of violations / lien_sale / evictions fired |
open_violations_a | Open class-A (non-hazardous) violations in the window |
open_violations_b | Open class-B (hazardous) violations in the window |
open_violations_c | Open class-C (immediately hazardous) violations in the window |
open_violations_i | Open class-I violations. HPD stopped issuing class I in Aug 2014, so this is 0 in any reachable window - it exists so those rows are counted rather than silently discarded |
open_violations_total | A + B + C + I |
rent_impairing_violations | Open violations HPD flags rent-impairing (tenants may deposit rent with the court) |
newest_violation_date | Issue date of the most recent open violation counted |
oldest_violation_date | Issue date of the oldest open violation counted in the window |
lien_sale_cycle | Most severe lien-sale stage, or null when the parcel is not on the list |
lien_sale_month | List month of that most severe notice |
lien_notice_count | How many lien-sale notices the parcel received in the window |
lien_water_debt_only | True when the lien listing is water/sewer debt only |
lien_building_class | NYC building class as published on the lien-sale list |
lien_tax_class_code | NYC tax class code as published on the lien-sale list |
evictions_count | Marshal-executed evictions at the parcel in the window |
evictions_residential | Of those, marked Residential |
evictions_commercial | Of those, marked Commercial |
newest_eviction_date | Execution date of the most recent eviction |
Owner and contact
| Field | Meaning |
|---|---|
owner_contact | Best single HPD registration contact: {type, name, corporation, business_address}, or null |
owner_contact_checked | True when the HPD contact source answered this run. When false, a null contact means "not checked", not "no contact on file" |
owner_contact_found | True/false only when owner_contact_checked is true; null otherwise |
owner_contact_count | How many HPD contact rows exist for the parcel |
owner_corporation | Best corporation name across ALL contacts - usually the owning LLC |
owner_full_name | Top-ranked contact name including middle initial |
owner_title | Title as filed with HPD (MEMBER, PRINCIPAL, TREASURER...) |
owner_contact_types | Every HPD contact type present for the registration |
owner_agent_name | Filed Agent or SiteManager - often the reachable party when the owner is an LLC |
owner_agent_address | Business address of that Agent or SiteManager |
owner_contacts_all | Up to 12 contacts, each {type, name, middle_initial, title, corporation, contact_description, business_address} |
registration_id | The HPD registration id used for the join |
Run provenance and source health
| Field | Meaning |
|---|---|
violations_status | ok / unavailable for the HPD violation read |
lien_sale_status | ok / unavailable for the tax-lien-sale read |
evictions_status | ok / unavailable for the marshal-eviction read |
contacts_status | ok / unavailable for the HPD contact join |
registrations_status | ok / unavailable / not_applicable for the registration-roll fallback |
sources_failed | Dataset URLs that did not answer. Empty on a healthy run |
partial_run | True when any contributing source did not answer |
violations_max_date | Newest record in the HPD violation dataset at run time |
lien_sale_max_date | Newest list month in the lien-sale dataset at run time |
evictions_max_date | Newest execution date in the eviction dataset at run time |
violations_cutoff | Earliest violation issue date counted |
lien_sale_cutoff | Earliest lien-sale list month counted |
evictions_cutoff | Earliest eviction execution date counted |
evictions_without_bbl_in_window | Executed evictions in this borough and window that carry no BBL upstream and cannot be attributed to a parcel |
lookback_months | Window used for this run |
data_as_of | UTC date the run executed |
unmapped_borough_rows | Eviction rows whose borough token this build does not recognise. Below the drift tolerance, so the run continued, but they are excluded from the score rather than mapped to a guess. 0 = checked, nothing unmapped |
unmapped_vocabulary_rows | Same, totalled across all eight closed-vocabulary gates (violation class and status, lien cycle, water-debt flag, eviction borough, contact type, property type, possession type) |
vocabulary_drift_note | Detail of any tolerated unknown value: the value, its row count, and its share of its group. null when every token was recognised |
source_urls | NYC Open Data pages that contributed to the row |
Metros covered
metros selects which city pipelines run. It defaults to ["nyc"], which is
exactly what this actor did before multi-metro support, so every saved task, API
caller and MCP client that omits the field is unaffected and every New York
output field keeps its original name and meaning.
| Metro | Distress signals | Owner name | Owner mailing address | Match method |
|---|---|---|---|---|
nyc | HPD violations, DOF tax-lien-sale notices, marshal evictions | HPD registration | HPD registration contact | BBL |
detroit | Blight tickets, unpaid judgment balances, BSEED vacant property registrations | On the ticket row (99.9998% populated, measured live) | On the ticket row, plus assessor taxpayer record | Parcel ID |
philadelphia | L&I open code violations, with the Notice of Violation PDF | On the violation row (98.1% of open violations) | OPA assessor roll | OPA account number |
chicago | Building violations, 311 Vacant/Abandoned Building Complaints, Building Code Scofflaw listings | Cook County assessor roll, or the scofflaw defendant | Cook County assessor roll | Exact address only |
Detroit and Philadelphia publish the sellable field on the violation record
itself, so no address matching is involved at all. Chicago publishes neither an
owner nor a parcel ID on either signal feed, so it is matched to the Cook County
parcel universe on an exact address: no street scan, no nearest-house
ladder, no fuzzy fallback. Its owner-match rate is therefore lower than
Detroit's or Philadelphia's, and the actual rate for the run is published on
every row as metro_owner_contact_rate rather than quoted from this page.
Each metro is a separate full-city scan and is billed per result. Adding a metro roughly adds its own row count to your bill, so add them one at a time.
{"metros": ["detroit"],"minSignals": 2,"lookbackMonths": 12,"maxResults": 100}
borough applies to New York City only and is ignored by the other three, which
are scanned city-wide.
Philadelphia publishes one signal type, and the threshold respects that
minSignals counts distinct signal types, and each city publishes a
different number of them: Detroit and Chicago three each, Philadelphia one.
A Philadelphia property therefore cannot ever show two distinct types, so a
literal minSignals: 2 there would return an empty dataset for a city with
tens of thousands of open violations - and an empty dataset reads as "no
distressed property in Philadelphia", which is false.
The threshold is therefore clamped to what the city can actually publish,
and both numbers ship on every row as metro_min_signals_requested and
metro_min_signals_applied, with metro_min_signals_note explaining the
difference whenever there is one. Nothing is clamped in Detroit or Chicago
below 3. Raising minSignals there still narrows the list exactly as it does
in New York.
What is deliberately NOT here
- Tax delinquency is not a v1 signal in any of the three new metros.
Philadelphia's delinquency table is frozen at
year_month202206 and says so nowhere in its own response; Cook County's entire tax-sale family is tagged inactive with data last updated in 2016; Wayne County publishes a 2016 snapshot. Presenting any of them as current would be a confident wrong answer. The Philadelphia vintage is still read on every run and published astax_delinquency_vintage_not_usedinsidemetro_data_vintage, so if that layer ever goes live again the run will say so. - Rental registrations are published but never scored. Registering a rental is compliance, not distress.
- Tax-exempt status is a scoring modifier, not a signal type, so
minSignalsmeans the same thing before and after owner enrichment. - Houston and Dallas are not covered. Houston's code-enforcement data is XLSX only and last updated 2023-06-09; Dallas publishes no keyless owner roll.
Multi-metro output fields (Detroit, Philadelphia, Chicago)
New York rows keep the v1.2 shape exactly. The fields below appear on rows from
the three new metros. Anything a metro does not publish is null, which means
not checked or not published - it never means checked-and-negative.
Property identity
| Field | Meaning |
|---|---|
metro | Which pipeline produced the row: detroit, philadelphia or chicago. Absent on New York rows |
metro_parcel_id | Detroit parcel_id with its trailing period stripped, the Philadelphia OPA account number, or the Cook County PIN when exactly one PIN matched |
metro_parcel_id_type | detroit_parcel, phila_opa or cook_pin. null when no unambiguous identifier could be attached |
property_key | The key the property was rolled up on: a parcel id in Detroit and Philadelphia, a normalized situs address in Chicago |
property_key_type | detroit_parcel, phila_opa or chicago_address |
property_address / property_city / property_state / property_zip | Situs address as published by the contributing source |
neighborhood | Neighborhood, where a contributing source publishes one |
Distress signals
| Field | Meaning |
|---|---|
metro_signal_types | Distinct signal types present: code_violation, unpaid_judgment, vacant_building, vacant_registration, scofflaw_listing |
metro_signal_count | How many distinct types this property shows, compared against minSignals |
code_violation_count | Open code violations or blight tickets counted inside the window |
newest_code_violation_date / oldest_code_violation_date | Date range of the counted violations |
code_violation_examples | Up to five counted violations, each with type, code, date, status, reference and url |
code_violation_nov_url | Philadelphia only: direct link to the published Notice of Violation PDF |
vacant_complaint_count | Chicago 311 Vacant/Abandoned Building Complaints for this address in the window, matched on the exact sr_type |
vacant_complaint_duplicate_count | How many of those 311 itself flags as duplicates. Excluded from the score so re-reports cannot inflate it |
vacant_complaint_open_count | How many are still Open |
vacant_registration_count | Detroit BSEED vacant property registrations for this parcel in the window |
newest_vacant_signal_date | Most recent vacant complaint or vacant registration date |
rental_registration_count | Detroit BSEED rental registrations. Context only, never scored |
rental_registration_type | Registration type as filed |
newest_rental_registration_date | Date of the most recent rental registration |
blight_ticket_count | Detroit blight tickets counted in the window, including paid ones |
unpaid_ticket_count | How many still carry a balance due |
unpaid_balance_total | Total outstanding judgment balance in dollars. null when no source published an amount, never 0 as a stand-in for unknown |
newest_ticket_disposition | Hearing disposition, e.g. Responsible by Default |
scofflaw_listed | true when the address is on the Chicago Building Code Scofflaw list. null in metros that publish no equivalent |
scofflaw_case_number / scofflaw_list_date | Circuit court case number and building list date behind the listing |
scofflaw_owner_raw | The defendant_owner string exactly as Chicago publishes it, retained even when the Cook roll supplied a better contact |
Owner and contact - the sellable fields
| Field | Meaning |
|---|---|
owner_name | Owner or taxpayer name as published, verbatim. Never parsed into first/last |
owner_name_raw | The same string, kept alongside any normalization so nothing is lost |
owner_name_convention | Which convention produced it: detroit_ticket_owner, detroit_taxpayer, phila_opa_owner, cook_mail_name or chicago_scofflaw |
owner_name_convention_note | Plain-English description of that convention |
owner_name_source | on_violation_row, assessor_roll or scofflaw_list |
owner_parties | Chicago scofflaw defendant_owner split on its pipe delimiter, so a two-party listing is not presented as one name |
owner_mailing_address | Full deliverable mailing address, comma-joined. This is the field the product is sold on |
owner_mailing_street / owner_mailing_city / owner_mailing_state / owner_mailing_zip / owner_mailing_country | Components of the same address |
owner_mailing_source | on_violation_row or assessor_roll |
owner_lookup_status | ok = an owner record was found. no_match = the lookup ran and found nothing, which is a measurement. unavailable is never published: a source that does not answer fails the run instead |
address_match_method | on_row, parcel_id or exact_address |
cook_pin_candidate_count | How many Cook PINs matched the address exactly. Greater than 1 means a condo or multi-PIN building, and no single PIN is asserted |
enriched | true only when the row carries both an owner name and a deliverable mailing address |
is_absentee | Owner mailing address differs from the property address. null when either side was not retrieved |
is_out_of_state | Owner mailing state differs from the property state. null when the mailing state was not retrieved |
is_entity_owner | Owner name contains an LLC / INC / TRUST / LP token. null when no owner name was retrieved |
non_owner_occupied | Detroit only: true when the Principal Residence Exemption is 0, which is the assessor stating the owner does not live there |
owner_occupied_pct | Detroit Principal Residence Exemption percentage (0-100). null when the assessor row was not joined, never coerced to 0 |
Three metros, three name conventions. Philadelphia is surname-first and
unpunctuated (ROMERO ANGEL), Detroit's taxpayer field is comma-delimited
(RIVERS, KRISTINA), and a Cook County mailing name can hold two people in one
field (BARRY ROBERTS & GLORIA). The raw string always ships, the convention is
named on the row, and no cross-source match is ever keyed on a parsed name.
Assessor detail
| Field | Meaning |
|---|---|
assessed_value / taxable_value | Detroit assessed and taxable value in dollars |
market_value | Philadelphia OPA market value |
last_sale_date / last_sale_price | Most recent recorded sale the assessor publishes |
year_built | Year built as published |
property_class_description | Assessor property class or category description |
land_use_description | Detroit assessor use-code description |
is_improved | Detroit: true when the parcel carries a structure, false for vacant land |
total_acreage | Detroit parcel acreage |
tax_status_code / tax_status_description | Detroit assessor tax status verbatim, e.g. TAXABLE or EXEMPT (211.7D). Treated as an open vocabulary |
tax_exempt | true when the tax status begins with EXEMPT. null when the assessor row was not joined |
homestead_exemption | Philadelphia OPA homestead exemption amount |
cook_property_class / cook_township | Cook County assessor property class code and township |
parcel_modified_date | Detroit: the assessor row's own modified date, published instead of asserting a vintage from the layer description |
Per-source status - every layer reports its own outcome
ok means that source answered and its full result set was read and reconciled
against the count the source itself reports. null means not checked - it
is what a Detroit row carries for a Philadelphia layer. A source that fails
does not produce a status of false; it fails the whole run.
| Field | Source |
|---|---|
detroit_blight_tickets_status | Detroit blight tickets |
detroit_parcels_status | Detroit assessor parcel roll |
detroit_vacant_registrations_status | Detroit BSEED vacant property registrations |
detroit_rental_registrations_status | Detroit BSEED rental registrations |
phila_violations_status | Philadelphia L&I violations |
phila_opa_status | Philadelphia OPA assessor roll |
chicago_building_violations_status | Chicago building violations |
chicago_vacant_311_status | Chicago 311 vacant-building complaints, with the lot-mowing negative control proving the filter discriminates |
chicago_scofflaw_status | Chicago Building Code Scofflaw list |
cook_assessor_status | Cook County parcel universe |
Score, coverage and run provenance
| Field | Meaning |
|---|---|
metro_distress_score | 0-100 additive score. A different field from the New York distress_score, which is an unbounded weighted sum with a live meaning to existing buyers |
metro_distress_reasons | Every component that contributed, with its point value |
metro_distress_score_unavailable_reason | Populated only when the score is null, naming the layers that did not answer |
metro_score_coverage | Which layers were available to score this metro. The denominator ships with the number |
metro_sources_queried / metro_sources_failed | Every source queried, and any that did not answer |
metro_enrichment_complete / metro_partial_run | Whether every source for this metro answered |
metro_min_signals_requested / metro_min_signals_applied | The threshold asked for, and the one actually used |
metro_min_signals_note | Populated only when the two differ, explaining why |
metro_lookback_months / metro_window_start / metro_window_end | The window actually used |
metro_data_vintage | Per-source newest record date and age in days, measured on this run |
metro_row_counts | Per-source row counts for the window, including the Chicago negative control and the Detroit future-dated rows the bound excluded |
metro_drift_measurements | Every live drift measurement taken before any row was emitted |
metro_owner_contact_rate | Share of this metro's emitted rows carrying a deliverable mailing address, measured on this run rather than quoted |
metro_candidate_count | How many properties met minSignals before ranking - the universe this row was selected from |
metro_enrichment_pool_size / metro_enrichment_pool_cap | How many candidates were owner-enriched, and the cap applied, so the selection method is visible rather than implied |
metro_properties_with_any_signal | How many properties showed at least one signal in the window |
implausible_date_rows | Rows for this property whose source date fell outside 1990..today and was not trusted |
implausible_date_rows_in_window | The same count across the whole metro window |
metro_source_urls | Open-data pages for every source that contributed |
The score is null, never lower, when a layer did not answer. A lower number
would read as "less distressed" when it means "less known". The same rule
applies to metro_score_coverage: a property scored in Detroit against four
layers and one scored in Philadelphia against two do not mean the same thing,
so the denominator is published alongside the number.
Live source checks that run before anything is billed
Every metro runs its gates first. If one fails, the run fails with an explanatory message and no row is emitted and nothing is billed - including rows from a metro that already succeeded, which are buffered and only pushed once every requested metro has passed.
| Check | Measured live 2026-08-06 |
|---|---|
| Detroit blight tickets, total | 897,472 |
Detroit property_owner_name populated | 99.9998% |
| Detroit parcel roll, total | 377,863 |
Detroit taxpayer_address populated | 99.99% |
| Detroit newest ticket date (bounded) | 2026-08-06 |
| Philadelphia violations, total / open | 2,002,359 / 89,045 |
Philadelphia opa_owner populated on open | 98.07% |
| Philadelphia OPA parcels | 583,700 |
| Philadelphia newest violation date | 2026-08-05 |
| Chicago building violations, total | 2,021,485 |
Chicago exact Vacant/Abandoned Building Complaint | 45,472 |
Chicago Clean Vacant Lot Request (negative control) | 38,911 |
| Chicago scofflaw listings / newest list date | 659 / 2026-03-01 |
Cook County parcel universe max(year) | 2023 |
The Chicago pair is a discriminating control, not a floor: the exact sr_type
match and the lot-mowing type are counted separately on every run. If the filter
ever collapsed to a LIKE '%Vacant%' the two counts would converge, and the
lead count would be inflated by 85.6% with requests to mow an empty lot.
Detroit's newest-ticket check is measured against a closed upper bound
because real rows carry year typos as far out as 8535; the rows the bound
excluded are counted and published rather than dropped silently.
What v1.3 added (2026-08-06)
Detroit, Philadelphia and Chicago, from keyless government sources, with the owner contact taken from whatever each city actually publishes.
- Nothing about New York changed. No field was removed, renamed or
re-meaned,
metrosdefaults to["nyc"], and the New York code path is the v1.2 path untouched. - The owner contact is not inferred. Detroit and Philadelphia publish it on
the violation record; Chicago is matched to the Cook County roll on an exact
address and nothing else. Where a match fails,
owner_lookup_statusreadsno_match, which is a measurement, not an outage. - Every source count is reconciled against the count the source itself reports before any row is ranked. A short read raises instead of ranking an arbitrary subset.
- Tax delinquency was investigated and deliberately excluded in all three metros. See "What is deliberately NOT here" above.
Freshness: dataset-anchored lookback
The lien-sale list is published in cycles and lags the other sources (currently
through the 2025-06 cycle). Every lookback window is therefore anchored to
that dataset's own most-recent record date, not today, and each anchor is
published on the row (violations_max_date, lien_sale_max_date,
evictions_max_date). The run log prints them too.
What v1.1 fixed (2026-08-01)
All five sources were re-probed live and the actor was rebuilt around what they actually return. Nothing was removed or renamed.
- The default run was reading 100,000 of 165,733 open Bronx violations. v1.0
paged at a hard 10 x 10,000 ceiling with no row-count reconciliation. Because
the read is ordered by
violationidascending, the missing 39.7% was the newest data - every open violation issued after 2026-05-04 was invisible. Effect on the shipped prefill: 25 of the 100 published leads did not belong in the top 100, 25 genuine top-100 leads were never seen, and all 75 survivors had an understated score (mean understatement 150 points). Worst case, 2338 Morris Avenue: published 66 open class-B violations against a true 244, and a score of 266 against a true 880. Every read now reconciles against thecount(*)the service itself reports and fails the run if it cannot. lien_water_debt_onlywas inverted on the freshest rows. The 2025 lien cycle emits the short tokensYandNalongsideYES/NO; v1.0 compared against the literal stringYES, so all 802Yrows nationwide (176 in the Bronx window alone) were published as "not water-debt-only" - the opposite of the truth, and it makes a weak signal look like a strong one.- A contacts outage was published as "no contact on file". The whole
enrichment sat inside one catch that emitted
owner_contact: nullfor every parcel. On a lead product that is a confident wrong answer. Rows now carrycontacts_statusandowner_contact_checked. - The owning LLC was fetched on every row and thrown away, along with the
managing agent, the title and the middle initial.
owner_corporationnow reads 98.0% populated on the prefill where it read 0.0%. - Class-I violations were silently discarded by a bare
else continue. residential_commercial_indwas fetched and dropped in the mapper.- Contact paging could starve a batch. One HPD registration carries 8,688
contact rows (registration 911741), so a 100-id batch under a flat
$limit=10000with no$ordertruncated non-deterministically. - A bad borough raised a bare module-level throw; it now fails loudly with an actionable message and bills nothing.
Honest notes (read before you buy)
- NYC-only. The five sources are NYC Open Data (Socrata).
- A distress score needs all three signal sources. A score built on two of them is not a smaller answer, it is a wrong one, and it is indistinguishable on the row from a parcel that genuinely has no lien and no eviction. So if any signal source fails, the run fails and bills nothing rather than publishing a partial score.
evictions_countis a floor. Roughly 5% of executed evictions carry no BBL in the city dataset and cannot be attributed to a parcel. The exact count for your run is published on every row asevictions_without_bbl_in_window.- Contact join path:
registrationidcarried on the HPD violation rows, with a fallback through HPD Registrations (tesw-yqqr) by borough/block/lot for parcels flagged only by liens or evictions. Buildings that never registered with HPD emit with a null contact and are never dropped. - Lien-sale data vintage: the DOF list updates only when the city runs a
lien-sale cycle; months-old
lien_sale_monthvalues between cycles are the source, not a bug. The run fails if that list is ever rolled back earlier than the 2025-06 cycle this build was verified against. - Tenant privacy: eviction records are used only as a per-parcel count. Respondent names, court index numbers, docket numbers and marshal names are never even requested. This is a landlord-side product.
- Violation counts cover open violations issued within the lookback window;
raise
lookbackMonthsto widen. - This is a screening tool, not a determination. Verify anything you act on against the city record.
Use as an MCP tool
This Actor is callable directly by any MCP-compatible AI agent through Apify's hosted MCP server. There is no server to run and no integration code to write - the tool schema an agent sees is generated from this Actor's own input and dataset schemas.
Endpoint
https://mcp.apify.com?tools=malonestar/distressed-property-signal-stacker
Claude Desktop, Claude Code or Cursor - add to claude_desktop_config.json,
.mcp.json or .cursor/mcp.json respectively:
{"mcpServers": {"apify": {"url": "https://mcp.apify.com?tools=malonestar/distressed-property-signal-stacker","headers": { "Authorization": "Bearer YOUR_APIFY_TOKEN" }}}}
Get a token at https://console.apify.com/settings/integrations. Claude Desktop can
also authenticate interactively via OAuth against https://mcp.apify.com with no
headers block. Full reference: https://docs.apify.com/platform/integrations/mcp
Try asking your agent
Find me 100 Detroit properties with at least two distress signals and give me the owner name and mailing address for each.
Chains well with - expose these alongside it by comma-separating the tools
parameter, and the agent can carry results from one into the next:
malonestar/parcel-owner-lookupmalonestar/acris-deed-transfer-intelmalonestar/absentee-owner-lead-list-builder
https://mcp.apify.com?tools=malonestar/distressed-property-signal-stacker,malonestar/parcel-owner-lookup,malonestar/acris-deed-transfer-intel,malonestar/absentee-owner-lead-list-builder
Billing is unchanged when called as an MCP tool: this Actor is Pay-Per-Event and an agent pays the same per-result price a human does. A run that cannot answer fails without billing rather than returning an unverified negative.
FAQ
How do I find distressed properties in NYC from free data sources? This actor does it for you: it merges the three public distress feeds per parcel and scores them, so you skip the joins, BBL normalization and cycle parsing.
Is this a foreclosure or pre-foreclosure list? No - it is upstream of foreclosure. Code violations, tax-lien-sale notices and evictions are the pressure signals that often precede a distressed sale.
How do I get the property owner's contact from a BBL? Each lead carries the best HPD registration contact, the owning corporation, the managing agent and up to 12 contacts in total, with business mailing addresses.
Can I run all five boroughs? Yes - run the actor once per borough. Per-borough runs keep results focused and your per-result spend predictable.
How is this priced?
Pay-per-result: you are charged only for the ranked parcel leads actually
emitted (cap with maxResults). Runs that fail a pre-flight check emit nothing
and bill nothing.
Related actors - the investor shelf
| Actor | What it adds | Where |
|---|---|---|
| Absentee Owner Lead List Builder | Out-of-state and entity owners straight off the assessor roll, before any violation exists | Cook County (Chicago), Philadelphia, NYC |
| Parcel Owner Lookup | Address to owner of record for one-off checks, with an explicit match-confidence on every answer | Cook County (Chicago), Philadelphia, NYC |
| NYC Landlord Registry Lead List | The owner's whole portfolio - filter to landlords holding 5-50 buildings, with business contact | NYC |
| NYC ACRIS Deed Transfer Intel | Who actually bought and sold - deed price, LLC-buyer, cash-sale and out-of-state-buyer flags | NYC |
Typical play: score distress here, then pull the owner's full portfolio to see
whether this is a one-off or a landlord under pressure across many buildings.
In Chicago and Philadelphia the assessor-roll actors above share the same parcel
keys this actor emits (cook_pin, phila_opa), so the two stack directly.