# Chicago Open Data Scraper (`bigibyte/chicago-open-data-scraper`) Actor

Chicago Open Data Scraper pulls crimes, business licenses, and building permits straight from the City of Chicago SODA API. Pick a mode or any Socrata dataset id, add optional SoQL filters, and export clean rows to JSON, CSV, or Excel. No login needed. Handles paging, retries, and dedupe for you.

- **URL**: https://apify.com/bigibyte/chicago-open-data-scraper.md
- **Developed by:** [BigiByte](https://apify.com/bigibyte) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

## 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 Open Data Scraper: Chicago crimes, business licenses, and building permits

<p align="center">
  <img src="https://imgur.com/C4yi9EC.png" alt="Chicago Open Data Scraper" style="max-width:100%; border-radius:14px; width:680px;" />
</p>

Chicago Open Data Scraper provides Chicago crimes, business licenses, and building permits as structured dataset rows. No login or signup is required. You can export the run as JSON, CSV, or Excel.

The actor has three built-in modes for crimes, business licenses, and building permits. A fourth mode, `dataset`, works with any public dataset id you supply.

### Use cases

- **Crime extracts**: recent incidents with case number, primary type, block, ward, and arrest flag
- **License lists**: legal name, doing-business-as name, address, and ward from business licenses
- **Permit watch**: building permits with permit number, review type, street, and fees
- **Filtered results**: narrow and sort output with the optional filter and order inputs
- **Scheduled CSV**: rerun the same input and download a fresh table

### Modes

- **Crimes** (`crimes`): Chicago crime incidents.
- **Business licenses** (`licenses`): Chicago business license records.
- **Building permits** (`permits`): Chicago building permit records.
- **Any dataset** (`dataset`): any public dataset id you set in `datasetId`.

### Input

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `mode` | string | `crimes` | `crimes`, `licenses`, `permits`, or `dataset`. |
| `datasetId` | string | empty | Public dataset id, used only in `dataset` mode. |
| `where` | string | empty | Optional filter expression. Leave empty to skip it. |
| `order` | string | empty | Optional sort, for example `date DESC`. |
| `maxItems` | integer | `100` | Row cap. Hard maximum 1000. |
| `requestDelaySecs` | number | `0.5` | Delay between requests in seconds. Never faster than 0.5. |
| `timeoutSecs` | integer | `300` | Stop the run after this many seconds. |
| `requestTimeoutSecs` | integer | `30` | Per-request timeout in seconds. |
| `proxyConfiguration` | object | No proxy | Optional. Default `{"useApifyProxy": false}`. |

#### Example input

```json
{
  "mode": "crimes",
  "datasetId": "",
  "where": "",
  "order": "date DESC",
  "maxItems": 100,
  "requestDelaySecs": 0.5,
  "timeoutSecs": 300,
  "requestTimeoutSecs": 30,
  "proxyConfiguration": { "useApifyProxy": false }
}
```

### Output data

Each row includes the available source fields plus `mode`, `source_url`, and `scraped_at`. The `description` field is stored as `incidentDescription`. A few other fields are renamed when they would collide with reserved names. The `location` object is preserved.

```json
{
  "id": "14348063",
  "case_number": "JK438266",
  "date": "2026-10-03T00:00:00.000",
  "block": "064XX S ELLIS AVE",
  "iucr": "1090",
  "primary_type": "ARSON",
  "incidentDescription": "ATTEMPT ARSON",
  "location_description": "VEHICLE NON-COMMERCIAL",
  "arrest": false,
  "domestic": false,
  "beat": "0314",
  "district": "003",
  "ward": "20",
  "community_area": "42",
  "fbi_code": "09",
  "x_coordinate": "1184164",
  "y_coordinate": "1862455",
  "year": "2026",
  "updated_on": "2026-10-10T15:41:23.000",
  "latitude": "41.778045",
  "longitude": "-87.601234",
  "location": {"type": "Point", "coordinates": [-87.601234, 41.778045]},
  "mode": "crimes",
  "source_url": "<reference URL for this record>",
  "scraped_at": "2026-10-12T00:00:00+00:00"
}
```

Failures are recorded as error rows with `error`, `input`, and `status`, and the run continues. Duplicate records are removed.

| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Record id. |
| `case_number` | string | Crime case number when present. |
| `date` | string | Crime date. |
| `block` | string | Address block. |
| `primary_type` | string | Primary crime category. |
| `incidentDescription` | string | Offense text. |
| `arrest` | boolean | Arrest flag when present. |
| `legal_name` | string | Business legal name on license rows. |
| `permit_` | string | Permit number on building permit rows. |
| `permit_type` | string | Permit type. |
| `mode` | string | `crimes`, `licenses`, `permits`, or `dataset`. |
| `source_url` | string | Reference URL for the record. |
| `scraped_at` | string | UTC time the row was saved. |
| `error` | string | Failure message on error rows. |
| `input` | string | Identifier that failed. |
| `status` | integer | Status code, or 0 if none. |

License and permit rows also include their other available fields, such as `doing_business_as_name`, `ward`, `work_description`, and fee fields.

### Limits

This actor caps a run at 1000 rows and stops at `maxItems`.

Data reflects what the city has published. The city decides when a dataset is refreshed.

### FAQ

**Do I need a login or signup?**
No. You can run it right away without an account.

**How fresh is the data?**
Each run returns current data. Freshness follows the city's update schedule, not a cache in this actor.

**Why is the crime description called incidentDescription?**
It is the same value, renamed to avoid a reserved field name.

**What happens if the dataset id is wrong?**
An invalid id produces a single error row, and an id that does not exist also produces a single error row.

**Can I export CSV?**
Yes. Open the dataset and download JSON, CSV, or Excel.

### Related tools

If you plan to map or join these rows, a geocoding pass or a quick CSV cleanup are the usual next steps. BigiByte also builds scrapers for other open-data portals when you need them as a separate job.

Need another city's open data, a scheduled monitor, or a custom pipeline? BigiByte builds custom scrapers and APIs:
https://bigibyte.com

#### Trademark Notice

Chicago Open Data and related names, logos, and trademarks belong to their respective owners. Their use in this documentation is solely to identify the source of the data and does not imply affiliation, sponsorship, endorsement, or partnership.

#### Disclaimer

Disclaimer: This Actor is an independent tool and is not affiliated with, endorsed by, sponsored by, or otherwise officially associated with Chicago Open Data or its owners. All data is collected from publicly available sources. Users are responsible for complying with the source's terms of use, rate limits, and attribution or licensing requirements.

#### Is web scraping legal?

This Actor only collects publicly available data from Chicago Open Data, without logging in or bypassing any access controls. Scraping public data is generally legal, but how you use it matters:

- **Personal data:** If the results include personal data (for example names, emails, or profile details), laws such as the GDPR in the EU and the CCPA in California may apply. Only collect and store personal data when you have a legitimate reason.
- **Copyright:** Some content (articles, images, descriptions) may be protected by copyright. Do not republish full copyrighted content without permission.
- **Terms of use:** Respect the source's terms, stated rate limits, and any attribution or non-commercial-use requirements listed in the Limits section.
- **Legal advice:** This is general information, not legal advice. If you're unsure, consult a lawyer.

To learn more, read Apify's guide: [Is web scraping legal?](https://blog.apify.com/is-web-scraping-legal/)

# Actor input Schema

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

crimes reads ijzp-q8t2, licenses reads r5kz-chrr, permits reads ydr8-5enu. dataset uses the Socrata 4x4 in datasetId on the same City of Chicago resource host.

## `datasetId` (type: `string`):

Socrata 4x4 id used when mode is dataset. Example: ijzp-q8t2 for crimes. Ignored for crimes, licenses, and permits.

## `where` (type: `string`):

Optional SoQL $where filter sent to the resource endpoint. Example: primary_type = 'THEFT'. Leave empty to skip the filter.

## `order` (type: `string`):

Optional SoQL $order clause. Example: date DESC. Leave empty to use the portal's default order.

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

Optional Socrata app token sent as X-App-Token. Leave empty for anonymous access. The token is not written into dataset rows or source URLs.

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

Maximum result rows to save this run. Hard cap is 1000.

## `requestDelaySecs` (type: `number`):

Polite pause between resource requests, in seconds. The run never goes faster than 0.5 seconds between calls.

## `timeoutSecs` (type: `integer`):

Stop the run after this many seconds. Default is 300. Maximum is 3600.

## `requestTimeoutSecs` (type: `integer`):

How long to wait for one HTTP response before retrying. Default is 30 seconds.

## `proxyConfiguration` (type: `object`):

Optional proxy for requests. Default is no proxy. Turn on Apify Proxy only if you need another IP.

## Actor input object example

```json
{
  "mode": "crimes",
  "datasetId": "ijzp-q8t2",
  "where": "primary_type = 'THEFT'",
  "order": "date DESC",
  "maxItems": 100,
  "requestDelaySecs": 0.5,
  "timeoutSecs": 300,
  "requestTimeoutSecs": 30,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (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 = {
    "mode": "crimes",
    "datasetId": "ijzp-q8t2",
    "maxItems": 100,
    "requestDelaySecs": 0.5,
    "timeoutSecs": 300,
    "requestTimeoutSecs": 30,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("bigibyte/chicago-open-data-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 = {
    "mode": "crimes",
    "datasetId": "ijzp-q8t2",
    "maxItems": 100,
    "requestDelaySecs": 0.5,
    "timeoutSecs": 300,
    "requestTimeoutSecs": 30,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("bigibyte/chicago-open-data-scraper").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": "crimes",
  "datasetId": "ijzp-q8t2",
  "maxItems": 100,
  "requestDelaySecs": 0.5,
  "timeoutSecs": 300,
  "requestTimeoutSecs": 30,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call bigibyte/chicago-open-data-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,bigibyte/chicago-open-data-scraper"
        }
    }
}
```

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/PHT41RxGEBLtdxAJd/builds/fDTqqMWpwediPWWVa/openapi.json
