# Cook County Property Sales & Assessed Values (Chicago) (`jserle/cook-county-property-sales-values`) Actor

Cook County Assessor open data as clean JSON: parcel sales with buyer, seller, price and deed type; assessed values (mailed, certified, Board of Review); building permits; assessment appeals with agent and outcome. Filter by date, township, class, price, PIN list and buyer or seller name.

- **URL**: https://apify.com/jserle/cook-county-property-sales-values.md
- **Developed by:** [JS Tech Solutions](https://apify.com/jserle) (community)
- **Categories:** Lead generation, Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$3.00 / 1,000 parcel records

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/actors/running/actors-in-store.md#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

## Cook County Property Sales & Assessed Values (Chicago Parcel Data)

Pulls the Cook County Assessor's Office open data into clean JSON records: every recorded parcel sale in Cook County, Illinois (Chicago and suburbs) with buyer, seller, price, deed type and the Assessor's non-market-sale flags; assessed values for every parcel and tax year at the mailed, certified and Board of Review stages; building permits reported to the Assessor; and assessment appeals with the filing agent and the outcome. Filter by date, township, property class, price, PIN list and buyer or seller name. No scraping of the Assessor's website, no PIN-at-a-time lookups: this reads the County's bulk open-data API, so a township's worth of sales arrives in one run.

Four datasets, one record shape per dataset, all keyed on the 14-digit PIN:

- **Sales** - one record per recorded sale. 2,686,366 sales from 1971 to the newest published date, 79,257 of them dated 2025.
- **Assessed values** - one record per parcel per tax year, 1999 to the current year. About 1.86 million parcels a year.
- **Permits** - building permits municipalities report to the Assessor, 726,959 rows; 10,480 issued in the 90 days to 23 August 2026.
- **Appeals** - 2,615,003 appeal rows; 362,530 for tax year 2025, with the agent or attorney named on 81% of them.

### Who uses this data

- **Chicago real estate investors and wholesalers** pulling every sale over a price in a township, or every sale to an LLC, as a weekly feed.
- **Appraisers** assembling comparable sales by township, class and date window, with deed type and the Assessor's own arms-length flags on each.
- **Property tax appeal firms** reading a parcel's mailed vs certified values, the appeal history and outcomes in a township, and which agents are filing.
- **Lenders and title researchers** checking a PIN list's sale history and current assessed values in one call.
- **Contractors and suppliers** watching permits by township and estimated job amount.

### What you get

One dataset item per source row. A real sales item, from the default input:

```json
{
  "mode": "sales",
  "rowId": "7890996",
  "pin": "17042070861145",
  "pinFormatted": "17-04-207-086-1145",
  "taxYear": 2026,
  "townshipCode": "74",
  "townshipName": "North Chicago",
  "neighborhoodCode": "74022",
  "classCode": "299",
  "saleDate": "2026-07-14",
  "saleDateIsFromMyDec": true,
  "salePrice": 162000,
  "documentNumber": "2619521029",
  "deedType": "Other",
  "myDecDeedType": null,
  "buyerName": "NEGAR NAGHASHZADEH",
  "sellerName": "AUCTION.COM LLC AUCTION.COM LLC",
  "saleType": null,
  "isMultiParcelSale": false,
  "parcelsInSale": 1,
  "ccaoFilters": { "sameSaleWithin365Days": false, "lessThan10k": false, "nonArmsLengthDeedType": false },
  "sourceDataset": "wvhk-k5uv",
  "sourceDatasetTitle": "Assessor - Parcel Sales",
  "source": "https://datacatalog.cookcountyil.gov/resource/wvhk-k5uv.json",
  "datasetPage": "https://datacatalog.cookcountyil.gov/d/wvhk-k5uv",
  "attribution": "Cook County Assessor's Office",
  "sourcePublishedAt": "2026-08-19T19:36:06.000Z",
  "fetchedAt": "2026-08-23T16:37:50.170Z"
}
```

Every item carries `pin` (14 digits, zero-padded), `pinFormatted`, `taxYear`, `townshipCode` and `townshipName` (both, whichever the source publishes), plus the `source`, `attribution`, `sourcePublishedAt` and `fetchedAt` envelope. The other modes add:

- **assessedValues**: `classCode`, `neighborhoodCode`, and three value stages `mailed`, `certified`, `boardOfReview`, each `{ building, land, total, homeImprovementExemption }`. These are assessed values, not market values; divide by the class's level of assessment to estimate market value.
- **permits**: `permitNumber`, `localPermitNumber`, `dateIssued`, `status` (CLOSED, PENDING, OPEN, RECHECK), `assessable`, `amount`, `municipality`, `propertyAddress`, `mailingAddress`, `applicantName`, `jobCodePrimary`, `jobCodeSecondary`, `workDescription`, `improvementCodes`.
- **appeals**: `caseNumber`, `hearingType`, `appealType`, `status`, `result` (change / no change), `mailed` and `certified` value triplets, `totalReduction` (mailed total minus certified total), `reasons` (code and description), `agentCode`, `agentName`.

Names, addresses and descriptions are delivered exactly as the Assessor records them (upper case, sometimes repeated). `null` means the source row has no value for that column. `documentNumber` is the Cook County Clerk's recording number and can be looked up on the Clerk's site. Set `includeRaw` to attach the untouched source row as `raw`.

### Input

| Field | Type | Default | Notes |
| --- | --- | --- | --- |
| `mode` | select | `sales` | `sales`, `assessedValues`, `permits`, `appeals` |
| `pins` | list | none | Up to 500 PINs; dashes allowed. Switches off the default window and tax year so you get the parcel's whole history. Lists over 100 PINs are queried in chunks of 100 and merged. |
| `township` | text | none | Name or two-digit code, any case: `Lake View`, `73`, `Evanston`. 38 townships; Chicago is 70-77. |
| `classCodes` | list | none | Assessor class codes such as `211`, `203`, `299`. Sales, values and appeals only. |
| `taxYear` | integer | newest year (values, appeals) | Also accepted for sales and permits, where it replaces the date window. |
| `dateFrom`, `dateTo` | date | see below | Sale date (recorded) or permit issue date. |
| `minPrice`, `maxPrice` | integer | none | Sale price, or permit estimated amount. |
| `buyerName`, `sellerName` | text | none | Sales only. Case-insensitive "contains". |
| `saleFilter` | select | `under10k` | `under10k` drops sales the Assessor flags under $10,000; `strict` also drops repeat same-price sales within a year and quit claim, executor and beneficiary deeds; `none` delivers everything. |
| `maxResults` | integer | 100 | Cap on delivered records, up to 50,000. Also your cost cap. |
| `includeRaw` | boolean | false | Attach the source row. |

**Default windows.** With no dates, PINs or tax year, sales returns the 60 days ending at the newest published sale (not at today: sales are published on a lag of several weeks, and the Assessor notes some arrive months after recording), permits returns the 90 days ending today, and assessed values and appeals return the newest tax year. Sales and permits are delivered newest first; values and appeals in PIN order. Assessed values skips parcel-years that have no mailed value yet, so a not-yet-reassessed township is not delivered as empty rows.

Every filter runs server-side on the County's API; a filter that does not exist for the chosen dataset (a price on assessed values, a class on permits) is listed under `filtersIgnoredForDataset` in the run summary rather than silently dropped.

### Pricing

Pay per record delivered. A filter that matches nothing costs nothing. `maxResults` is honored before anything is pushed, and a run that hits its charge limit stops cleanly and says so in the `OUTPUT` summary (`stoppedByChargeLimit`).

### Data source, refresh and licence

All four datasets are published by the **Cook County Assessor's Office** on the Cook County open data portal (Tyler Data & Insights / Socrata) and read through its public API without a token:

- Parcel sales: https://datacatalog.cookcountyil.gov/d/wvhk-k5uv
- Assessed values: https://datacatalog.cookcountyil.gov/d/uzyt-m557
- Permits: https://datacatalog.cookcountyil.gov/d/6yjf-dfxs
- Appeals: https://datacatalog.cookcountyil.gov/d/y282-6ig3

The portal lists each as updated bi-weekly ("semi-monthly" in the dataset notes); each record's `sourcePublishedAt` is the portal's own last-updated stamp for its dataset. The datasets carry no licence field. The portal's "About Open Data" page (https://datacatalog.cookcountyil.gov/stories/s/pvqg-z4sc) describes the data as "free public data published by Cook County agencies" under the County's Open Government Plan and lists among its principles: "Non-discriminatory: Data is available to anyone, with no registration requirement" and "License-free: Data is not subject to copyright, patent, trademark, or trade secret regulation where reasonable". The County's website Terms of Use (https://www.cookcountyil.gov/terms-use) disclaim warranty and accuracy. This Actor is not affiliated with or endorsed by the Cook County Assessor's Office or Cook County Government. Attribution: Cook County Assessor's Office.

Requests are sent one at a time with the one-second crawl delay the portal publishes in its robots.txt, from a User-Agent that identifies this Actor.

### Limitations

- **Sales lag.** On 23 August 2026 the newest sale in the dataset was dated 14 July 2026. The Assessor warns that many sales do not appear until months after recording. Use the default window for "recent", not for "this week".
- **Non-market sales.** The dataset includes quit claims, intra-family transfers and foreclosure-related deeds. The three `ccaoFilters` flags and `saleFilter=strict` are the Assessor's own screen, not a guarantee of arms-length.
- **Missing names.** Buyer and seller names are absent on some sales (buyer on 19% of the default window, seller on 33%, read 23 August 2026), so name filters cannot match those.
- **Appeals history is sparse before 2021** while the Assessor repopulates its system of record; agent, reason and type fields are complete from 2021 onward.
- **Permits are what municipalities reported**, in inconsistent formats, and are not the complete universe of Cook County permits. A handful carry mistyped issue dates far in the future; the default window excludes them.
- **Assessed, not market.** Values must be adjusted by the class's level of assessment (published by the Assessor, and changed over the years covered) to estimate market value.
- Values for the current tax year change until the Board of Review certifies them.

### FAQ

#### How do I get Cook County property sales by township or neighborhood?

Set `township` (for example `Lake View` or `73`) and optionally `classCodes` and a date window. Neighborhood is delivered on each record as `neighborhoodCode` (township plus Assessor neighborhood), so you can filter on it after the fact.

#### Can I search Cook County property records by owner or buyer name?

Sales records carry buyer and seller names as recorded on the deed; `buyerName` and `sellerName` do a case-insensitive contains match. The Assessor's datasets do not publish a current-owner column. The buyer on the most recent published sale can be a useful lead, but it is not a current-owner or title determination: sales are published on a lag, some transfers never appear here, and names are recorded inconsistently. Verify ownership with the Cook County Assessor or Clerk (Recordings) or a title source.

#### How do I look up assessed values and appeals for a list of PINs?

Put the PINs in `pins` (any format, up to 500 per run) and choose `assessedValues` or `appeals`. With a PIN list the Actor returns the full history, every tax year, for those parcels.

#### Where do I find the Assessor property search data for Chicago?

Chicago parcels sit in townships 70 to 77 (Hyde Park, Jefferson, Lake, Lake View, North Chicago, Rogers Park, South Chicago, West Chicago). Run one township at a time, or omit `township` for the whole county.

### Related datasets

- https://apify.com/jserle/nyc-dob-permits-feed - New York City approved building permits with contractor and owner.
- https://apify.com/jserle/wa-contractor-license-registry - Washington State contractor licences with principals, bond and insurance.
- https://apify.com/jserle/pa-business-registry-officers and https://apify.com/jserle/ct-business-registry-leads - state business registries for looking up the entities behind LLC buyers.
- https://apify.com/jserle/nyc-passport-procurement - New York City procurement opportunities.

# Actor input Schema

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

Sales: one record per recorded parcel sale (buyer, seller, price, deed type). Assessed values: one record per parcel per tax year (mailed, certified and Board of Review values). Permits: building permits reported to the Assessor. Appeals: assessment appeals filed with the Assessor, with agent and outcome.

## `pins` (type: `array`):

Up to 500 Cook County Parcel Index Numbers, one per line. Dashes are fine (17-04-207-086-1145) and short PINs are zero-padded to 14 digits. With a PIN list, the default date window and tax year are not applied, so you get the parcel's full history in the chosen dataset.

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

One Cook County township, by name or two-digit code, case-insensitive. Examples: Lake View, 73, Hyde Park, Evanston. The eight Chicago townships are Hyde Park (70), Jefferson (71), Lake (72), Lake View (73), North Chicago (74), Rogers Park (75), South Chicago (76) and West Chicago (77).

## `classCodes` (type: `array`):

Assessor property class codes, one per line, for example 211 (two to six unit apartment), 203 (single family), 299 (condominium), 500-599 (commercial). Applies to sales, assessed values and appeals; permits carry no class.

## `taxYear` (type: `integer`):

Four-digit tax year. For assessed values and appeals this is the main selector and defaults to the newest year in the dataset. For sales and permits it restricts to that year's records and switches off the default date window.

## `dateFrom` (type: `string`):

ISO date, for example 2026-01-01. Sales: sale date (recorded date) on or after. Permits: issue date on or after. Ignored for assessed values and appeals, which are by tax year.

## `dateTo` (type: `string`):

ISO date, for example 2026-06-30. Sales: sale date on or before. Permits: issue date on or before.

## `minPrice` (type: `integer`):

Sales: sale price at least this. Permits: estimated job amount at least this. Ignored for assessed values and appeals.

## `maxPrice` (type: `integer`):

Sales: sale price at most this. Permits: estimated job amount at most this.

## `buyerName` (type: `string`):

Sales only. Case-insensitive substring matched against the buyer name as recorded. Example: LLC, or a surname. Roughly one recent sale in five has no buyer name on file; those cannot match.

## `sellerName` (type: `string`):

Sales only. Case-insensitive substring matched against the seller name as recorded. Examples: bank, trust, auction.

## `saleFilter` (type: `string`):

The Assessor flags three kinds of likely non-market sales. Default drops sales flagged under $10,000. Strict also drops repeat same-price sales within 365 days and quit claim, executor and beneficiary deeds (the Assessor's own pre-2023 filtering). None delivers every recorded sale. The three flags are delivered on every record either way.

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

How many records to deliver. Sales and permits come newest first; assessed values and appeals come in PIN order. You are charged per delivered record, so this is also your cost cap.

## `includeRaw` (type: `boolean`):

Attach the original Socrata row under `raw`, with the Assessor's own column names. Useful for auditing the normalization; roughly doubles item size.

## Actor input object example

```json
{
  "mode": "sales",
  "saleFilter": "under10k",
  "maxResults": 100,
  "includeRaw": false
}
```

# Actor output Schema

## `results` (type: `string`):

One item per record delivered by this run.

## `summary` (type: `string`):

Dataset read, publication date, the SoQL filter that ran, matching and delivered counts, malformed rows skipped, and whether the charge limit stopped the run.

# 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 = {
    "mode": "sales",
    "saleFilter": "under10k",
    "maxResults": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("jserle/cook-county-property-sales-values").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 = {
    "mode": "sales",
    "saleFilter": "under10k",
    "maxResults": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("jserle/cook-county-property-sales-values").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 '{
  "mode": "sales",
  "saleFilter": "under10k",
  "maxResults": 100
}' |
apify call jserle/cook-county-property-sales-values --silent --output-dataset

```

## MCP server setup

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

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/GbnV2dR3AdeypUbOk/builds/E39y1hvVtCUIIprOX/openapi.json
