# Chicago Ordinance Violations - Cases, Fines & Hearings (`j0401/chicago-ordinance-violations`) Actor

Chicago ordinance-violation citations from administrative hearings (824,745 rows): docket and notice numbers, address and ward, violation code and description, respondents, hearing date, disposition, imposed fine and costs, with a change stamp. Filter by address, ward, code or date.

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

## Pricing

from $0.03 / 1,000 chicago ordinance violation 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

## Chicago Ordinance Violations

Every ordinance-violation citation heard by Chicago's Department of Administrative Hearings - the docket, the cited address and ward, the violation code, the respondent, the hearing outcome, the fine imposed and the administrative costs.

### Low cost

Ordinance-violation records: **from $0.00005 per record, down to $0.00003 at Gold.**

### What you get

- `violationId`, `docketNumber`, `novNumber` - the case and notice keys
- `address`, `streetNumber`, `streetDirection`, `streetName`, `streetType`, `ward`
- `violationCode`, `violationDescription` - the violation code (mostly numeric, some with a suffix such as `001000FR`) and the ordinance text
- `violationDate`, `dateSuspect` - the violation date and a flag when the city's date is not a real date
- `hearingDate`, `caseDisposition`
- `imposedFine`, `adminCosts` - dollars
- `respondents` - the cited party name(s), as published
- `issuingDepartment` - "Buildings" on every row in this extract
- `lastModifiedDate` - the city's real change stamp, usable for daily or weekly deltas via `updatedSince`
- `latitude`, `longitude`

### Examples

The 50 most recently modified citations:

```json
{ "mode": "rows", "maxResults": 50 }
```

One ward:

```json
{ "ward": 27, "maxResults": 100 }
```

One address:

```json
{ "address": "543 N LAWNDALE", "maxResults": 50 }
```

One violation code:

```json
{ "violationCode": "061014", "maxResults": 100 }
```

A code family (prefix match):

```json
{ "violationCode": "0610", "maxResults": 200 }
```

Liable cases with a fine above $10,000:

```json
{ "disposition": "Liable", "minFine": 10000, "maxResults": 100 }
```

Hearings scheduled in a window (future hearing dates are included):

```json
{ "hearingFrom": "2026-11-01", "hearingTo": "2027-02-28", "maxResults": 200 }
```

Everything the city changed since a date - a delta pull:

```json
{ "updatedSince": "2026-10-20", "maxResults": 500 }
```

Case-level view of one ward - one row per docket and hearing date, each fine counted once:

```json
{ "mode": "cases", "ward": 27, "maxResults": 200 }
```

Respondent search:

```json
{ "respondent": "HOMAN", "maxResults": 50 }
```

Citations by ward:

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

Citations by year:

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

### Coverage and honest limits

- **824,745 citations** measured 2026-10-28 (city dataset last updated **2026-10-28**; newest change stamp in the data is **2026-10-27**).
- 🔴 **This is not the Chicago building-violations dataset.** The city runs two separate regimes: this corpus is `6br9-quuz` (ordinance violations heard by the Department of Administrative Hearings). Building-code violations (`22u3-xenr`, 2.0M rows) are published separately and are a different corpus - see *Related* below.
- **One row is one citation.** A single case can carry several citations (one per violation code); they share a `docketNumber` but have different `violationId`s. Counting rows is not counting cases. 119 citations carry no docket number (and no hearing date), so they appear in rows mode but in neither `mode=cases` nor the docket-level view.
- `caseDisposition` is **blank on 18,377 rows** - no hearing outcome recorded yet, not "not liable". Dispositions in the corpus (2026-10-28): Liable 291,307 / Non-Suit 255,245 / Default 116,599 / Not Liable 106,562 / Continuance 31,360 / Adjudication Performance / Other 4,238 / Dismissed Without Prejudice 664 / Judgment Vacated 393.
- 🔴 **Some `violationDate` values are not dates.** 94 rows sit after 2027 and a handful carry impossible years (4201, 5112, 6111); 89 rows have no violation date at all. Those rows are returned with `dateSuspect=true`, and the `year` aggregate buckets every one of them as `suspect` so the totals still add up to the corpus.
- 🔴 **`imposedFine` and `adminCosts` are hearing-level values repeated on every citation row of that hearing.** Docket `12BT01656A`, for example, has 130 citation rows and every one of them carries `imposed_fine = 5000`. A docket heard more than once carries a different fine and disposition per hearing, so there is **no dollar aggregate** in this actor: summing the column multiplies each hearing's fine by its citation count. **`mode=cases`** returns one row per docket *and hearing date* - that hearing's fine counted once, with `citationCount` saying how many citations it folds in. Within one hearing the city can still cite several properties and occasionally carry conflicting outcomes: `address`, `ward`, `caseDisposition` and `imposedFine` are then the maximum value in that hearing, and `addressCount`, `wardCount`, `dispositionVariants` and `fineVariants` say how many distinct values were folded (for example docket `09BS09261A` on 2015-12-04 carries 36 Continuance and 36 Default citations). Filters are applied to the individual citations *before* the fold, so a filtered `mode=cases` counts only the citations that matched.
- **`imposedFine` is the fine the hearing imposed, not the amount collected.** The source publishes no payment or collection column.
- `violationCode` has **1,260 distinct values** in the corpus; `groupBy=violationCode` returns all of them (billed per aggregate row).
- `groupBy=ward` returns **52 groups**: wards 1-50, plus `(blank)` (2,919 rows) and `0` (71 rows) as the city publishes them.
- `issuingDepartment` is "Buildings" on every row - it is kept for compatibility, not as a useful dimension.
- `respondents` is the **cited party as the city publishes it** (public administrative record). It is not a person-level profile and must not be used for consumer screening.

### Related

Also available in this account: **Chicago Building Violations** (`22u3-xenr`, 2.0M building-code violations), **Chicago Building Permits** and **Chicago Business Licenses**.

# Actor input Schema

## `address` (type: `string`):

Violation-address prefix, case-insensitive, e.g. "543 N LAWNDALE".

## `ward` (type: `number`):

Chicago ward number. 1-50 normally; the source also carries ward 0 (71 rows) and a blank ward (2,919 rows), which are selected with 0 or by leaving the field empty and filtering in rows mode.

## `docketNumber` (type: `string`):

Exact administrative-hearing docket number, e.g. "26BT00911A".

## `novNumber` (type: `string`):

Exact NOV number, e.g. "26W0730889".

## `respondent` (type: `string`):

Cited-party name prefix, case-insensitive, e.g. "HOMAN".

## `disposition` (type: `string`):

Exact disposition, e.g. Liable, Not Liable, Default, Non-Suit, Continuance, Dismissed Without Prejudice.

## `violationCode` (type: `string`):

Violation code or a leading prefix to select a code family, e.g. "0610". Most codes are numeric; some carry a suffix such as 001000FR or EV1110.

## `issuingDepartment` (type: `string`):

Issuing department name. Every row in this extract is issued by "Buildings".

## `minFine` (type: `number`):

Only rows whose imposed fine is at least this amount.

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

ISO date YYYY-MM-DD on the violation date.

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

ISO date YYYY-MM-DD, inclusive, on the violation date.

## `hearingFrom` (type: `string`):

ISO date YYYY-MM-DD on the administrative hearing date (future scheduled hearings included).

## `hearingTo` (type: `string`):

ISO date YYYY-MM-DD, inclusive, on the hearing date.

## `updatedSince` (type: `string`):

ISO date YYYY-MM-DD; only rows whose source last-modified date is on or after it. Use it to pull a daily or weekly delta.

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

rows = citations (default). cases = one row per administrative docket and hearing date, with citationCount and that hearing's fine counted once instead of once per citation (a docket heard twice yields two coherent rows). aggregate = one row per group.

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

Dimension for mode=aggregate (blank = ward). Every group is returned; implausible violation dates are bucketed as "suspect" under year.

## `metric` (type: `string`):

Only count. The source repeats the case-level fine and costs on every citation row of a docket, so a dollar sum would multiply each case fine by its citation count - no money aggregate is offered; take one row per docket and hearing date with mode=cases instead.

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

Cap on records pushed in rows and cases mode (0 = default 50; up to 5,000 per run). Aggregate mode returns every group and ignores it. Each record is metered individually, so there is no per-run charge cap.

## Actor input object example

```json
{
  "mode": "",
  "groupBy": "",
  "metric": "",
  "maxResults": 50
}
```

# Actor output Schema

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

Chicago ordinance violation records - 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/chicago-ordinance-violations").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/chicago-ordinance-violations").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/chicago-ordinance-violations --silent --output-dataset

```

## MCP server setup

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

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/eTIXqEr7ezmqkb0p2/builds/RruxMPML4ulR531jm/openapi.json
