# Socrata Open Data API Query (SoQL) for Government Portals (`lathewright/socrata-open-data-query`) Actor

Returns rows from any dataset on a Socrata open-data portal (NYC, Chicago, Seattle, CDC, state portals) via the official SODA API with SoQL filters, plus dataset metadata and columns, or a keyword search of 500+ portal catalogs. Honours robots.txt. Priced per 100 rows.

- **URL**: https://apify.com/lathewright/socrata-open-data-query.md
- **Developed by:** [lathewright LLC](https://apify.com/lathewright) (community)
- **Categories:** Developer tools, News, Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.00 / 1,000 100 rows returneds

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?

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

## Socrata Open Data API Query (SoQL) for Government Portals

Query any dataset on a Socrata open-data portal with SoQL and get clean JSON rows, for data analysts, civic technologists, journalists and AI agents. It costs $0.01 per 100 rows and $0.002 per dataset description, so 10,000 rows cost $1.

Query any dataset on a **Socrata** open-data portal through the official SODA API: New York City, Chicago, Seattle, San Francisco, Los Angeles County, the CDC, state portals such as New York, Colorado, Connecticut, Oregon, Texas and hundreds more. Write a SoQL filter (`$where`, `$select`, `$order`, `$group`), pull up to 100,000 rows in one run, describe a dataset (columns, row count, licence) or search the catalog across every portal by keyword. Built for analysts, civic agents and automations that need public records as clean JSON.

Built and maintained by **Lathewright**. Lathewright is operated by AI systems under human ownership. A person reviews and approves everything we publish, send and sell. Pay only for what you get: **$0.01 per 100 rows**, **$0.002 per dataset description**.

### What you can do with it

- **Pull records on a schedule**: new 311 complaints, building permits, restaurant inspections, crime incidents, procurement awards, salaries, licences, since yesterday, into a sheet, a warehouse or a Slack digest.
- **Aggregate without downloading**: `$select: "complaint_type, count(*)"` with `$group` returns the summary table only.
- **Discover data**: `catalogSearch` finds datasets about "building permits" across 500+ portals with their columns and API endpoints, then `datasetInfo` shows row counts and column types before you spend on rows.
- **Agent tool**: an LLM can look up the dataset, inspect columns and run the query in three calls.

### Example tasks

Paste any of these into the Input tab, or send the same JSON through the API. Prices are the per-result prices listed under Pricing.

#### 1. Recent residential noise complaints in New York City

```json
{
  "mode": "query",
  "domain": "data.cityofnewyork.us",
  "datasetId": "erm2-nwe9",
  "select": "unique_key, created_date, complaint_type, descriptor, incident_zip, borough, status",
  "where": "complaint_type = 'Noise - Residential' AND created_date > '2026-10-17T00:00:00'",
  "order": "created_date DESC",
  "maxRows": 25
}
```

What comes back: Up to 25 rows with the selected columns. One block of 100 rows or fewer is $0.01.

#### 2. Find Chicago datasets about building permits

```json
{
  "mode": "catalogSearch",
  "q": "building permits",
  "domains": ["data.cityofchicago.org"],
  "maxRows": 10
}
```

What comes back: One row per matching dataset with `name`, `datasetId`, `category`, `rowCount` and `url`, each billed as a dataset description ($0.002).

#### 3. Describe one dataset's columns

```json
{
  "mode": "datasetInfo",
  "domain": "data.cityofnewyork.us",
  "datasetId": "erm2-nwe9"
}
```

What comes back: One metadata row with the dataset `columns`, `rowCount`, `license` and `apiEndpoint`. $0.002.

### Input

| Field | Type | Notes |
|---|---|---|
| `mode` | select | `query`, `datasetInfo`, `catalogSearch` |
| `domain`, `datasetId` | string | Portal host and 4x4 id from the dataset URL (`data.cityofnewyork.us`, `erm2-nwe9`) |
| `select`, `where`, `q`, `order`, `group`, `having` | string | SoQL clauses (`$select`, `$where`, `$q`, `$order`, `$group`, `$having`) |
| `maxRows`, `pageSize`, `offset` | integer | Row cap (billed per started block of 100), rows per request, start offset |
| `domains`, `categories` | array | Catalog search restrictions |
| `socrataAppToken` | secret | Optional free app token for higher portal rate limits |

```json
{
  "mode": "query",
  "domain": "data.cityofnewyork.us",
  "datasetId": "erm2-nwe9",
  "select": "unique_key, created_date, complaint_type, descriptor, incident_zip, borough, status",
  "where": "complaint_type = 'Noise - Residential' AND created_date > '2026-10-17T00:00:00'",
  "order": "created_date DESC",
  "maxRows": 5000
}
```

```json
{ "mode": "catalogSearch", "q": "building permits", "domains": ["data.cityofchicago.org", "data.seattle.gov"], "maxRows": 20 }
```

### Output

- `query`: every row is a raw dataset record with the dataset's own column names (dates as ISO strings, locations as objects). The run's key-value store gets a `QUERY_SUMMARY` record with the dataset name, licence, columns, the query that ran, rows pushed and `nextOffset` for continuing.
- `datasetInfo` and `catalogSearch`: one row per dataset:

```json
{
  "type": "dataset",
  "domain": "data.cityofchicago.org",
  "datasetId": "ydr8-5enu",
  "name": "Building Permits",
  "description": "Permits issued by the Department of Buildings...",
  "category": "Buildings",
  "attribution": "City of Chicago",
  "license": "See Terms of Use",
  "rowsUpdatedAt": "2026-10-18T10:02:11.000Z",
  "rowCount": 865123,
  "columns": [{ "fieldName": "permit_", "name": "PERMIT#", "type": "text", "description": "Permit number" }],
  "url": "https://data.cityofchicago.org/d/ydr8-5enu",
  "apiEndpoint": "https://data.cityofchicago.org/resource/ydr8-5enu.json"
}
```

### Pricing

| Event | Price | Notes |
|---|---|---|
| 100 rows returned (`rows-100`) | $0.01 | Primary event. 10,000 rows = $1.00; a partial last block counts as one |
| Dataset described (`dataset`) | $0.002 | `datasetInfo` and each `catalogSearch` hit |

Plus Apify's standard start fee ($0.00005 per run). No proxies, no browser. A failing query charges nothing. Set a **maximum cost per run** and the Actor stops cleanly at the limit with the rows so far saved.

### How to use it

- **Apify Console**: fill in the form, press Start, download JSON, CSV or Excel from the Output tab.
- **API**: `POST https://api.apify.com/v2/acts/lathewright~socrata-open-data-query/run-sync-get-dataset-items?token=<token>` with the input JSON as the body.
- **n8n, Make, Zapier**: use the Apify node/module, pick this Actor, paste the input JSON; use `nextOffset` from `QUERY_SUMMARY` to continue large pulls.

### Use from an AI agent (MCP)

Add this Actor as a tool in Claude, Cursor, VS Code or any MCP client with `https://mcp.apify.com/?tools=lathewright/socrata-open-data-query`. The Actor description is the tool description, and the input fields above are the tool parameters. Example prompts: "Find Chicago datasets about building permits", "How many noise complaints did each NYC borough log this week?".

### Data sources, robots.txt and fair use

- Portals are queried through the SODA API (`https://<portal>/resource/<id>.json`) and described through `https://<portal>/api/views/<id>.json`; the catalog through `api.us.socrata.com/api/catalog/v1` (no robots.txt, public discovery API).
- Before touching a portal the Actor fetches its robots.txt, refuses any portal that disallows the API paths and honours `Crawl-delay` for all user agents (the Socrata portals checked on 19 October 2026, including data.cityofnewyork.us, data.cityofchicago.org, data.seattle.gov, data.cdc.gov and data.ny.gov, allow `/resource/` and `/api/views/` and publish `Crawl-delay: 1`, which the Actor applies as a minimum spacing of one second; a portal asking for a longer delay, such as 60 seconds, is honoured as well).
- Each portal's own terms and dataset licence apply to what you do with the rows; the licence and attribution fields are returned with every dataset description. Public datasets occasionally contain names of individuals (e.g. licence holders); you are responsible for how you use them.
- One request at a time per portal, 100 to 10,000 rows per request, your optional app token raises Socrata's per-IP throttle.

### FAQ

**Which portals work?** Any Socrata-hosted portal (the URL has `/resource/` and dataset ids like `abcd-1234`). ArcGIS Hub, CKAN and OpenDataSoft portals are different products and are not covered.

**Why is my query rejected?** SoQL errors (unknown column, bad quote) come back verbatim in the failure message. Use `datasetInfo` to see exact field names.

**Can I download a whole dataset?** Up to 100,000 rows per run; continue with `offset` = `nextOffset` for more. For multi-million-row bulk exports use the portal's export links instead.

# Actor input Schema

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

`query` pulls rows from one dataset. `datasetInfo` returns its metadata, columns and row count. `catalogSearch` finds datasets across portals.

## `domain` (type: `string`):

Host of the portal, e.g. `data.cityofnewyork.us`, `data.cityofchicago.org`, `data.seattle.gov`, `data.cdc.gov`, `data.ny.gov`.

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

The 4x4 identifier from the dataset URL, e.g. `erm2-nwe9` (NYC 311 service requests).

## `select` (type: `string`):

Columns or aggregates, e.g. `unique_key, created_date, complaint_type, incident_zip` or `complaint_type, count(*)`. Empty = all columns.

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

SoQL filter, e.g. `created_date > '2026-10-01T00:00:00' AND complaint_type = 'Noise - Residential'`.

## `q` (type: `string`):

Full-text search across the dataset's text columns in `query` mode, or the search words in `catalogSearch` mode.

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

Sort, e.g. `created_date DESC`.

## `group` (type: `string`):

Group-by columns for aggregates in `$select`.

## `having` (type: `string`):

Filter on aggregates, e.g. `count(*) > 10`.

## `maxRows` (type: `integer`):

Row cap for `query` (billed per started block of 100) or dataset cap for `catalogSearch`.

## `pageSize` (type: `integer`):

Rows per request to the portal.

## `offset` (type: `integer`):

Skip this many rows first (continue a previous pull with its `nextOffset`).

## `domains` (type: `array`):

Restrict `catalogSearch` to these portals.

## `categories` (type: `array`):

Restrict `catalogSearch` to portal categories such as `Public Safety`, `Transportation`, `Health`.

## `socrataAppToken` (type: `string`):

Your free application token (any Socrata portal > Developer settings). Raises the per-IP throttling limits.

## `compactOutput` (type: `boolean`):

Return only the key fields (smaller, cheaper for AI agents to read).

## Actor input object example

```json
{
  "mode": "query",
  "domain": "data.cityofnewyork.us",
  "datasetId": "erm2-nwe9",
  "select": "unique_key, created_date, complaint_type, descriptor, incident_zip, borough, status",
  "where": "complaint_type = 'Noise - Residential' AND created_date > '2026-10-17T00:00:00'",
  "order": "created_date DESC",
  "maxRows": 25,
  "pageSize": 1000,
  "offset": 0,
  "compactOutput": false
}
```

# Actor output Schema

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

Dataset rows (query mode) or dataset descriptions (datasetInfo, catalogSearch).

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

Dataset metadata, columns, the query that ran, rows pushed and the next offset.

# 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 = {
    "domain": "data.cityofnewyork.us",
    "datasetId": "erm2-nwe9",
    "select": "unique_key, created_date, complaint_type, descriptor, incident_zip, borough, status",
    "where": "complaint_type = 'Noise - Residential' AND created_date > '2026-10-17T00:00:00'",
    "order": "created_date DESC",
    "maxRows": 25
};

// Run the Actor and wait for it to finish
const run = await client.actor("lathewright/socrata-open-data-query").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 = {
    "domain": "data.cityofnewyork.us",
    "datasetId": "erm2-nwe9",
    "select": "unique_key, created_date, complaint_type, descriptor, incident_zip, borough, status",
    "where": "complaint_type = 'Noise - Residential' AND created_date > '2026-10-17T00:00:00'",
    "order": "created_date DESC",
    "maxRows": 25,
}

# Run the Actor and wait for it to finish
run = client.actor("lathewright/socrata-open-data-query").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 '{
  "domain": "data.cityofnewyork.us",
  "datasetId": "erm2-nwe9",
  "select": "unique_key, created_date, complaint_type, descriptor, incident_zip, borough, status",
  "where": "complaint_type = '\''Noise - Residential'\'' AND created_date > '\''2026-10-17T00:00:00'\''",
  "order": "created_date DESC",
  "maxRows": 25
}' |
apify call lathewright/socrata-open-data-query --silent --output-dataset

```

## MCP server setup

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

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/sKM2EQ2ioYexC9fgJ/builds/h53o2WhoUtFBxiLnJ/openapi.json
