# Seattle Open Data Scraper (`kawsar/seattle-open-data-scraper`) Actor

Seattle Open Data Scraper extracts City of Seattle datasets, such as building permits, or searches the public catalog. Save clean, flat records directly as JSON, CSV, or Excel spreadsheets for easy tracking and research

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

## Pricing

from $1.99 / 1,000 results

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

## Seattle Open Data Scraper: data.seattle.gov (Socrata)

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

Extract building permits, civic records, and public dataset catalogs from Seattle's open data platform into flat JSON, CSV, or Excel files.

The default setup fetches building permit details (cap of 100 rows per run). Set up automated schedules whenever you need fresh exports for reporting, tracking, or analysis.

***

### Use Cases

- **Permit Tracking:** Export building permit details—including class, type, key dates, estimated cost, and address—straight into a spreadsheet.
- **Civic Research:** Collect dataset snapshots on a regular schedule to analyze changes over time.
- **Catalog Search:** Search available public datasets by keyword to easily grab dataset IDs and direct links.
- **Filtered Exports:** Apply custom filtering conditions when you only need records matching a specific permit class or status.
- **Newsroom & Planning Desks:** Refresh target data tables before stories, meetings, or public hearings while keeping strict limits on row counts.

***

### Extracted Data & Schema

Each extracted record includes a link back to the source record along with a timestamp marking when it was pulled.

| Field | Type | Description |
| --- | --- | --- |
| `permitnum` | string | Permit identification number |
| `permitclass` | string | Broad permit classification |
| `permitclassmapped` | string | Mapped permit classification category |
| `permittypemapped` | string | Mapped permit type category |
| `permittypedesc` | string | Specific permit type description |
| `permitDescription` | string | Detailed project work description |
| `housingunits` | string | Total housing units associated with the permit |
| `housingunitsremoved` | string | Housing units removed |
| `housingunitsadded` | string | Housing units added |
| `estprojectcost` | string | Estimated project valuation in USD |
| `applieddate` | string | Date application was submitted |
| `issueddate` | string | Date permit was officially issued |
| `expiresdate` | string | Date permit expires |
| `completeddate` | string | Date work/inspection was completed |
| `statuscurrent` | string | Current processing status |
| `originaladdress1` | string | Street address |
| `originalcity` | string | City |
| `originalstate` | string | State |
| `originalzip` | string | ZIP code |
| `link` | object | Record link object |
| `latitude` | string | Geographic latitude coordinate |
| `longitude` | string | Geographic longitude coordinate |
| `location1` | object | Record location object |
| `daysoutcorrections` | string | Days spent awaiting corrections |
| `numberreviewcycles` | string | Number of review cycles completed |
| `zoning` | string | Property zoning designation |
| `dwellingunittype` | string | Specific dwelling unit type |
| `standardplan` | string | Standard plan indicator |
| `dependentbuilding` | string | Dependent building indicator |
| `housingcategory` | string | Housing classification category |
| `catalogId` | string | Dataset identifier from catalog searches |
| `catalogName` | string | Dataset title from catalog searches |
| `permalink` | string | Catalog entry permalink |
| `domainName` | string | Dataset host domain |
| `scrapedAt` | string | UTC timestamp recorded at scrape time |
| `sourceUrl` | string | Full web address used during extraction |
| `error` | string | Error details if a row fails; empty on successful records |

***

### Sample Output

```json
{
  "permitnum": "6064208-CN",
  "permitclass": "Single Family/Duplex",
  "permitclassmapped": "Residential",
  "permittypemapped": "Building",
  "permittypedesc": "New",
  "permitDescription": "Establish use as single family dwelling and construct a new single family residence with attached garage all per plan.",
  "housingunits": "1.0",
  "housingunitsremoved": "0.0",
  "housingunitsadded": "1.0",
  "estprojectcost": "380715.0000",
  "applieddate": "2006-03-08T00:00:00.000",
  "issueddate": "2006-08-10T00:00:00.000",
  "expiresdate": "2008-02-10T00:00:00.000",
  "completeddate": "2008-02-07T00:00:00.000",
  "statuscurrent": "",
  "originaladdress1": "",
  "link": {},
  "location1": {},
  "scrapedAt": "2026-10-12T00:00:00+00:00",
  "sourceUrl": "https://data.seattle.gov/resource/76t5-zqzr.json?$limit=50&$offset=0",
  "error": ""
}

```

***

### Input Options

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | `rows` | Select `rows` to export a single dataset, or `catalog` to search available datasets. |
| `datasetId` | string | `76t5-zqzr` | Dataset identifier (e.g. building permits). Used in `rows` mode. |
| `where` | string | *empty* | Custom filtering condition for rows mode (e.g., `permitclassmapped = 'Residential'`). |
| `query` | string | `permits` | Keyword search query used in `catalog` mode. |
| `maxItems` | integer | `100` | Maximum number of records to pull (hard limit: 1000). |
| `timeoutSecs` | integer | `300` | Overall run execution timeout limit in seconds. |
| `requestTimeoutSecs` | integer | `30` | Timeout per individual network request in seconds. |
| `proxyConfiguration` | object | `{"useApifyProxy": false}` | Network configuration settings. |

#### Sample Input Configuration

```json
{
  "mode": "rows",
  "datasetId": "76t5-zqzr",
  "where": "permitclassmapped = 'Residential'",
  "query": "permits",
  "maxItems": 100,
  "timeoutSecs": 300,
  "requestTimeoutSecs": 30,
  "proxyConfiguration": { "useApifyProxy": false }
}

```

***

### How Data Collection Works

- Runs extract records in batches of 50.
- Paging proceeds sequentially until all matching records are gathered or your configured `maxItems` threshold is hit.
- Built-in deduplication prevents identical permit or catalog IDs from appearing twice in your output.
- Requests automatically retry up to 3 times if temporary connection interruptions occur, using exponential backoff (2s, 4s, 8s).
- Unhandled request errors are captured directly inside an `error` field on a individual record row, allowing the run to finish gracefully without crashing.

***

### Frequently Asked Questions

**How many rows can I export in a single run?**

The default limit is set to 100 records per run, with a maximum limit of 1,000. If you need to pull large historical datasets, set up scheduled runs using filtered queries to break up your collection.

**What happens if I enter an invalid filter or dataset ID?**

Instead of crashing the entire run, the actor records an error row containing details about what went wrong and continues processing.

**Why is the work description field labeled `permitDescription`?**

The platform reserves `description` for internal schema handling, so we renamed the field to `permitDescription` to keep the dataset structure consistent and clear.

#### Disclaimer

Disclaimer: This Actor is an independent tool and is not affiliated with, endorsed by, sponsored by, or otherwise officially associated with Seattle 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 Seattle Open Data, mostly through its public API or feed, 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 Data source and licence 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`):

Choose rows to export records from one Socrata dataset, or catalog to search the data.seattle.gov catalog.

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

Four-by-four Socrata dataset id on data.seattle.gov. Building permits use 76t5-zqzr. Ignored in catalog mode.

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

Optional SoQL $where filter applied to dataset rows, for example permitclassmapped = 'Residential'. Ignored in catalog mode.

## `query` (type: `string`):

Search text sent as q to the Socrata catalog API. Used only in catalog mode.

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

Optional Socrata application token sent as the X-App-Token header. Leave blank to use the public shared pool. The value is a secret and is never written to the dataset.

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

Maximum rows to save in one run. Values above 1000 are clamped to 1000.

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

Stop fetching when this many seconds have elapsed. The run still keeps rows already saved.

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

How long to wait for a single HTTP response before retrying.

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

Optional proxy for requests. Leave Apify Proxy off. The public SODA endpoints are called directly so the run stays inside the shared rate-limit pool.

## Actor input object example

```json
{
  "mode": "rows",
  "datasetId": "76t5-zqzr",
  "where": "permitclassmapped = 'Residential'",
  "query": "permits",
  "maxItems": 100,
  "timeoutSecs": 300,
  "requestTimeoutSecs": 30,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `dataset` (type: `string`):

Open the full dataset of exported Seattle open data rows.

## `datasetItems` (type: `string`):

JSON items endpoint for the default dataset.

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

Table view of the default dataset.

# 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": "rows",
    "datasetId": "76t5-zqzr",
    "where": "",
    "query": "permits",
    "maxItems": 100,
    "timeoutSecs": 300,
    "requestTimeoutSecs": 30,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("kawsar/seattle-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": "rows",
    "datasetId": "76t5-zqzr",
    "where": "",
    "query": "permits",
    "maxItems": 100,
    "timeoutSecs": 300,
    "requestTimeoutSecs": 30,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("kawsar/seattle-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": "rows",
  "datasetId": "76t5-zqzr",
  "where": "",
  "query": "permits",
  "maxItems": 100,
  "timeoutSecs": 300,
  "requestTimeoutSecs": 30,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call kawsar/seattle-open-data-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,kawsar/seattle-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/WGrnktpVs4bhkksd2/builds/VD6aXTqzkpHZRkswH/openapi.json
