# Code Violations Scraper - Distressed & Motivated Seller Leads (`permitdata/us-code-violations-scraper`) Actor

Motivated-seller leads from city code violations: owner name + mailing address, absentee-owner, tax-delinquency & foreclosure flags, 0-100 distress score. 15 verified sources (Chicago, NYC, Philly, Detroit +11) checked daily. Source-direct government data from $3/1,000.

- **URL**: https://apify.com/permitdata/us-code-violations-scraper.md
- **Developed by:** [PermitData](https://apify.com/permitdata) (community)
- **Categories:** Real estate, Lead generation, Agents
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 violations

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 a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use 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

## US Code Violations Scraper: Distressed Property & Motivated Seller Leads

A code violation is the city telling you a property is in trouble, long before it hits
the MLS. This Actor watches code enforcement feeds across 15 verified city sources,
normalizes every record to one schema, scores it 0-100 for distress, and, where the
government publishes it, attaches the **property owner's name and mailing address**, an
absentee-owner flag, tax-delinquency balance, and foreclosure status. Built for
real-estate wholesalers, fix-and-flip investors, and agents hunting off-market deals.
It's driving for dollars without the driving, source-direct from official city open-data
APIs. No proxies, no fragile HTML scraping, no reseller feed that can vanish.

### Pick your lead list

| Lead list | How to get it |
| --- | --- |
| **Code violation property list** (any covered city) | default `scrape` mode, pick `sources` |
| **Distressed property list / motivated sellers** | `distressScoring: true`, `minDistressScore: 60`, `statuses: ["open"]` |
| **Absentee owner list** (owner mails out-of-state) | owner-data cities (Detroit, Cleveland, Baltimore) — filter `extra.absentee_owner` |
| **Vacant & condemned property list** | Baltimore vacant-building notices, Cleveland active condemnations, `category: vacant_abandoned` anywhere |
| **Tax-delinquent + violation stack** | Cleveland (county enrichment adds unpaid-tax balance + foreclosure flag) |
| **Demolition / unsafe structure list** | `categories: ["demolition", "unsafe_structure"]` |
| **Daily alert feed of new violations** | `monitor` mode on an Apify schedule + webhook |

### Why this one

- **Verified coverage, not claimed coverage.** Every listed city passes an automated
  daily canary check. If a source breaks, it gets flagged here and in your run's health
  report, so you never pay for a silently broken city. Each city shows its last-verified
  date below.
- **A distress score built for lead-gen.** Every case gets a transparent `distress_score`
  (0-100) with `distress_reasons`; higher means more likely a motivated seller (open case,
  severe category like demolition/unsafe/vacant, long-unresolved, absentee owner, unpaid
  taxes). Filter to `minDistressScore: 60` and `status: open` for a hot-lead list.
- **Owner data where it exists, free.** Six of the fifteen feeds carry the owner's
  name (five with mailing address) from government records, at no extra charge.
- **Honest schema.** A field a city doesn't publish is `null`, never guessed. The per-city
  matrix below tells you exactly what you'll get before you run.
- **Monitor mode.** Run it on a schedule and get only new cases since your last run:
  watermarked, deduplicated, webhook-ready.
- **Works with AI agents.** Callable via MCP from Claude, Cursor, or any MCP client.

### Who buys this

Real-estate wholesalers ("we buy houses"), fix-and-flip and buy-and-hold investors,
agents targeting distressed inventory, property-service contractors (repair/cleanup
work), and PropTech/insurance teams. Code violations are a leading signal of a property
in trouble, and of an owner who may sell below market.

### Coverage

| City | Status | Last verified | Refresh | Owner | Severity signal |
|---|---|---|---|---|---|
| Detroit, MI (Blight Violations) | ✅ verified | 2026-07-03 | daily | ✅ **name + mailing address** | absentee-owner flag |
| Philadelphia, PA (L&I Violations) | ✅ verified | 2026-07-04 | daily | ✅ **owner name** | ~2M records, OPEN/CLOSED |
| Chicago, IL (Building Violations) | ✅ verified | 2026-07-03 | daily | ❌ | OPEN/CLOSED status |
| New York City, NY (HPD housing) | ✅ verified | 2026-07-03 | ~weekly | ✅ **name (PLUTO)** | class A/B/C hazard |
| Cincinnati, OH (Code Enforcement) | ✅ verified | 2026-07-03 | daily | ❌ | status + type |
| San Francisco, CA (DBI complaints) | ✅ verified | 2026-07-04 | daily | ❌ | NOV type + status |
| Seattle, WA (SDCI code) | ✅ verified | 2026-07-04 | daily | ❌ | complaint vs violation |
| New Orleans, LA (Code Enforcement) | ✅ verified | 2026-07-04 | daily | ❌ | violation type |
| Los Angeles, CA (open code cases) | ✅ verified | 2026-07-04 | weekly | ❌ | all cases open/active |
| Austin, TX (Code Complaints) | ✅ verified | 2026-07-04 | daily | ❌ | status + repeat-offender flag |
| Baltimore, MD (vacant-building notices) | ✅ verified | 2026-07-04 | daily | ✅ **name + mailing address** | all open + vacant |
| Boston, MA (building & property violations) | ✅ verified | 2026-07-04 | daily | ✅ **name + mailing + assessed value** | OPEN/CLOSED + absentee |
| Cleveland, OH (B&H violation notices) | ✅ verified | 2026-07-04 | weekly | ✅ **name + mailing + tax data** | open + tax-delinquency |
| Cleveland, OH (active condemnations) | ✅ verified | 2026-07-04 | weekly | ✅ **name + mailing + tax data** | condemned + tax-delinquency |
| Columbus, OH (code enforcement cases) | ✅ verified | 2026-07-04 | daily | ❌ (parcel number included) | Active/status + type |
| More major cities | 🔜 rolling out | — | — | — | — |

**Detroit is the flagship native source**: the city itself publishes the property owner's
name and mailing address, so the feed flags absentee owners (owner mailing state ≠
property state), one of the strongest motivated-seller signals. Fine amounts and collection
status included.

New cities ship regularly. Want a specific city or county? Open an issue and it goes to
the top of the queue.

### Owner & financial-distress enrichment (absentee owners, unpaid taxes, foreclosure)

For supported cities, each record is joined to the **county assessor's public parcel
records** (on the parcel number the violation already carries) to add data the code
dataset itself doesn't publish:

- **Owner name and mailing address** → automatic absentee-owner flagging
  (owner mailing state ≠ property state).
- **Tax-delinquency balance and foreclosure status.** A property with an open
  violation, unpaid taxes, and an out-of-state owner is a prime lead, and the
  distress score reflects all three.

**Live now:** Cleveland (Cuyahoga County Fiscal Office: owner name, mailing address,
tax-delinquency, foreclosure; daily), New York City (Dept. of City Planning PLUTO:
owner name by tax lot/BBL), Baltimore (city real property: owner name, mailing
address, vacant indicator), and Boston (FY2026 Property Assessment, joined via the
city's live SAM address registry: owner name, owner mailing address, assessed value,
owner-occupied flag, plus the parcel ID backfilled onto each record). More cities and
counties are rolling out. Enrichment is on by default and adds no extra charge; turn it
off with `enrichOwners: false`. Owner data is public record, published by the government
for public use.

### PropStream / DealMachine / BatchLeads alternative?

If you're paying a monthly seat plus credits just to pull code violation and
distressed-property lists, this Actor replaces that slice of your stack with
source-direct government data:

| | This Actor | PropStream / BatchLeads / DealMachine |
| --- | --- | --- |
| Access | **API-first** (Apify, schedules, webhooks, MCP) | Web app / app, seat-based |
| Price | **$0.003/record, $0.02/scored lead**, no subscription | ~$60–120/mo + credits |
| Data path | **Source-direct** city & county open data | Aggregated third-party feeds |
| Coverage honesty | Per-city matrix + daily verified badge | Opaque |
| Owner phones | ❌ no skip-tracing (see FAQ) | Included at credit prices |

Honest trade-off: this Actor does **not** skip-trace phone numbers. It hands you the
owner's name and mailing address (where government records publish them), exactly the
inputs that make any skip-trace tool or direct-mail campaign work. Pair it with the
skip-tracer you already use and you're still paying a fraction of a per-credit platform.

### Output schema (one row per violation)

```json
{
  "violation_id": "chicago:7511764",
  "case_number": "7511764",
  "source": "chicago",
  "source_label": "Chicago, IL",
  "state": "IL",
  "status": "open",
  "status_raw": "OPEN",
  "category": "unsafe_structure",
  "categories_all": ["unsafe_structure", "vacant_abandoned"],
  "severity": "high",
  "violation_type_raw": "DANGEROUS AND UNSAFE - VACANT OPEN AND HAZARDOUS",
  "description": "…",
  "address": "4959 S BLACKSTONE AVE",
  "zip": null,
  "latitude": 41.804,
  "longitude": -87.590,
  "opened_date": "2026-07-01",
  "last_activity_date": "2026-07-01",
  "department": "BUILDING",
  "parcel_number": "20-11-114-001",
  "owner_name": null,
  "distress_score": 70,
  "distress_reasons": ["category:unsafe_structure(+40)", "multi:vacant_abandoned(+5)", "open(+25)"],
  "dedupe_key": "…",
  "first_seen_at": "2026-07-03T…Z"
}
````

On owner-data cities, `owner_name` is populated and `extra` carries
`owner_mailing_address` / `owner_mailing_state`, `absentee_owner`, and (Cleveland)
`tax_delinquent_balance` + `foreclosure` flags.

Categories: demolition, unsafe\_structure, vacant\_abandoned, fire, health\_safety,
vehicles, vegetation, junk\_debris, zoning, occupancy, property\_maintenance, other.
Classification is deterministic keyword matching on the violation type + description.
Reproducible, no LLM randomness.

### Distress score (how it's computed, no magic)

Starts from the violation category (demolition/unsafe/vacant = highest), then adds points
for: an open/unresolved case, a long-unresolved open case (older = more motivated),
NYC class C (immediately hazardous) / class B, multiple violation types at once, a
known owner, an absentee owner (+12), tax delinquency (+8, +12 above $1k/$5k), and
foreclosure (+15). Every row shows the exact breakdown in `distress_reasons`. Filter
with `minDistressScore` + `statuses: ["open"]`.

### Modes

- **Scrape**: pull a date range (`sinceDays` or explicit `sinceDate`/`untilDate`).
- **Monitor**: stateful, emits only cases you haven't seen, with a 3-day overlap re-check.
  Point a schedule + webhook at it and you have a distressed-property alert feed.
- **Canary**: free self-test of all sources; optionally posts the health summary to a
  Discord webhook. I run it daily myself.

### How to use

1. Click **Try for free** (first 25 records are free; no card needed on the free plan).
2. Pick your `sources` (start with an owner-data city: `detroit`, `cleveland`, or `baltimore`).
3. Set `sinceDays: 30`, `distressScoring: true`, `minDistressScore: 60`, `statuses: ["open"]`.
4. **Save & Start**, then download JSON/CSV/Excel from the Output tab, ready for your
   dialer, direct-mail list, or CRM.

From Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("permitdata/us-code-violations-scraper").call(run_input={
    "mode": "scrape",
    "sources": ["detroit", "cleveland", "baltimore"],
    "sinceDays": 30,
    "distressScoring": True,
    "minDistressScore": 60,
    "statuses": ["open"],
})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(row["distress_score"], row["address"], row.get("owner_name"))
```

### Pricing

- First **25 records free** (per account, ever).
- **$0.003 per violation** (raw normalized record), $3.00 per 1,000.
- **$0.02 per scored lead** when `distressScoring` is on. Replaces the per-violation
  charge, so you're never double-billed for the same row.
- Owner enrichment is free, with no per-lookup charge.

Example: a weekly Detroit + Cleveland + Baltimore pull of ~500 scored, filtered hot leads
costs about **$10**, less than most platforms charge for 100 credits.

### FAQ

#### How do I get a code violation list for my city?

If your city is in the coverage table, select it in `sources` and run. If it isn't, open
an issue; cities with open data get added quickly.

#### Can I get owner phone numbers?

No. This Actor does no skip-tracing. It returns the owner name and mailing address
where government records publish them, which is the input every skip-trace tool needs.
This keeps the data 100% public-record and keeps your compliance story clean.

#### What is an absentee owner and why does it matter?

An owner whose tax bill mails to a different state than the property. Combined with an
open violation, it's one of the strongest signals of a neglected property and a seller
open to an offer. The Actor flags this automatically on owner-data cities.

#### Can I schedule a daily or weekly motivated-seller feed?

Yes: use `monitor` mode with an Apify schedule and a webhook. You'll get only new
violations since the last run, deduplicated.

#### Is this legal to use?

The data comes from official government open-data portals published for public use under
open/public-domain licenses. Accessing public records for investment research is legal in
the US. See the FCRA disclaimer below; you're responsible for how you use the output.

#### Does it work with AI agents?

Yes, the Actor is callable via MCP from Claude, Cursor, or any MCP client, so an agent
can pull scored leads as a tool call.

### FCRA disclaimer

This Actor is intended for **real-estate purchase offers and investment research only**.
The output may **not** be used for tenant screening, credit decisions, employment
screening, insurance underwriting, or any other purpose regulated by the Fair Credit
Reporting Act (FCRA), 15 U.S.C. § 1681 et seq. You are solely responsible for your use of
the output.

### Fair use & data provenance

All data comes from official government open-data portals published for public use, under
open / public-domain licenses. No login-walled sources, no private databases, no
skip-tracing; owner data appears only where a government source itself publishes it.

Some sources carry a share-alike open license (e.g. Cleveland's datasets are under the Open
Data Commons Open Database License, ODbL); attribution to the originating city is preserved
in each record's `source` / `source_label` and `source_portal` fields.

### Support

Issues tab, or the contact form on my profile. Broken-source reports are typically fixed
within 24-48h; my own daily canary usually catches breakage before users hit it.

If this Actor helps you find deals, a quick review on this page helps other investors
find it, and city/county requests from reviewers get built first.

# Actor input Schema

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

scrape = pull a date range once. monitor = only NEW cases since your last run (stateful, deduped - ideal for schedules/webhooks). canary = coverage self-test, no charges.

## `sources` (type: `array`):

Which verified sources to pull. Leave empty for ALL verified cities.

## `categories` (type: `array`):

Only return cases in these categories. Empty = all. High-distress categories (best RE-investor leads) are demolition, unsafe\_structure, vacant\_abandoned.

## `statuses` (type: `array`):

Only return cases with these normalized statuses. Empty = all. 'open' = active/unresolved (best leads).

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

Only return cases at or above this distress score. 0 = all. Try 60+ for hot motivated-seller leads.

## `sinceDays` (type: `integer`):

Scrape mode: how many days back from today. Ignored if 'Since date' is set.

## `sinceDate` (type: `string`):

Scrape mode: explicit start date. Overrides look-back window.

## `untilDate` (type: `string`):

Scrape mode: explicit end date (inclusive). Default: today.

## `maxResultsPerSource` (type: `integer`):

Safety cap per source per run.

## `distressScoring` (type: `boolean`):

Every row always includes distress\_score + distress\_reasons. Turning this ON bills items as 'scored lead' events instead of raw 'violation' events - use it when you're pulling scored leads (see pricing).

## `enrichOwners` (type: `boolean`):

For supported cities (currently Cleveland), join the county assessor's public parcel records to add owner name, owner mailing address (absentee-owner flag), and tax-delinquency / foreclosure signals. Adds a little latency; leave on for the richest leads. No extra charge.

## `monitorStateName` (type: `string`):

Monitor mode: name of the key-value store holding watermarks. Change it to run independent monitors (e.g. one per client).

## `includeUnverified` (type: `boolean`):

Also run sources still marked pending verification. Off by default.

## `discordWebhookUrl` (type: `string`):

Canary mode: post the health summary to this Discord webhook.

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

Raises Socrata rate limits for very large backfills. Not needed for normal use.

## Actor input object example

```json
{
  "mode": "scrape",
  "sources": [],
  "categories": [],
  "statuses": [],
  "minDistressScore": 0,
  "sinceDays": 14,
  "maxResultsPerSource": 5000,
  "distressScoring": false,
  "enrichOwners": true,
  "monitorStateName": "code-violations-monitor",
  "includeUnverified": false
}
```

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("permitdata/us-code-violations-scraper").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("permitdata/us-code-violations-scraper").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 '{}' |
apify call permitdata/us-code-violations-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=permitdata/us-code-violations-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Code Violations Scraper - Distressed & Motivated Seller Leads",
        "description": "Motivated-seller leads from city code violations: owner name + mailing address, absentee-owner, tax-delinquency & foreclosure flags, 0-100 distress score. 15 verified sources (Chicago, NYC, Philly, Detroit +11) checked daily. Source-direct government data from $3/1,000.",
        "version": "0.1",
        "x-build-id": "qeJ2o4ZD5kM7z9OlN"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/permitdata~us-code-violations-scraper/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-permitdata-us-code-violations-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/permitdata~us-code-violations-scraper/runs": {
            "post": {
                "operationId": "runs-sync-permitdata-us-code-violations-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/permitdata~us-code-violations-scraper/run-sync": {
            "post": {
                "operationId": "run-sync-permitdata-us-code-violations-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "properties": {
                    "mode": {
                        "title": "Mode",
                        "enum": [
                            "scrape",
                            "monitor",
                            "canary"
                        ],
                        "type": "string",
                        "description": "scrape = pull a date range once. monitor = only NEW cases since your last run (stateful, deduped - ideal for schedules/webhooks). canary = coverage self-test, no charges.",
                        "default": "scrape"
                    },
                    "sources": {
                        "title": "Cities",
                        "type": "array",
                        "description": "Which verified sources to pull. Leave empty for ALL verified cities.",
                        "items": {
                            "type": "string",
                            "enum": [
                                "detroit",
                                "philadelphia",
                                "chicago",
                                "nyc_hpd",
                                "los_angeles",
                                "san_francisco",
                                "seattle",
                                "boston",
                                "austin",
                                "baltimore",
                                "new_orleans",
                                "cincinnati",
                                "cleveland",
                                "cleveland_condemned",
                                "columbus"
                            ],
                            "enumTitles": [
                                "Detroit, MI (blight + owner names)",
                                "Philadelphia, PA (L&I + owner names)",
                                "Chicago, IL",
                                "New York City, NY (HPD)",
                                "Los Angeles, CA",
                                "San Francisco, CA",
                                "Seattle, WA",
                                "Boston, MA (+ owner names)",
                                "Austin, TX",
                                "Baltimore, MD (vacant buildings + owner names)",
                                "New Orleans, LA",
                                "Cincinnati, OH",
                                "Cleveland, OH (violation notices + owner names)",
                                "Cleveland, OH (active condemnations + owner names)",
                                "Columbus, OH (code enforcement cases)"
                            ]
                        },
                        "default": []
                    },
                    "categories": {
                        "title": "Violation category filter",
                        "type": "array",
                        "description": "Only return cases in these categories. Empty = all. High-distress categories (best RE-investor leads) are demolition, unsafe_structure, vacant_abandoned.",
                        "items": {
                            "type": "string",
                            "enum": [
                                "demolition",
                                "unsafe_structure",
                                "vacant_abandoned",
                                "fire",
                                "health_safety",
                                "vehicles",
                                "vegetation",
                                "junk_debris",
                                "zoning",
                                "occupancy",
                                "property_maintenance",
                                "other"
                            ],
                            "enumTitles": [
                                "Demolition",
                                "Unsafe structure",
                                "Vacant / abandoned",
                                "Fire",
                                "Health & safety",
                                "Vehicles",
                                "Vegetation / weeds",
                                "Junk / debris",
                                "Zoning",
                                "Occupancy / no-permit",
                                "Property maintenance",
                                "Other"
                            ]
                        },
                        "default": []
                    },
                    "statuses": {
                        "title": "Status filter",
                        "type": "array",
                        "description": "Only return cases with these normalized statuses. Empty = all. 'open' = active/unresolved (best leads).",
                        "items": {
                            "type": "string",
                            "enum": [
                                "open",
                                "closed",
                                "other",
                                "unknown"
                            ],
                            "enumTitles": [
                                "Open / active",
                                "Closed / resolved",
                                "Other",
                                "Unknown"
                            ]
                        },
                        "default": []
                    },
                    "minDistressScore": {
                        "title": "Minimum distress score (0-100)",
                        "minimum": 0,
                        "maximum": 100,
                        "type": "integer",
                        "description": "Only return cases at or above this distress score. 0 = all. Try 60+ for hot motivated-seller leads.",
                        "default": 0
                    },
                    "sinceDays": {
                        "title": "Look-back window (days)",
                        "minimum": 1,
                        "maximum": 3650,
                        "type": "integer",
                        "description": "Scrape mode: how many days back from today. Ignored if 'Since date' is set.",
                        "default": 14
                    },
                    "sinceDate": {
                        "title": "Since date (YYYY-MM-DD)",
                        "type": "string",
                        "description": "Scrape mode: explicit start date. Overrides look-back window."
                    },
                    "untilDate": {
                        "title": "Until date (YYYY-MM-DD)",
                        "type": "string",
                        "description": "Scrape mode: explicit end date (inclusive). Default: today."
                    },
                    "maxResultsPerSource": {
                        "title": "Max results per city",
                        "minimum": 1,
                        "maximum": 200000,
                        "type": "integer",
                        "description": "Safety cap per source per run.",
                        "default": 5000
                    },
                    "distressScoring": {
                        "title": "Distress scoring (billing mode)",
                        "type": "boolean",
                        "description": "Every row always includes distress_score + distress_reasons. Turning this ON bills items as 'scored lead' events instead of raw 'violation' events - use it when you're pulling scored leads (see pricing).",
                        "default": false
                    },
                    "enrichOwners": {
                        "title": "Owner + tax-delinquency enrichment",
                        "type": "boolean",
                        "description": "For supported cities (currently Cleveland), join the county assessor's public parcel records to add owner name, owner mailing address (absentee-owner flag), and tax-delinquency / foreclosure signals. Adds a little latency; leave on for the richest leads. No extra charge.",
                        "default": true
                    },
                    "monitorStateName": {
                        "title": "Monitor state namespace",
                        "type": "string",
                        "description": "Monitor mode: name of the key-value store holding watermarks. Change it to run independent monitors (e.g. one per client).",
                        "default": "code-violations-monitor"
                    },
                    "includeUnverified": {
                        "title": "Include unverified sources (testing)",
                        "type": "boolean",
                        "description": "Also run sources still marked pending verification. Off by default.",
                        "default": false
                    },
                    "discordWebhookUrl": {
                        "title": "Discord webhook (canary alerts)",
                        "type": "string",
                        "description": "Canary mode: post the health summary to this Discord webhook."
                    },
                    "socrataAppToken": {
                        "title": "Socrata app token (optional)",
                        "type": "string",
                        "description": "Raises Socrata rate limits for very large backfills. Not needed for normal use."
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
