# Cook County Parcel Sales - Buyer & Seller Names (`j0401/cook-parcel-sales`) Actor

2.69M recorded parcel sales in Cook County, Illinois (public open data, 1971-present): sale date, price, deed type, and BOTH named parties - seller (grantor) and buyer (grantee). Search by buyer, seller, PIN, township or price; filter to arms-length comparables; aggregate by class or township.

- **URL**: https://apify.com/j0401/cook-parcel-sales.md
- **Developed by:** [Wenhao Yang](https://apify.com/j0401) (community)
- **Categories:** Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.03 / 1,000 cook county parcel sale records

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Cook County Parcel Sales - Buyer & Seller Names

Every time a parcel sells in Cook County, the deed is recorded and the Assessor keeps the record: the date, the price, the kind of deed - and **both parties by name**. This actor turns that register into a **charged-per-record search, filter and aggregate tool** over all **2,686,086 recorded sales**, covering **1,334,404 distinct parcels**.

**Built for:** comparable-sales work with real names attached, ownership-chain tracing, buyer and seller research, portfolio and investor mapping, and anyone who needs to ask **who bought what, from whom, for how much** in Cook County without paging a recorder's viewer.

### What it covers

Each sale carries:

- **the two named parties** - seller (grantor) and buyer (grantee), as the Assessor recorded them
- **the transaction** - sale date, consideration, the recorded document number
- **the deed** - the Assessor's deed type and, where present, the recorder's own deed wording
- **the parcel** - 14-digit PIN, township code, neighborhood code, property class
- **the Assessor's own comparability flags** - see below

A row is a **sale, not a parcel**. The file carries 1,334,404 distinct PINs across 2,686,086 sales - a parcel appears once per time it sold, about twice on average. Search a PIN to get that parcel's full sale history.

### The fine print that matters

**The Assessor publishes its own comparability screen, and this actor exposes it.** Three boolean columns mark the sales that are *not* clean market transactions:

| Flag | Rows | What it marks |
|---|---|---|
| `sale_filter_same_sale_within_365` | 13,375 | the same parcel sold again within a year - a flip, not a market price |
| `sale_filter_less_than_10k` | 215,686 | consideration under $10,000 |
| `sale_filter_deed_type` | 121,348 | a deed type the Assessor does not treat as a market sale |

**2,409,818 of the 2,686,086 sales carry all three flags false.** Setting `onlyComps=true` returns exactly those. That single flag is the difference between a comparable set and a raw deed dump - a median drawn over the unfiltered file is averaging in foreclosures, family transfers and $1 quit-claims.

**Not every sale names both parties.** The buyer name is blank on **165,739 rows (6.2%)** and the seller name on **182,619 (6.8%)** - the Assessor did not record the party. A name search cannot find those sales, and they are returned with the name field empty rather than dropped.

**Multi-parcel conveyances repeat across rows.** A sale covering several parcels appears once per parcel, sharing one document number, with `isMultisale=true` on **565,374 rows**. It is returned so a comp run can exclude them (`multisale=false` leaves 2,120,712).

**`sale_type` is blank on 511,936 rows (19%).** That is not a defect - it is the Assessor not classifying the sale as `LAND` or `LAND AND BUILDING`. `deed_type` is blank on just 1,159.

### Typical questions

- "Every **sale of a given parcel** by PIN - the full ownership chain."
- "What did **<buyer>** buy in Cook County, and for how much?"
- "Who sold to **<seller>**?"
- "**Arms-length comparables** over $400,000 in the last 12 months."
- "Recent sales in a **township** or by **property class**."
- "Sales that were **part of a multi-parcel conveyance**."
- "Aggregate by **deed type**, **property class**, **township**, **sale type** or **year**."

### Inputs

| Input | What it does |
|---|---|
| `mode` | `rows` (default) / `aggregate` |
| `pin` | exact 14-digit parcel index number |
| `buyer` / `seller` | name substring on the grantee / grantor |
| `township` / `propertyClass` | exact township code / property class code |
| `deedType` / `saleType` | exact deed type / land-only vs land-and-building |
| `saleFrom` / `saleTo` | sale-date range (inclusive) |
| `minPrice` / `maxPrice` | consideration range |
| `onlyComps` | `true` applies the Assessor's comparability screen |
| `multisale` | `false` excludes multi-parcel conveyances |
| `groupBy` | aggregate over township / propertyClass / deedType / saleType / saleYear |
| `maxResults` | cap records (default 50) |

**Default run = 50 sales, newest first** - a plain read of the register, fast enough for the daily auto-test. For a targeted query add a filter; for a broad view use `aggregate`.

### Example inputs

**One parcel's sale history** - every recorded sale of that PIN.

```json
{ "pin": "17221101001199" }
```

**Arms-length comparables in a date window** - `onlyComps` applies the
Assessor's three-flag screen.

```json
{ "saleFrom": "2025-10-01", "saleTo": "2026-09-27", "minPrice": 400000, "onlyComps": "true", "maxResults": 5 }
```

**What a buyer purchased**

```json
{ "buyer": "LLC", "maxResults": 5 }
```

**Sales by an entity that sold** - e.g. a trustee unwinding an estate.

```json
{ "seller": "TRUSTEE", "maxResults": 5 }
```

**How the register splits by deed type** - one row per deed type.

```json
{ "mode": "aggregate", "groupBy": "deedType" }
```

### Low cost

**From $0.00005 per record, down to $0.00003 at Gold** Pay-per-event: you are charged per record delivered, and nothing for the query. Cost scales with what you pull, not with the size of the file, and each record is metered individually - one parcel's sale history costs a fraction of a cent.

### Example output

**One sale** - `pin=17221101001199` returns the parcel's most recent sale, with
both named parties:

```json
{
  "platform": "cook-parcel-sales",
  "source": "cook-county-parcel-sales",
  "mode": "rows",
  "groupKey": "",
  "groupCount": "",
  "groupBy": "",
  "rowId": "7891346",
  "pin": "17221101001199",
  "saleYear": "2026",
  "townshipCode": "76",
  "neighborhood": "76012",
  "propertyClass": "299",
  "saleDate": "2026-07-13",
  "salePrice": "535000",
  "documentNumber": "2619521032",
  "deedType": "Warranty",
  "mydecDeedType": "",
  "sellerName": "VICTOR A DES LAURIER",
  "buyerName": "ALEX CUSUMANO",
  "saleType": "",
  "isMultisale": "true",
  "numParcelsSale": "2",
  "isMydecDate": "true",
  "sameSaleWithin365": "false",
  "lessThan10k": "false",
  "nonMarketDeedType": "false",
  "sourceUpdatedAt": "2026-09-15"
}
```

Note `isMultisale: "true"` and `numParcelsSale: "2"` - this sale covered two
parcels, so it appears twice in the register under one document number.

**An aggregate row** - `{"mode": "aggregate", "groupBy": "deedType"}` returns one
row per deed type, with the record fields blank:

```json
{
  "platform": "cook-parcel-sales",
  "source": "cook-county-parcel-sales",
  "mode": "aggregate",
  "groupKey": "Warranty",
  "groupCount": "1820065",
  "groupBy": "deedType",
  "pin": "",
  "saleDate": "",
  "salePrice": "",
  "sellerName": "",
  "buyerName": "",
  "sourceUpdatedAt": "2026-09-15"
}
```

### Notes

- **Source:** Cook County Assessor, published on `datacatalog.cookcountyil.gov`
  as public open data. No login, no key.
- **Freshness:** the portal stamps the dataset on each refresh; every record
  carries it as `sourceUpdatedAt`.
- **Coverage:** Cook County, **1999 to present** - the range the publisher states.
  A handful of records carry earlier dates (10 rows before 1999 against 2.68M),
  and they are returned as-is rather than hidden. Recording lags the sale date by
  design: measured content ran about two and a half months behind the probe date.
- **A parcel's history may exceed one page.** The most-recorded PIN in the file
  carries 151 sales; `maxResults` defaults to 50, so raise it when pulling a
  parcel's full chain.

# Actor input Schema

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

rows = sales matching your filters (default). aggregate = one count row per group (see groupBy).

## `pin` (type: `string`):

Exact 14-digit parcel index number, e.g. '17042210521210'. Every recorded sale of that parcel. Blank = any.

## `buyer` (type: `string`):

Buyer name substring, e.g. 'LLC', 'SMITH'. Blank = any.

## `seller` (type: `string`):

Seller name substring, e.g. 'TRUST', 'BANK'. Blank = any.

## `township` (type: `string`):

Cook County township code, e.g. '39' for the Chicago lakefront townships. Blank = any.

## `propertyClass` (type: `string`):

Assessor property class code, e.g. '299' (single-family), '203', '211'. Blank = any.

## `deedType` (type: `string`):

Deed type as the Assessor records it. Blank = any.

## `saleType` (type: `string`):

Whether the sale conveyed land with a building or land only. Blank = any.

## `saleFrom` (type: `string`):

Earliest sale date (YYYY-MM-DD), e.g. '2025-01-01'. Blank = any.

## `saleTo` (type: `string`):

Latest sale date (YYYY-MM-DD), inclusive. Blank = any.

## `minPrice` (type: `number`):

Only sales at or above this price (USD). Blank = any.

## `maxPrice` (type: `number`):

Only sales at or below this price (USD). Blank = any.

## `onlyComps` (type: `string`):

true = apply the Assessor's own comparability screen, excluding same-parcel resales within a year, sub-$10,000 consideration and non-market deed types. 2,409,818 of 2,686,086 sales pass. Blank = no screen.

## `multisale` (type: `string`):

true = only conveyances covering more than one parcel (565,374 rows); false = exclude them (2,120,712). Blank = any.

## `groupBy` (type: `string`):

aggregate mode only: the dimension to count by.

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

Maximum rows to return in rows mode (1-10000). Default 50.

## Actor input object example

```json
{
  "mode": "rows",
  "pin": "",
  "buyer": "",
  "seller": "",
  "township": "",
  "propertyClass": "",
  "deedType": "",
  "saleType": "",
  "saleFrom": "",
  "saleTo": "",
  "onlyComps": "",
  "multisale": "",
  "groupBy": "deedType",
  "maxResults": 50
}
```

# Actor output Schema

## `recordsUrl` (type: `string`):

Cook County parcel sale records or aggregates - as JSON

## `datasetUrl` (type: `string`):

No description

## `runUrl` (type: `string`):

No description

# 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("j0401/cook-parcel-sales").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("j0401/cook-parcel-sales").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 '{}' |
apify call j0401/cook-parcel-sales --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,j0401/cook-parcel-sales"
        }
    }
}
```

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/ybDntdiowUJ664BeU/builds/VWAlcAGTxt49MXV2n/openapi.json
