# US Parcel & Property Assessment Scraper (2 cities) (`steadfetch/us-parcel-assessment-extractor`) Actor

Public parcel & property-assessment data from Los Angeles & Denver in one normalized schema: parcel IDs, legal parts, land use, values, last sale and districts, plus a parcel change monitor. For real estate research, valuation analytics and market monitoring. Open data, no login, no owner names.

- **URL**: https://apify.com/steadfetch/us-parcel-assessment-extractor.md
- **Developed by:** [Steadfetch](https://apify.com/steadfetch) (community)
- **Categories:** Real estate, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.20 / 1,000 parcel delivereds

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

## US Parcel & Property Assessment Scraper (2 cities)

Public parcel and property-assessment records from Los Angeles and Denver in one normalized property-side schema. One row per parcel with parcel identifiers, legal parcel parts, situs address, land use, building and land values, last sale and districts, plus an optional change monitor for a parcel watchlist.

### What it does

- Queries two official US open-data JSON endpoints (Socrata) and returns **one unified schema** regardless of portal.
- Normalizes Los Angeles (`qyra-qm2s`) and Denver (`tsdg-z9uy`) into the same fields, so downstream code does not need two parsers.
- Requests only an explicit **property-side column allow-list** with SoQL `$select`. Owner, taxpayer and mailing-address columns are never downloaded, even when the source publishes them.
- Supports optional `parcelIds` and a `bbox` filter for both cities (both publish geometry).
- Optional **monitor mode**: keeps a property-side snapshot per parcel in the Actor key-value store and emits only parcels that are new or whose fields changed. A quiet day delivers no rows and bills 0 results; each watched check bills the cheap `monitor-check` event (0.10 USD per 1,000 checks).
- Paginates with SoQL `$limit`/`$offset` and respects each portal's `Crawl-delay: 1` (at least 1 second between requests).

### Sources and compliance

| City | Dataset id | Public endpoint | Licence |
| --- | --- | --- | --- |
| Los Angeles, CA | `qyra-qm2s` | `https://data.lacity.org/resource/qyra-qm2s.json` | CC0 1.0 Universal (Public Domain Dedication). |
| Denver, CO | `tsdg-z9uy` | `https://data.colorado.gov/resource/tsdg-z9uy.json` | Public Domain (State of Colorado open data). |

Both portals allow `/resource/*` in `robots.txt` and declare `Crawl-delay: 1`. This Actor only calls those JSON endpoints, with a descriptive User-Agent that identifies Steadfetch: no login, no token, no CAPTCHA bypass, no browser and no proxies. The HTML front ends and proprietary assessor search portals are never requested.

A third source, Ramsey County MN (`4fwy-py9e`), was evaluated and **discarded**: its `robots.txt` declares `Disallow: /` for every user-agent, so it is not a permitted source under Steadfetch rule 3. It is not queried by this Actor.

**No personal data (binding rule).** Owner names, taxpayer names and mailing addresses are excluded by construction: the queries select property-side columns only. The Denver dataset publishes `owner_*` and mailing columns and the Actor never requests them. Output contains property facts only and complies with the RGPD.

#### Attribution

- Contains information from the City of Los Angeles Open Data Portal (dataset `qyra-qm2s`, Bureau of Engineering), made available under CC0 1.0.
- Data provided by the State of Colorado Open Data Portal (dataset `tsdg-z9uy`).

### Input

| Field | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `city` | string | Yes | `denver` | `los-angeles` or `denver`. |
| `parcelIds` | array | No | `[]` | Optional parcel filter (`pind` in Los Angeles, `schednum` in Denver). Required in monitor mode: it is the watchlist. |
| `bbox` | string | No | empty | `"west,south,east,north"` in decimal degrees. Both sources publish geometry. |
| `mode` | string | No | `extract` | `extract` returns every parcel in scope; `monitor` emits only new or changed watchlist parcels. |
| `maxItems` | integer | No | `1000` | Maximum parcels read (extract) or checked (monitor), 1-5000. |
| `strictSchema` | boolean | No | `true` | Fail the run when a row does not match the output schema. Keep enabled. |

#### Example input

Matches the declared `exampleRunInput`:

```json
{
  "city": "denver",
  "maxItems": 25,
  "mode": "extract"
}
```

#### Monitor mode

```json
{
  "city": "denver",
  "parcelIds": ["1100001000", "2100002000"],
  "mode": "monitor",
  "maxItems": 100
}
```

The first monitor run emits every watchlist parcel as `changeType: "new"`. Later runs compare against the stored snapshot and emit only parcels whose property-side fields changed, with `changeType: "updated"` and the list of changed fields. State is kept under `us-parcel-assessment-extractor-state`, keyed by city, parcel id and roll year when the source publishes one.

### Output

#### Example output

```json
{
  "url": "https://data.colorado.gov/resource/tsdg-z9uy.json?$where=schednum%3D'1100001000'",
  "title": "1100001000 - 26100 E 88TH AVE",
  "scrapedAt": "2026-10-25T00:00:00.000Z",
  "city": "denver",
  "sourceDataset": "tsdg-z9uy",
  "parcelId": {"primary": "1100001000", "scheduleNumber": "1100001000", "appendage": "0", "map": "11", "block": "0", "lot": "1"},
  "address": {"full": "26100 E 88TH AVE", "city": "DENVER", "state": "CO", "zip": null},
  "tract": null,
  "block": "0",
  "lot": "1",
  "landUse": {"code": "9139", "description": "VACANT LAND"},
  "yearBuilt": null,
  "buildingAreaSqft": null,
  "landAreaSqft": 7974751,
  "valuations": {"currency": "USD", "land": 45800, "building": 0, "total": 45800, "marketTotal": 45800, "assessedTotal": 11910, "taxable": 0},
  "lastSale": {"date": null, "year": null, "price": null},
  "rollYear": null,
  "districts": {"council": null, "engineering": null, "engineeringName": null, "zone": "DIA", "municipality": "DENVER"},
  "collectedAt": "2026-10-25T00:00:00.000Z"
}
```

#### Output fields

| Field | Type | Description |
| --- | --- | --- |
| `url` | string | Public JSON row permalink used as the source URL. |
| `title` | string | Parcel identifier plus situs address. |
| `scrapedAt` | date-time | ISO 8601 UTC timestamp of the run. |
| `city` | string | `los-angeles` or `denver`. |
| `sourceDataset` | string | Socrata dataset id. |
| `parcelId` | object | `primary` plus the city-specific identifiers the source publishes (`mod` is populated for Los Angeles). |
| `address` | object | Situs address (`full`, `city`, `state`, `zip`); null parts are never invented. |
| `tract`, `block`, `lot` | string or null | Legal parcel parts when published. Los Angeles also reports `parcelId.mod`. |
| `landUse` | object | Property/land-use code and description as published. |
| `yearBuilt` | number or null | Year built when published. |
| `buildingAreaSqft`, `landAreaSqft` | number or null | Building and land area when published. |
| `valuations` | object | USD land/building/total, market value, assessed value and taxable amount; null when the source does not publish it (the Los Angeles parcel layer has no values). |
| `lastSale` | object | Last sale date, year and price when published. |
| `rollYear` | string or null | Assessment/roll year when published. |
| `districts` | object | Council, engineering, zone and municipality when published. |
| `collectedAt` | date-time | Timestamp of the normalized record. |
| `changeType`, `changedFields` | string / array | Monitor mode only: `new` or `updated` plus the changed property-side fields. |

New fields are additive and required fields never change, so downstream code can rely on the documented contract.

### Pricing

Pay-per-event with the two synthetic platform events plus the reserved per-check event for the monitor:

- `apify-actor-start`: 0.00005 USD per run (Apify-recommended default, includes the first 5 seconds of compute).
- `apify-default-dataset-item`: **0.004 USD per parcel = 4.00 USD per 1,000 results**.
- `monitor-check`: **0.0001 USD per watched parcel checked = 0.10 USD per 1,000 checks**, charged once per monitor run for every watchlist parcel actually checked, including checks where nothing changed (STE-389 contract). Extract runs (no watchlist) never charge this event.

Users are never billed twice: rows rejected by the schema are never pushed and never charged; unchanged checks bill only the check event, and new or changed parcels bill the per-result event on top of their check. A 1,000-parcel watchlist with a quiet day (nothing changed) costs about 0.10 USD in checks plus the run start.

Measured cost is about **0.001 USD per 1,000 parcels** (JSON endpoints, no browser, no proxies). The result price is inside the Steadfetch 1-10 USD per 1,000 results band and about 10x below the closest proprietary owner-lookup competitor, which this Actor intentionally does not replicate; the product is official, cross-city normalized property data. The check price sits inside the 0.01-1.00 USD per 1,000 checks monitor band with a wide margin over the measured compute cost per check.

**Works on the Apify free plan**: with the 5 USD monthly credit, about **1,249 parcels** can be delivered per month (N = floor((5.00 - 0.00005) / 0.004)).

### Usage with the API

```bash
curl -X POST "https://api.apify.com/v2/acts/steadfetch~us-parcel-assessment-extractor/runs?token=<YOUR_API_TOKEN>" \
  -H 'Content-Type: application/json' \
  -d '{"city":"denver","maxItems":100,"mode":"extract"}'
```

Then read the default dataset of the run.

### FAQ

**Do I need an API key for the portals?** No. Both are anonymous public Socrata endpoints. The Actor sends only a descriptive User-Agent and respects each portal's crawl delay.

**Are both cities really the same schema?** Yes. Parcel identifiers, legal parts, address, land use, areas, valuations, last sale and districts are normalized. Fields a city does not publish are `null`, never invented. The Los Angeles parcel layer, for example, publishes no valuation or address; it is delivered as a parcel-identifier, legal-part and district layer.

**Does the Actor return owner names?** No, never. Owner, taxpayer and mailing-address columns are excluded by construction and are not even downloaded. Steadfetch does not sell personal data.

**Which legal parcel parts are filled?** Los Angeles publishes `tract`, `block`, `mod` and `lot`, so all of them are delivered. Denver publishes `block` and `lot`; its `tract` stays `null`.

**What does monitor mode bill?** The check itself plus the new result: every watchlist parcel actually checked charges `monitor-check` (0.10 USD per 1,000 checks, including checks where nothing changed), and only new or changed parcels additionally charge the per-result event (4.00 USD per 1,000 results). The first run emits the watchlist as `new`; later runs emit only drift. Extract runs never charge the check event.

**Does the Actor use a browser?** No. Both sources are JSON APIs, so HTTP is roughly 10x cheaper than a browser.

**Is the output schema stable?** Yes. New fields are added additively and required fields never change.

### Maintenance

The Actor is **tested daily** through the Steadfetch catalogue QA and the monorepo CI (`npm test` and `npm run publish:check` on every push). Repairs take priority over new features. Schema changes are announced through repository releases.

### Support

Report bugs or request features through the Apify Store page (Issues tab) of this Actor, or through the Steadfetch actors repository issues. Include the run ID and the input used.

# Actor input Schema

## `city` (type: `string`):

Official open-data portal to query: Los Angeles (CC0 1.0) or Denver (public domain).

## `parcelIds` (type: `array`):

Optional list of parcel identifiers to fetch (pind in Los Angeles, schednum in Denver). Required in monitor mode: it is the watchlist.

## `bbox` (type: `string`):

Optional geographic filter as "west,south,east,north" in decimal degrees. Both sources publish geometry.

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

extract returns every parcel in the selected scope. monitor checks the parcelIds watchlist and emits only parcels that are new or whose property-side fields changed since the previous run.

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

Maximum number of parcels read (extract) or checked (monitor) in this run.

## `strictSchema` (type: `boolean`):

Fail the run when a row does not match the output schema. Keep enabled: Steadfetch never publishes Actors without output-schema validation.

## Actor input object example

```json
{
  "city": "denver",
  "parcelIds": [],
  "mode": "extract",
  "maxItems": 25,
  "strictSchema": true
}
```

# Actor output Schema

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

Dataset items validated against the item schema in dataset_schema.json (fields).

# 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 = {
    "city": "denver",
    "mode": "extract",
    "maxItems": 25
};

// Run the Actor and wait for it to finish
const run = await client.actor("steadfetch/us-parcel-assessment-extractor").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 = {
    "city": "denver",
    "mode": "extract",
    "maxItems": 25,
}

# Run the Actor and wait for it to finish
run = client.actor("steadfetch/us-parcel-assessment-extractor").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 '{
  "city": "denver",
  "mode": "extract",
  "maxItems": 25
}' |
apify call steadfetch/us-parcel-assessment-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,steadfetch/us-parcel-assessment-extractor"
        }
    }
}
```

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/HXSZEc7VSwzS4hfSW/builds/6cFBCz00L0C4kFJld/openapi.json
