Chicago Building Violations
Pricing
from $0.03 / 1,000 violation records
Chicago Building Violations
Chicago building code violations (public open data, 2.03M records, daily): the violation code, description and ordinance, the inspector's comments, the inspection that produced it, and the property address with lat/lon. Filter by status/category/bureau/code/date or pull the daily change stream.
Pricing
from $0.03 / 1,000 violation records
Rating
0.0
(0)
Developer
Wenhao Yang
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
8 hours ago
Last modified
Categories
Share
Chicago publishes every building-code violation it writes - over two million of them - as open data. This actor turns that register into a charged-per-record lookup, filter and aggregate tool: pull the violations at an address, sweep a bureau's caseload, isolate the open cases, or follow the daily change stream to see what the buildings department touched today.
Built for: property and title diligence, real-estate and construction underwriting, code-compliance screening, and anyone who needs the code-enforcement record behind a Chicago address - not a keyword match against a street name.
What it covers
~2.03M violations - every code violation on file, spanning the department's own bureaus:
| Bureau | Violations |
|---|---|
| Conservation | 1,333,299 |
| Special Task Force | 148,295 |
| Elevator | 135,695 |
| Demolition | 119,476 |
| Electrical | 42,820 |
| Boiler | 40,416 |
| Plumbing | 39,617 |
| New Construction | 39,121 |
| Ventilation | 36,631 |
| Refrigeration | 32,032 |
| Special Inspection Program / Signs / Iron / Water / Construction Equipment | ~60,400 |
and why each inspection happened - COMPLAINT (~1.43M), PERIODIC (~493k), PERMIT (~102k), REGISTRATION (2,593).
Each violation carries real depth, not just a code:
- the violation code (100% populated -
CN190019,EV1110,CN061014, ), its plain-language description (2,013,116 rows), and the municipal ordinance citation it falls under (1,968,752) - the inspector's own comments on the finding (1,837,142 rows)
- the inspection that produced it: number, category, status, and the bureau that ran it
- the property: full street address (100%, 174,821 distinct addresses), street number/direction/name/type, property group, SSA, and lat/lon (2,025,788)
The fine print that matters
Status is exactly three source values - OPEN (~1.16M, still outstanding), COMPLIED (~862k, resolved) and NO ENTRY (~7.2k, inspector couldn't gain access) - matched as exact values. The daily change stream is real: violation_last_modified_date is a per-row timestamp, so mode=delta returns what the department actually touched since a date (1,210 rows in the last 7 days).
Two source quirks are handled honestly. The inspection_waived column is 'N' on all 2.03M rows - a dead field, not surfaced. And the violation_location column (a short free-text note like "rear porch") is filled on only ~49% of rows - surfaced as-is and never pitched as a location field; the real geocoding is latitude/longitude, present on 99.9%.
One boundary stated plainly: Chicago's building-permit register stores street parts separately and has no address column, so the violationpermit relationship is a fuzzy address / lat-lon match, not an id join. This actor gives you the violation record complete; joining it to a permit is address work, not a key lookup.
Typical questions
- "Every open violation at 200 E Garfield Blvd."
- "Elevator bureau violations filed this year."
- "Complaint-driven inspections that FAILED in Chicago."
- "Violations under code EV1110."
- "What did the buildings department change since yesterday?" (
mode=delta) - "Aggregate the register by bureau, category or status."
Inputs
| Input | What it does |
|---|---|
mode | rows (default) / delta (change stream) / aggregate |
status / category / inspectionStatus | open vs resolved; why inspected; inspection outcome |
bureau | which department unit |
violationCode | exact code |
address / streetName | where |
dateFrom / dateTo / changedSince | violation-date range, and the change window |
groupBy | aggregate over status / category / bureau / inspectionStatus / code |
maxResults | cap records (default 50) |
Default run = the 50 most recent violations - fast for the daily auto-test. For a targeted query add a filter; for a broad view use aggregate.
Example inputs
Every open violation at one address - address matches the full street
address; status=OPEN keeps only what is still outstanding.
{ "address": "1812 S RIDGEWAY", "status": "OPEN", "maxResults": 20 }
Complaint-driven inspections that failed - category is why the inspection
happened, inspectionStatus is how it went.
{ "category": "COMPLAINT", "inspectionStatus": "FAILED","maxResults": 20 }
What the department changed this week - mode=delta follows the change
column, not the violation date.
{ "mode": "delta", "changedSince": "2026-09-18", "maxResults": 50 }
Open vs resolved across the register - one count per group.
{ "mode": "aggregate", "groupBy": "status" }
Low cost
From $0.00005 per record, down to $0.00003 at Gold - billed only for the rows you use, at the low end of the store. Cost scales with what you pull, not with the size of the register, and each record is metered individually.
The traps in this register are the silent ones. The status column carries a value - NO ENTRY - that means the inspector couldn't look, which a filter written for open/closed quietly files under neither; the change timestamp and the violation date are different columns, so "what the department touched since yesterday" and "what was filed since yesterday" are different questions (1,210 rows were modified in the last seven days, while 614 new ones were filed - the two axes move at different rates), and a single date filter conflates them; and the location note is present on less than half the file, so treating it as the location drops half the register. Normalizing the three-valued status, the separate change column and the half-empty note into a schema where an address / status=OPEN / bureau query returns exactly the violations you mean - and mode=delta returns the true daily delta - is the actual product. Every pull is integrity-checked against the register's known shape, so a degraded source fails loudly instead of returning bad rows.
Example output
One violation - address="1812 S RIDGEWAY" with status=OPEN returns the
open violations at that address; each is a record like this one. Every record
also carries platform, source, mode, and the aggregate columns groupKey
/ groupCount / groupBy, which are "" outside aggregate mode:
{"platform": "chicago-violations","source": "chicago-building-violations","mode": "rows","groupKey": "", "groupCount": "", "groupBy": "","violationId": "7544352","violationDate": "2026-09-15","lastModified": "2026-09-15","status": "OPEN","statusDate": "","violationCode": "NC2071","description": "WORK NOT ALLOWED","ordinance": "Remove work performed without permit and restore building or site to original construction. (13-32-130, 13-32-290)","locationNote": "OTHER : :ATTIC ALTERED INTO APARTMENT,","inspectorComments": "REMOVE ALL WORK IN ATTIC PERFORMED WITHOUT PERMIT14A-4-402.3","inspectorId": "BL00875","inspectionNumber": "14941923","inspectionStatus": "FAILED","inspectionCategory": "COMPLAINT","departmentBureau": "CONSERVATION","address": "1812 S RIDGEWAY AVE","streetNumber": "1812","streetDirection": "S","streetName": "RIDGEWAY","streetType": "AVE","propertyGroup": "274131","ssa": "","latitude": "41.8566655428","longitude": "-87.7189787466"}
mode=aggregate, groupBy=status - one row per group (the record fields
are ""; the group lands in groupKey / groupCount):
OPEN ~1.16MCOMPLIED ~862kNO ENTRY ~7.2k
Source
- City of Chicago: Building Violations - the city's full violation register, refreshed daily. Public open data. Records are the city's own published fields; a violation is an alleged code violation, not a court finding.