# Cook County Assessor Parcel Scraper (`automation-lab/cook-county-assessor-parcel-scraper`) Actor

Export official Cook County parcel assessments, owner and mailing names, property addresses, and mailed, certified, and Board values by PIN, year, township, class, or address.

- **URL**: https://apify.com/automation-lab/cook-county-assessor-parcel-scraper.md
- **Developed by:** [Stas Persiianenko](https://apify.com/automation-lab) (community)
- **Categories:** Real estate
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.02 / 1,000 parcel record extracteds

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

## Cook County Assessor Parcel Scraper

Export official Cook County parcel assessments, owner and mailing names, property addresses, and valuation history without navigating assessor pages one by one.

Use a PIN lookup for a single property, upload several PINs for portfolio review, or search by year, township, class, address, or owner name. Every default result joins two official Cook County Open Data datasets into one analysis-ready parcel-year record.

- 🏠 Find parcel addresses and owner or mailing names
- 💵 Compare mailed, certified, and Board assessment values
- 📅 Export one year or a historical year range
- 🎯 Filter by Cook County PIN, township, class, address, or owner
- 📦 Download JSON, CSV, Excel, XML, or RSS from the Apify dataset
- 🔌 Connect recurring exports to APIs, webhooks, Make, Zapier, Google Sheets, or an MCP client

### What does Cook County Assessor Parcel Scraper do?

The Actor queries Cook County's official Socrata data catalog and joins:

1. **Assessed Values** (`uzyt-m557`) for property class, township, neighborhood, and mailed, certified, and Board values.
2. **Parcel Addresses** (`3723-97qp`) for property address, owner identity, and mailing identity and address.

The sources share a parcel-year `row_id`. The Actor uses that key first and falls back to PIN plus year when needed. You get one normalized row instead of reconciling separate government exports.

### Who is this Cook County parcel data for?

#### Real-estate investors and acquisition teams

Screen parcels by location and assessed value, build owner outreach lists, and refresh a target area when a new assessment year appears.

#### Tax-appeal and valuation analysts

Compare mailed, certified, and Board totals and separate land from building values for a parcel or a portfolio.

#### Insurers and lenders

Enrich underwriting records with an official parcel identifier, property address, class, township, and assessment history.

#### Property managers and portfolio owners

Run the same PIN list periodically and route fresh parcel-year records to a warehouse or spreadsheet.

#### Researchers and civic-data teams

Create reproducible extracts from two named official datasets with source IDs and timestamps retained in every result.

### Why use this Actor?

Cook County publishes useful data, but users otherwise need to understand Socrata, write SoQL, paginate both datasets, and join the rows safely. This Actor packages that work into guided inputs and a stable schema.

- ✅ No Cook County login required
- ✅ No browser or proxy required
- ✅ Server-side filtering reduces unnecessary downloads
- ✅ Deterministic pagination and deduplication
- ✅ Typed numeric valuation fields
- ✅ Optional raw SoQL filters for advanced users
- ✅ Optional Socrata app token for higher rate limits

### What data can I extract?

| Field group | Example fields |
| --- | --- |
| Parcel identity | `pin`, `pin10`, `year`, `rowId` |
| Classification | `propertyClass`, `townshipCode`, `townshipName`, `neighborhoodCode` |
| Mailed values | land, building, total, HIE |
| Certified values | land, building, total, HIE |
| Board values | land, building, total, HIE |
| Property location | street, city, state, ZIP |
| Mailing record | name, street, city, state, ZIP |
| Owner record | name, street, city, state, ZIP |
| Provenance | source dataset IDs, source URL, scrape timestamp |

Fields unavailable for a particular historical record are omitted rather than filled with invented values.

### How to scrape Cook County property records

1. Open the Actor input page.
2. Enter one or more 14-digit PINs, or choose search filters.
3. Set the assessment year range.
4. Keep `maxItems` small for a first run.
5. Click **Start**.
6. Preview the joined records in the **Cook County parcels** dataset view.
7. Export the dataset or connect it to your downstream workflow.

PINs may include hyphens or spaces. The Actor normalizes them to the official 14-digit representation.

### Input options

#### Parcel search

- `pins`: one or more 14-digit Cook County parcel index numbers.
- `yearFrom`: earliest assessment year.
- `yearTo`: latest assessment year.
- `townshipNames`: exact township names such as `Bloom`.
- `propertyClasses`: exact class codes such as `202`.
- `addressQuery`: case-insensitive text in property street, city, or ZIP.
- `ownerQuery`: case-insensitive text in owner or mailing name.

#### Output controls

- `maxItems`: maximum joined parcel-year rows to save.
- `includePartialRecords`: include a row found in only one official source. It is off by default.
- `scanLimit`: cap source rows examined when combining cross-dataset filters.

#### Advanced Socrata controls

- `assessedValuesWhere`: additional SoQL `$where` expression for `uzyt-m557`.
- `parcelAddressesWhere`: additional SoQL `$where` expression for `3723-97qp`.
- `appToken`: optional Socrata token. Anonymous access works without it.

All filter groups combine with AND. For example, a township, year, and class search returns records satisfying all three.

### Example input: one parcel across one year

```json
{
  "pins": ["32-21-321-009-0000"],
  "yearFrom": 2020,
  "yearTo": 2020,
  "maxItems": 10
}
```

This resolves PIN `32213210090000` and joins its 2020 assessment with its property, mailing, and owner addresses.

### Example input: township and property class

```json
{
  "townshipNames": ["Bloom"],
  "propertyClasses": ["202"],
  "yearFrom": 2020,
  "yearTo": 2020,
  "maxItems": 500
}
```

Use this workflow to assemble a bounded set of similarly classified properties for valuation review.

### Example input: owner or address lead search

```json
{
  "addressQuery": "HANOVER",
  "ownerQuery": "SOUTHALL",
  "yearFrom": 2020,
  "yearTo": 2020,
  "maxItems": 100
}
```

The Actor uses the parcel-address dataset as the search driver, then joins matching assessed values.

### Example input: advanced assessed-value filter

```json
{
  "townshipNames": ["Bloom"],
  "yearFrom": 2020,
  "yearTo": 2020,
  "assessedValuesWhere": "board_tot > 100000",
  "maxItems": 1000
}
```

Raw predicates are intended for users familiar with Socrata SoQL and the official field names.

### Example output

```json
{
  "pin": "32213210090000",
  "year": 2020,
  "propertyClass": "202",
  "townshipName": "Bloom",
  "mailedTotalValue": 3294,
  "certifiedTotalValue": 3294,
  "boardTotalValue": 3294,
  "propertyAddress": "1617 HANOVER ST",
  "propertyCity": "CHICAGO HEIGHTS",
  "ownerName": "JOHNI  SOUTHALL",
  "mailingName": "JOHNI SOUTHALL",
  "rowId": "322132100900002020",
  "sourceDatasetIds": ["uzyt-m557", "3723-97qp"],
  "scrapedAt": "2026-07-24T06:12:33.662Z"
}
```

Valuation amounts are returned as numbers. PINs, ZIP codes, classes, and neighborhood codes remain strings so leading zeroes are preserved.

### How much does it cost to scrape Cook County parcel records?

The Actor uses pay-per-event pricing: a **$0.005 start fee** plus **$0.000033422 per joined parcel record** on the BRONZE plan, with plan-based volume discounts. Apify shows the exact maximum charge before each run.

A 100-record BRONZE run is approximately $0.00834 at these rates. Failed joins and source rows that are not saved are not charged as result items.

These prices were derived from a representative 100-record cloud run. The live Apify pricing panel is authoritative.

### Export formats

Apify datasets make the same records available as:

- JSON for applications and data pipelines
- CSV for spreadsheets and imports
- Excel for analyst review
- XML for legacy integrations
- RSS for lightweight monitoring

Use dataset query parameters to select fields, filter rows, unwind arrays, or clean the export.

### API usage with Node.js

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/cook-county-assessor-parcel-scraper').call({
  pins: ['32213210090000'],
  yearFrom: 2020,
  yearTo: 2020,
  maxItems: 10,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

### API usage with Python

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/cook-county-assessor-parcel-scraper').call(run_input={
    'pins': ['32213210090000'],
    'yearFrom': 2020,
    'yearTo': 2020,
    'maxItems': 10,
})
items = client.dataset(run['defaultDatasetId']).list_items().items
print(items)
```

### API usage with cURL

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~cook-county-assessor-parcel-scraper/runs?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"pins":["32213210090000"],"yearFrom":2020,"yearTo":2020,"maxItems":10}'
```

Poll the returned run ID or use Apify webhooks to receive a notification when the run finishes.

### MCP integration for AI agents

Connect Apify's MCP server to let an AI assistant run the Actor and inspect its dataset.

For Claude Code, run:

```bash
claude mcp add --transport http apify "https://mcp.apify.com?tools=automation-lab/cook-county-assessor-parcel-scraper"
```

For Claude Desktop, Cursor, or VS Code, add this MCP JSON block:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=automation-lab/cook-county-assessor-parcel-scraper"
    }
  }
}
```

Your client will ask you to authenticate with Apify.

Example prompts:

- “Look up 2020 Cook County assessment and owner records for PIN 32213210090000.”
- “Export up to 100 class 202 parcels in Bloom township for 2020.”
- “Compare mailed, certified, and Board values for these parcel PINs.”

### Integrations and automation workflows

#### Google Sheets portfolio refresh

Schedule the Actor monthly, export the latest dataset to Google Sheets, and use PIN plus year as the upsert key.

#### CRM owner lead enrichment

Search a bounded geography or class, then map owner and mailing fields into a CRM through Make or Zapier.

#### Tax-appeal analysis warehouse

Send each annual extract to BigQuery, Snowflake, or PostgreSQL. Compare value columns by PIN and year.

#### Change alerts

Use a scheduled run plus a webhook to trigger a downstream comparison when Cook County publishes a new year or updates Board values.

### Tips for accurate and efficient runs

- Start with PINs or a narrow year range.
- Use exact township and class values from prior results.
- Increase `scanLimit` only when a cross-dataset text filter needs a wider search.
- Keep `includePartialRecords` off when your workflow requires both source datasets.
- Use an app token for recurring high-volume workloads.
- Treat owner and mailing names as public-record labels that may contain spacing or punctuation differences.
- Preserve PINs as strings in spreadsheets.

### Reliability and rate limits

The Actor retries HTTP 429 and transient 5xx responses with exponential backoff. It orders pages by `row_id`, deduplicates records, and limits the number of scanned rows.

Anonymous Socrata access is suitable for normal workloads. An optional user-supplied app token can increase rate limits, but it does not unlock additional fields and is never required.

If the official catalog is temporarily unavailable, the run fails rather than returning fabricated or silently stale data.

### Data provenance

Every record names the official dataset IDs used for the join and includes a source URL, source row ID, and extraction timestamp.

Source datasets:

- Cook County Assessed Values: `uzyt-m557`
- Cook County Parcel Addresses: `3723-97qp`

Cook County remains the authoritative publisher. Field definitions and historical coverage can evolve upstream.

### Legal and responsible use

The Actor accesses public Cook County government datasets. Public availability does not remove your obligations under privacy, anti-discrimination, consumer-protection, outreach, and data-retention laws.

Do not use owner or address data for harassment, unlawful discrimination, identity theft, or decisions that require regulated notices and verification. Review the official dataset terms and applicable laws for your use case.

### Troubleshooting

#### Why did my valid query return no rows?

Check that the year exists for the PIN and that all filters can be true together. Township and property class are exact matches. Address and owner searches are contains matches, but combining both requires both to match.

#### Why did the run stop at `scanLimit` before reaching `maxItems`?

A cross-dataset filter can reject many driver rows after the join. Increase `scanLimit`, narrow the year/PIN/township filters, or use an expert SoQL predicate on the appropriate source.

#### Why are some optional fields absent?

Historical source rows vary. Missing upstream values are omitted. Turn on `includePartialRecords` only if a one-source row is useful to you.

#### Do I need a Cook County or Socrata account?

No. Both official endpoints work anonymously. The app token is optional and intended only for improved rate limits.

#### Can I search a PIN with hyphens?

Yes. Hyphens, spaces, and other non-digits are removed before validation.

### FAQ

#### Does this include property tax bills or sale history?

This Actor focuses on the official assessed-values and parcel-address datasets described above. It does not claim tax-bill, exemption, sale, deed, or building-characteristic coverage.

#### Can I export multiple assessment years?

Yes. Set `yearFrom` and `yearTo`. Each parcel-year is a separate dataset row.

#### Does it scrape the assessor website with a browser?

No. It uses official JSON resources, which is faster, cheaper, and easier to audit.

#### Are owner names guaranteed to be the current legal owner?

No. They reflect the labels published in the selected parcel-year source. Verify critical decisions against current official records.

#### Can I use custom SoQL?

Yes. Advanced users can add a `$where` expression separately for each official dataset.

### Related real-estate scrapers

- [Maricopa County Property Records Scraper](https://apify.com/automation-lab/maricopa-county-property-records-scraper) for Maricopa County, Arizona.
- [Clark County Property Records Scraper](https://apify.com/automation-lab/clark-county-property-records-scraper) for Clark County, Nevada.
- Browse other public-data and real-estate Actors from [automation-lab](https://apify.com/automation-lab).

### Support

For a reproducible support request, include the run ID, sanitized input, expected PIN/year, and the official source row you expected. Do not post private app tokens in support messages.

# Actor input Schema

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

Cook County 14-digit parcel index numbers. Hyphens and spaces are accepted.

## `yearFrom` (type: `integer`):

Earliest assessment year to include.

## `yearTo` (type: `integer`):

Latest assessment year to include.

## `townshipNames` (type: `array`):

Exact township names, such as Bloom, Hyde Park, or Lake View.

## `propertyClasses` (type: `array`):

Exact Cook County class codes, such as 202 for a one-story residence.

## `addressQuery` (type: `string`):

Case-insensitive text matched against property street, city, or ZIP fields.

## `ownerQuery` (type: `string`):

Case-insensitive text matched against owner and mailing names.

## `maxItems` (type: `integer`):

Maximum joined records saved to the dataset.

## `includePartialRecords` (type: `boolean`):

Include parcels found in only one source. Off by default so every result has both assessment and address coverage.

## `scanLimit` (type: `integer`):

Safety cap for cross-dataset filters. Must be at least maxItems.

## `assessedValuesWhere` (type: `string`):

Raw $where expression applied to official dataset uzyt-m557, for example board\_tot > 100000.

## `parcelAddressesWhere` (type: `string`):

Raw $where expression applied to official dataset 3723-97qp.

## `appToken` (type: `string`):

Optional Cook County Socrata app token for higher rate limits. Anonymous access works without it.

## Actor input object example

```json
{
  "pins": [
    "32213210090000"
  ],
  "yearFrom": 2020,
  "yearTo": 2020,
  "maxItems": 10,
  "includePartialRecords": false,
  "scanLimit": 2000
}
```

# Actor output Schema

## `overview` (type: `string`):

API link to normalized parcel-year assessment, address, owner, and mailing records.

# 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 = {
    "pins": [
        "32213210090000"
    ],
    "yearFrom": 2020,
    "yearTo": 2020,
    "maxItems": 10,
    "includePartialRecords": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/cook-county-assessor-parcel-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 = {
    "pins": ["32213210090000"],
    "yearFrom": 2020,
    "yearTo": 2020,
    "maxItems": 10,
    "includePartialRecords": False,
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/cook-county-assessor-parcel-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 '{
  "pins": [
    "32213210090000"
  ],
  "yearFrom": 2020,
  "yearTo": 2020,
  "maxItems": 10,
  "includePartialRecords": false
}' |
apify call automation-lab/cook-county-assessor-parcel-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=automation-lab/cook-county-assessor-parcel-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/Hk4qthYVgjtCU8CwE/builds/DW1FTJhdJPOJ0TTUc/openapi.json
