US Distressed Property Scraper - Violations & Foreclosures
Pricing
from $1.80 / 1,000 distressed property records
US Distressed Property Scraper - Violations & Foreclosures
Scrape US distressed property leads from Socrata open data across Chicago, New York, Seattle and New Orleans. Extract building violations, vacant properties, tax sales and foreclosure records. Supports resume and incremental change tracking for ongoing property monitoring.
Pricing
from $1.80 / 1,000 distressed property records
Rating
0.0
(0)
Developer
Abot API
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
a day ago
Last modified
Categories
Share
US Distressed Property Scraper
US municipal distressed property signals in one run: building violations, vacant buildings, tax sales and foreclosures across Chicago, New York City, Seattle and New Orleans, read from Socrata open data. Pick metros or paste dataset links, and track what changed between scheduled runs.
Why This Scraper?
- Four metros, one actor. Walk Chicago building violations, vacant buildings and Cook County tax sales; New York building, housing and ECB violations plus tax-lien sales; King County foreclosure parcels; New Orleans sheriff lien-foreclosure sales. Every record comes back normalized to one schema.
- Signal-level rows. Each violation, lien and sale row is its own record with native identity, dates, status, parties where published, fines and balances - the same granularity distress buyers work with.
- Open data, no walls. Every lane reads the municipalities' own public JSON APIs at a polite pace, most recent first, with an optional recency cutoff.
- Made for recurring monitoring. Resume an interrupted run from its ID, or let incremental mode remember what you already scraped and return only NEW and UPDATED records, with EXPIRED markers when a record leaves the source.
- Search by metro or paste links. Walk whole metros, override with any Socrata dataset id, or paste Socrata API links and let URL mode sort them out.
- Honest by design. If a source refuses every request the run fails loudly instead of returning an empty dataset that looks like "no results". A genuine empty search says so.
Use Cases
- Who: Investors and buyers hunting below-market property across US code-enforcement and tax-sale channels.
- Who: Compliance and risk teams monitoring building violations and vacant stock by metro on a schedule.
- Who: Lead-generation teams feeding distressed-property signals into CRM systems and alerting pipelines.
- Who: Analysts comparing municipal distress volumes across Chicago, New York, Seattle and New Orleans from one dataset.
Data You Get
Sample shape: values are illustrative placeholders, not from a live record.
| Field | Example |
|---|---|
recordId | "us:socrata:22u3-xenr:7000000" |
country | "us" |
source | "chicago-violations", "nyc-taxlien", "nola-sheriff" and 7 more |
title | "123 Sample St MAINTAIN WINDOW" |
distressType | "violation", "vacant", "tax-sale" or "foreclosure" |
portalId | "7000000" |
publishedAt | "2026-10-09T00:00:00.000" |
auctionDate | "2026-10-30T10:00:00.000" |
location.address / location.city / location.region / location.zip | "123 Sample St" / "Chicago" / "IL" / "60601" |
streetNumber / streetName / city / state / postalCode | top-level address parts, "123" / "Sample St" / "Chicago" / "IL" / "60601" |
latitude / longitude | 41.73 / -87.61 |
sourceDomain / datasetId / sourceRecordId | "data.cityofchicago.org" / "22u3-xenr" / "7000000" |
deadlineAt | compliance or hearing deadline where published |
attachments | links published with the record, when the source has any |
valueAmount / valueCurrency / valueText | 2500 / "USD" / "$2,500" |
status | "OPEN", "sold", "Pending" |
parties | [{ "role": "respondent", "name": "Sample Respondent" }] |
description | violation text, ordinance, sale caption |
details | dataset id, metro, coordinates, codes, inspections, fines, hearings |
changeType / changedFields / firstSeenAt / lastSeenAt | incremental-mode metadata |
url | source dataset API URL |
How to Use
- Pick a Mode:
search(default) walks the selected metros (or the dataset override);urlreads only the links you paste. Links you paste are ignored in search mode, and the run log says so (the untouched pre-filled examples are not warned about). - In search mode, pick your metros (or leave the default walk of all four) and optionally set a recency cutoff in days. In url mode, paste Socrata API links.
- Set Max items, then click Start.
- Download the dataset as JSON, CSV or Excel, or read it through the API.
Chicago walk, recent signals:
{"mode": "search","metros": ["chicago"],"sinceDays": 7,"maxItems": 50}
Single-dataset override:
{"mode": "search","datasetDomain": "data.cityofnewyork.us","datasetId": "9rz4-mjek","maxItems": 50}
URL mode:
{"mode": "url","urls": ["https://data.cityofchicago.org/resource/22u3-xenr.json?$limit=2&$order=:id"],"maxItems": 20}
URL mode: supported link shapes
| # | Shape | Example | Notes |
|---|---|---|---|
| 1 | Resource JSON | https://data.cityofnewyork.us/resource/9rz4-mjek.json?$limit=2 | Reads rows from the JSON API. |
| 2 | Resource JSON with $order and $offset | https://data.cityofchicago.org/resource/22u3-xenr.json?$limit=2&$offset=2&$order=:id | Your $order and $offset are honoured. |
| 3 | Resource JSON with a $where filter | https://data.cityofchicago.org/resource/22u3-xenr.json?$where=violation_date>'2026-01-01'&$limit=1 | Your $where is combined with the dataset's own filter. |
| 4 | .gov domain | https://datacatalog.cookcountyil.gov/resource/55ju-2fs9.json?$limit=1 | Any .gov Socrata domain works. |
| 5 | County open-data portal | https://data.kingcounty.gov/resource/nx4x-daw6.json?$limit=1 | Same API, different domain. |
| 6 | City open-data portal | https://data.nola.gov/resource/d52w-8nva.json?$limit=1 | Same API, different domain. |
| 7 | Resource CSV form | https://data.cityofchicago.org/resource/22u3-xenr.csv?$limit=2 | Read through the JSON API; rows are identical. |
| 8 | Human dataset page /d/<id> (also /dataset/<name>/<id>) | https://data.cityofchicago.org/d/22u3-xenr | Mapped to the JSON API for that dataset. |
| 9 | Legacy /api/views/<id>/rows.json | https://data.cityofchicago.org/api/views/22u3-xenr/rows.json?$limit=1 | Mapped to the JSON API; the large legacy payload is never downloaded. |
A pasted $limit caps the rows read from that link. An unsupported link gets a loud warning in the run log and the remaining links continue; the run fails only when no link is usable.
Behaviour change: the URLs field now ships pre-filled with one example of each shape above as its default. In URL mode with the field untouched, the run scrapes those nine examples (capped by Max items). Search mode ignores the field, so existing search runs are unchanged. Clear the field and paste your own links to scrape something else.
Run it from your code
Python:
from apify_client import ApifyClientclient = ApifyClient("YOUR_APIFY_TOKEN")run = client.actor("abotapi/distressed-us-scraper").call(run_input={"metros": ["chicago"],"sinceDays": 7,"maxItems": 50,})for item in client.dataset(run["defaultDatasetId"]).iterate_items():print(item["recordId"], item["title"], item.get("valueAmount"))
JavaScript:
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });const run = await client.actor('abotapi/distressed-us-scraper').call({metros: ['nyc'],sinceDays: 7,maxItems: 50,});const { items } = await client.dataset(run.defaultDatasetId).listItems();for (const item of items) console.log(item.recordId, item.title);
Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.
Resume and recurring updates
- Resume (
resumeFromRunId) continues one interrupted run: paste its run ID and the actor skips everything already collected there, so you don't pay twice. - Incremental mode (
incrementalMode) is for scheduled runs over the same scope. Each record is classifiedNEW,UPDATED(withchangedFields),UNCHANGED(suppressed and not billed unlessemitUnchangedis on) orEXPIRED(only after a run that scanned the whole scope, and only withemitExpired). UsestateKeyto share one baseline across slightly different filter variants, andignoreFieldsForChangesto exclude noisy fields from the comparison. The two features are mutually exclusive; pick one per run.
Send results into your apps (MCP connectors)
Optionally send a summary of each returned record into an app connected through Apify MCP integrations. Select one or more MCP connectors and, for Notion, provide the parent page URL or ID. The actor creates a Notion page per record; other connectors receive a best-effort write or digest. Exports are capped by Max items to export per connector (50 by default), and connector failures do not change dataset output.
| Field | What it does |
|---|---|
mcpConnectors | Selected MCP connectors (empty = no export). |
notionParentPageUrl | Notion parent page URL or ID; required for Notion exports. |
maxNotifyListings | Maximum records sent to each selected connector (default 50, maximum 1000). |
Input Parameters
All parameters are optional unless noted.
| Parameter | Type | Default | Description |
|---|---|---|---|
mode | string | search | search walks the metro registers (or the dataset override); url reads only the pasted links. |
metros | array | all 4 metros | Metro registers to walk, in order: chicago, nyc, seattle, new-orleans. Search mode only. |
datasetId | string | (none) | Walk exactly one Socrata dataset id instead of the metro lists. Search mode only. |
datasetDomain | string | (none) | Socrata domain for the dataset override. |
sinceDays | integer | 30 | Only rows newer than this many days ago (date-filtered sets); 0 = no cutoff. |
dateFrom | string | (none) | Absolute start of the date window on each dataset's date column. Replaces sinceDays when set. Undated datasets (King County foreclosures) ignore it. |
dateTo | string | (none) | Absolute end of the date window (inclusive). Undated datasets ignore it. |
soqlWhere | string | (none) | Raw SoQL $where clause, combined with every walked dataset's own filter, e.g. violation_status = 'OPEN'. Column names are dataset-specific, so use it with a single metro or a dataset override. |
sortOrder | string | newest | newest or oldest: which end of each dataset is walked first. |
maxItemsPerDataset | integer | 0 | Stop each dataset after this many rows so one big register cannot use up Max items; 0 = no per-dataset cap. |
urls | array | one example of each of the 9 link shapes | Pasted Socrata links (API, CSV, dataset page, legacy rows); each is walked as its own scope. Read in url mode. |
maxItems | integer | 50 | Total result cap across all metros and links (0 = no limit; wide metros hold hundreds of thousands of rows, so set a number you are happy to pay for). |
maxPages | integer | 0 | Page cap per dataset walk; 0 = until natural end. |
proxy | object | standard pool | Connection settings (see Plan Requirement). |
resumeFromRunId | string | (none) | Continue a previous run; only unpushed records are saved. |
incrementalMode | boolean | false | Track changes across scheduled runs; only NEW/UPDATED returned. Mutually exclusive with resumeFromRunId. |
stateKey | string | (none) | Named incremental baseline; derived from the scope when empty. |
emitUnchanged | boolean | false | Also return UNCHANGED records in incremental mode. |
emitExpired | boolean | false | Also return EXPIRED records in incremental mode. |
ignoreFieldsForChanges | array | (none) | Extra fields that never count as a change. |
mcpConnectors | array | (none) | Optional MCP connectors to receive returned record summaries after the run. |
notionParentPageUrl | string | (none) | Parent page URL or ID required for Notion page creation. |
maxNotifyListings | integer | 50 | Maximum records sent to each connector (1-1000). |
Output Example
The Output tab offers two tables: Overview (one row per record) and Changes (change type, changed fields, first and last seen, for incremental runs).
Sample shape: values are illustrative placeholders, not from a live record.
Example record (Chicago building violation):
{"recordId": "us:socrata:22u3-xenr:7000000","country": "us","source": "chicago-violations","title": "123 Sample St MAINTAIN WINDOW","distressType": "violation","portalId": "7000000","publishedAt": "2026-10-09T00:00:00.000","location": { "address": "123 Sample St", "city": "Chicago", "region": "IL", "country": "US" },"valueAmount": 2500,"valueCurrency": "USD","valueText": "$2,500","status": "OPEN","description": "MAINTAIN WINDOW Failed to maintain windows in sound condition and good repair.","details": {"dataset": "22u3-xenr","metro": "chicago","lat": 41.73,"lng": -87.61,"violation_code": "CN000000","inspection_number": "14000000","inspection_status": "FAILED"},"url": "https://data.cityofchicago.org/resource/22u3-xenr.json","raw": {}}
Plan Requirement
The open data APIs are public and the actor reads them through the Apify datacenter connection, so the default proxy setting works on any plan that includes proxy access; no residential proxy is needed. If the default connection is refused for your account (a CONNECT-level refusal, as on free plans), the actor switches to a backup connection and logs: "The selected connection is unavailable to this run. Apify free plans do not include residential proxies. Continuing on a backup connection; some pages may still be unavailable." Pages served that way are noted in the log as "Served over the backup connection". If every connection is refused, the run fails with a message that names the cause ("The proxy connection was refused for this account ... a paid plan is required"); if the backup also cannot load the pages, it fails with "No results: residential proxies are unavailable on this run ... Upgrade ... or supply your own proxy URLs". If no page loads for any other reason, the run fails with "Could not load any results page ... This is a connection problem, not an empty search." Your own proxy URLs are used as given, with no backup. A run never reports an empty dataset when nothing could be read.
Very large unfiltered Socrata queries can be slow upstream; the actor always orders its queries and reads pages of up to 500 rows, with a per-request time limit.
FAQ
How much does it cost?
You pay per record returned plus the actor start. There is no detail-enrichment surcharge: every row ships complete from the source API. The events are the actor start and each dataset item; there are no monthly fees, and UNCHANGED records in incremental mode are suppressed and not billed unless emitUnchanged is on (then each returned record is a billed dataset item). See the Pricing tab for the current per-event prices.
Is it legal to scrape this data?
These are public municipal open-data APIs published for unrestricted reuse. The actor reads the same JSON endpoints a browser or script would, at a polite pace with paging, and links every record back to its source dataset.
Can I get only new or changed records on a schedule?
Yes. Use incremental mode: the actor remembers its baseline per scope and returns only what changed (NEW, UPDATED), with optional EXPIRED markers for records that left the source. Use stateKey to share one baseline across slightly different filter variants.
Why does a run return fewer records than I expected?
Max items (50 by default) stops the run once the cap is reached, walking the selected metros in order; raise it or set it to 0 to collect everything. The recency cutoff (sinceDays) also hides older rows on date-filtered datasets; raise it or set it to 0 for the full history.
How current is each source?
Chicago violations, New York housing, building and ECB violations are updated daily. Chicago vacant buildings and New York tax-lien sales are updated a few times a year, so a short recency cutoff can return nothing for them. The Cook County tax-sale lists (last sale year 2014 and 2015) and the New Orleans sheriff lien-foreclosure sales (cases up to 2018) are historical archives their cities no longer update, and the King County foreclosure list holds parcel numbers only, with no address or date. Use a larger recency cutoff, or 0, to see them.
Why did my run fail instead of returning an empty dataset?
A source refused every request in that run, or no page could be loaded (rate limiting, an outage, or a proxy refusal; the error message says which). The run is failed loudly rather than returning an empty dataset that would look like "no results". Wait and rerun.
Can I use it with AI agents or MCP?
Yes, the run output is a clean JSON dataset that works with MCP connectors and any AI workflow. Use the mcpConnectors input to send results straight into your apps after each run.
🔗 Want more Distressed data?
Pair this actor with these related scrapers from the same team:
| 🧩 Distressed Property Scraper Find mortgagee-sale and distressed-property listings in Australia and New Zealand. NZ... | 🏠 Europe Distressed Property Scraper Distressed-property leads across Europe: German ZVG foreclosure auctions, Polish KRZ... |
| 🏠 Canada Distressed Property Scraper Canadian tax-sale properties from taxsales.ca: tender and auction listings across 9... | 🧩 Homes.com Scraper Scrape US property records from Homes.com. Pick a city, state or ZIP and a channel (for... |
| 🧩 Propwire Scraper Scrape Propwire.com: 157M+ US MLS & off-market properties with owner names, mailing... | 🏠 StreetEasy Scraper Scrape StreetEasy sales, rentals and in-contract listings across NYC and Jersey City... |
💬 Support
- 🐞 Found a bug or a missing field? Open a ticket on the Issues tab. We usually reply within hours.
- 🛠️ Need another site, extra fields or a private build? Email contact@abotapi.com.
- ⭐ Enjoying it? A quick review on the actor page helps other users find it.