# Distressed Property Leads — Tax, Vacancy & Code Violations (`omegapointsolutions/distressed-property-leads`) Actor

Finds distressed real estate in official city and county open-data portals: tax-delinquent and tax-sale rolls, vacant and condemned building registries, demolitions, and open code violations. Merges every signal onto the same property and scores it, so you call the most motivated owners first.

- **URL**: https://apify.com/omegapointsolutions/distressed-property-leads.md
- **Developed by:** [Omega Point Solutions](https://apify.com/omegapointsolutions) (community)
- **Categories:** Real estate, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 leads

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/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

## Distressed Property Leads

Finds distressed real estate in official city and county open-data portals, merges
every signal onto the same property, and ranks the results so you call the most
motivated owners first.

Public government records only. No logins, no protected pages, no captcha solving,
no scraping of sites that forbid it.

### What counts as distress

| Signal | Meaning |
|---|---|
| `tax_sale` | The parcel went to a tax sale or scavenger sale |
| `tax_delinquent` | Property taxes unpaid, often with the amount owed |
| `condemned` | On a dangerous/condemned buildings list |
| `demolition` | Scheduled or completed blight demolition |
| `vacant` | Vacant/abandoned registry, abatement case, or 311 report |
| `code_violation` | Open building or code-enforcement violation |

### Why merging matters

One code violation is noise. A property carrying a vacancy registration *and* eight
open violations is a phone call. The Actor joins records across datasets on both
street address and parcel number, so signals that different agencies file under
different identifiers still land on the same property:

```json
{
  "distress_score": 61,
  "address": "6359 S HERMITAGE AVE",
  "jurisdiction": "Chicago",
  "signal_kinds": ["code_violation", "vacant"],
  "signal_count": 10,
  "score_reasons": ["vacant x2 (+26)", "code_violation x8 (+18)",
                    "2 independent signal types (+9)", "recency x1.15"]
}
```

Set `minSignalCount` to 2 to get only these stacked properties.

### Scoring

`distress_score` is 0–100 and is a **ranking aid, not a valuation**. Stronger signal
kinds weigh more, repeats of the same kind have diminishing returns, independent
signal types earn a stacking bonus, recent records outweigh stale ones, and large
sums owed add a little. Every lead ships with `score_reasons` so you can see the
arithmetic and disagree with it.

### Coverage

15 datasets across Chicago, Cook County, Cincinnati, Los Angeles, Kansas City,
Richmond, Norfolk, New Orleans, New York, Howard County MD, and Sonoma County CA —
roughly 5.6M source records. Every dataset in `src/sources.py` was probed live and
its column names read off a real record before being added.

Coverage is the honest limit of this Actor: it reaches the jurisdictions that publish
machine-readable open data, which is a minority of US counties. It does not cover
your county unless it is listed above.

### Input

| Field | Meaning |
|---|---|
| `states` | Two-letter codes to include. Empty = all. |
| `jurisdictions` | City/county names to include. Empty = all. |
| `signalTypes` | Which distress kinds to collect. Empty = all. |
| `maxRecordsPerSource` | Rows read per dataset (default 1000). |
| `minDistressScore` | Drop leads below this score. |
| `minSignalCount` | Require N records on one property. Use 2+ for a short list. |
| `maxResults` | Cap the output. 0 = no cap. |
| `socrataAppToken` | Optional free token; raises rate limits on big runs. |
| `requestDelaySeconds` | Politeness delay between paged requests. |

### Output

One record per property, sorted by score, with a `signals` array holding each
underlying government record and a link to its source dataset. A `RUN_REPORT` in
the key-value store lists rows read and any dataset that failed, so a partial run
is never silently presented as a complete one.

### Running it locally

```bash
pip install -r requirements.txt
python3 tools/smoke.py 300     # live end-to-end check, no Actor runtime needed
```

### Adding a jurisdiction

Add a `Source` to `src/sources.py` with the publisher domain, dataset id, and the
real column names, then re-run the smoke test. If a dataset reports `no usable rows`,
its address or parcel mapping is wrong — the row carried no locator the Actor could
key on.

Publishers rename columns without notice. Re-run the smoke test before trusting a
large run.

# Actor input Schema

## `states` (type: `array`):

Two-letter state codes to include, e.g. IL, OH. Leave empty to query every jurisdiction in the registry.

## `jurisdictions` (type: `array`):

City or county names to include, e.g. Chicago, Cook County. Leave empty for all jurisdictions in the selected states.

## `signalTypes` (type: `array`):

Which kinds of distress to collect. Leave empty for all of them.

## `maxRecordsPerSource` (type: `integer`):

How many rows to read from each dataset before moving on. Higher values find more properties and cost more to run.

## `minDistressScore` (type: `integer`):

Drop properties scoring below this (0-100). Around 40 keeps only properties with strong or stacked signals.

## `minSignalCount` (type: `integer`):

Require at least this many distress records on the same property. Set to 2 or more for a short, high-conviction list.

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

Cap the number of scored properties in the output. 0 means no cap.

## `socrataAppToken` (type: `string`):

Your own free Socrata application token. Not required, but it raises the per-publisher rate limit on large runs.

## `requestDelaySeconds` (type: `number`):

Pause between paged requests to stay polite to the data publishers.

## `requireAddress` (type: `boolean`):

Drop leads that end up with no street address and no coordinates. Some datasets publish only a parcel number; those rows merge with addressed rows when possible, and any that remain unlocatable cannot be mailed, visited or skip-traced. Turn off only if you work parcel numbers directly.

## Actor input object example

```json
{
  "states": [
    "IL"
  ],
  "maxRecordsPerSource": 1000,
  "minDistressScore": 0,
  "minSignalCount": 1,
  "maxResults": 0,
  "requestDelaySeconds": 0.2,
  "requireAddress": true
}
```

# 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 = {
    "states": [
        "IL"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("omegapointsolutions/distressed-property-leads").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 = { "states": ["IL"] }

# Run the Actor and wait for it to finish
run = client.actor("omegapointsolutions/distressed-property-leads").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 '{
  "states": [
    "IL"
  ]
}' |
apify call omegapointsolutions/distressed-property-leads --silent --output-dataset

```

## MCP server setup

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

```

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/ynP0aOMrgh9OYx2rM/builds/EaxffoYesjeUKVhrh/openapi.json
