# Property Tax Disparity — Assessed Value vs Sale Price (`omegapointsolutions/property-tax-disparity`) Actor

Measures property tax assessment fairness from official county and state open data. Compares assessed values against actual recorded sale prices using the IAAO sales-ratio method, returning median ratio, COD, PRD and PRB with pass/fail thresholds. Covers 252 jurisdictions across IL, MD, CT and NY.

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

## Pricing

from $20.00 / 1,000 record delivereds

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

## Property Tax Disparity — Assessed Value vs Sale Price

Measures whether lower-value homes are assessed at a higher share of what they
actually sell for than higher-value homes — the property tax disparity — using the
**IAAO sales-ratio method** over official county and state open data.

The "market value" side of every ratio is a **recorded sale price from the
jurisdiction's own records**. No automated valuation model, no Zestimate, no
commercial valuation service. That matters twice: nothing here depends on another
company's terms of service, and a recorded sale price is evidence an assessment
appeal or a newsroom can actually stand behind. An AVM estimate is not.

### What you get

Per county, an IAAO ratio study:

| Measure | What it means | IAAO standard |
|---|---|---|
| **Median ratio** | Level of assessment | 0.90 – 1.10 |
| **COD** | Horizontal equity — are similar homes treated alike? | 5–15 homogeneous, 5–20 heterogeneous |
| **PRD** | Vertical equity — > 1.03 is regressive | 0.98 – 1.03 |
| **PRB** | Vertical equity by regression, robust to outliers | −0.05 – +0.05 |

Plus sale-price quintiles with the median ratio in each (the disparity, shown
directly), pass/fail on every threshold, and a plain-language verdict.

Set `output` to `parcels` to get every individual sale with its own ratio.

### Coverage — stated honestly

There are about **3,144 county-equivalents** in the US, and property tax is levied
locally in **all 50 states**. This Actor covers **252 jurisdictions across 4 states**:

| Source | Level | Jurisdictions | Notes |
|---|---|---|---|
| `maryland` | parcel | 24 | All 23 counties + Baltimore City, SDAT |
| `connecticut` | parcel | 169 | All towns, 1.14M sales 2001–2023 |
| `new-york` | jurisdiction | 58 | State's own municipality equity survey |
| `cook-county-il` | parcel | 1 | Chicago; assessment + sales joined on PIN |

**Why not all 3,144?** Because the data does not exist in machine-readable form for
most of them. A sales-ratio study needs two things published together: assessed
values *and* actual sale prices. A sweep of the Socrata open-data network across 12
query terms found only 15 domains publishing both, and most were a single city. The
majority of US counties publish assessments behind a parcel-lookup web form with no
bulk export and no sale prices at all, and many states treat sale prices as
non-public entirely (non-disclosure states such as TX, UT and MO).

Coverage grows by adding verified entries to `src/registry.py`. Every entry there
was queried live before being listed — a promising dataset *name* in a catalog is
not evidence it carries a sale price.

### Why a registry and not a loop

Three things differ per jurisdiction, and getting any of them wrong produces a
confidently wrong number:

1. **Level of assessment.** Cook County assesses class-2 residential at 10% of
   market value, Maryland at 100%, Connecticut at 70%. Comparing a raw assessed
   value to a sale price without dividing by the LOA is off by an order of magnitude.
2. **The arm's-length screen.** Ratio studies are only valid over open-market sales.
   Cook ships boolean filter flags; Maryland encodes it as English prose in
   `how_conveyed` (902k of its transfers are explicitly non-arm's-length); Connecticut
   ships neither. Leave quitclaims and $1 family transfers in and you manufacture
   fake regressivity at the low end.
3. **Shape.** Cook needs a PIN join across two datasets; MD and CT carry assessment
   and sale price on the same row; NY publishes no parcel sale price at all, so it
   returns the state's own municipality-level equity survey instead.

### Methodological caveats

These are surfaced rather than buried, because a buyer who misreads them publishes
something wrong:

- **Connecticut median ratios run low (~0.6–0.8).** CT towns revalue on a 5-year
  cycle, so an assessment can be 4 years stale against a current sale. In a rising
  market that depresses the median for reasons that are not inequity. COD, PRD and
  PRB are computed *within* a town and remain valid; read the median as a
  revaluation-lag signal.
- **Cook County's county-wide COD runs high (~29).** Cook is one county spanning 38
  townships with very different markets, and mixing them inflates dispersion.
  Township is carried on every parcel record so you can stratify.
- **Minimum sample.** Counties with fewer than 30 arm's-length sales in the year are
  skipped, not reported thin. IAAO sets 30 as the floor.
- **PRD and PRB can disagree.** PRD is a ratio of two means that a few extreme sales
  can swing; PRB fits the trend across the distribution. When they disagree the
  verdict says so and defers to PRB rather than picking the louder number.

### Network routing

Portals disagree about which IPs they trust, and not consistently with each other:
Cook County serves a datacenter IP fine but **403s the residential proxy**, while
Maryland does the exact opposite. Pinning either route breaks whichever source
disagrees. Every request therefore tries **direct first and falls back to the proxy
on 403/429**. Supply a free `socrataAppToken` to lift the anonymous rate limit on
large runs.

### Sources

All public, all official:

- Cook County Assessor — `datacatalog.cookcountyil.gov` (`uzyt-m557`, `wvhk-k5uv`)
- Maryland SDAT / MD Planning — `opendata.maryland.gov` (`ed4q-f8tm`)
- Connecticut OPM — `data.ct.gov` (`5mzw-sjtu`)
- NY Dept. of Taxation and Finance — `data.ny.gov` (`4sut-q3dt`)

Schemas verified live 2026-08-08.

### Who buys this

Assessment appeal firms and property tax consultants, civil-rights and housing
policy researchers, investigative newsrooms, county boards reviewing their own
assessor, and law firms building equal-protection claims on assessment uniformity.

# Actor input Schema

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

Which sources to analyze. Use "all" for every verified jurisdiction. Options: cook-county-il (1 county, parcel level), maryland (24 jurisdictions, parcel level), connecticut (169 towns, parcel level), new-york (58 counties, state equity survey).

## `year` (type: `string`):

Calendar year of sales to analyze. Cook County and Maryland carry 2019-2026; Connecticut list years run 2001-2023. Defaults to 2023, the most recent year present in every parcel-level source.

## `output` (type: `string`):

"summary" returns one IAAO ratio study per county (the headline product). "parcels" returns every individual sale with its ratio. "both" returns both.

## `maxRecords` (type: `integer`):

Hard cap on delivered (and billed) records. Summary output is small — a few hundred records covers every jurisdiction. Raise this only when requesting parcel-level output.

## `maxParcelsPerJurisdiction` (type: `integer`):

How many arm's-length sales to pull per source before computing statistics. Larger samples give tighter COD and PRB estimates but take longer. IAAO requires at least 30 sales per county for a valid study. 5000 is enough for a stable statewide read; raise it for per-county precision.

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

Optional free Socrata application token. Lifts the strict anonymous rate limit on the open-data portals and reduces throttling on large runs. Get one at any Socrata portal under Developer Settings.

## `proxyGroups` (type: `array`):

Apify Proxy groups to route requests through. Maryland's portal returns 403 to datacenter IPs, so RESIDENTIAL is the default. Set to \["DATACENTER"] to run cheaper if your account lacks residential proxy access.

## `homogeneousMarket` (type: `boolean`):

Sets the IAAO COD acceptance ceiling: 15 for homogeneous single-family areas, 20 for heterogeneous or rural ones. Only affects the pass/fail flag, never the computed statistics.

## Actor input object example

```json
{
  "jurisdictions": [
    "all"
  ],
  "year": "2023",
  "output": "summary",
  "maxRecords": 5000,
  "maxParcelsPerJurisdiction": 5000,
  "proxyGroups": [
    "RESIDENTIAL"
  ],
  "homogeneousMarket": 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 = {
    "jurisdictions": [
        "all"
    ],
    "year": "2023"
};

// Run the Actor and wait for it to finish
const run = await client.actor("omegapointsolutions/property-tax-disparity").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 = {
    "jurisdictions": ["all"],
    "year": "2023",
}

# Run the Actor and wait for it to finish
run = client.actor("omegapointsolutions/property-tax-disparity").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 '{
  "jurisdictions": [
    "all"
  ],
  "year": "2023"
}' |
apify call omegapointsolutions/property-tax-disparity --silent --output-dataset

```

## MCP server setup

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

```

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/9SezvAIV1aneLJbSe/builds/KMPGgMHFasbP8nSyS/openapi.json
