# Maryland Property Assessments - Values, Structure & Sales (`j0401/md-property-assessments`) Actor

All 2.44M Maryland real-property assessment accounts (public open data, 24 jurisdictions): assessment values, land/improvement split, CAMA structure (year built, sq ft, units, grade, type), last recorded sale with seller name, exemptions, homestead. Search by county, city, ZIP, address or sale date.

- **URL**: https://apify.com/j0401/md-property-assessments.md
- **Developed by:** [Wenhao Yang](https://apify.com/j0401) (community)
- **Categories:** Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.03 / 1,000 maryland property assessment records

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

## Maryland Property Assessments - Values, Structure & Sales

Every real property in Maryland is reassessed on a rolling cycle, and the Department of Assessments and Taxation publishes the result as an open record: one account per parcel, with what the State thinks the land and the buildings on it are worth, what actually stands there, and the last sale it has on file. This actor turns that file into a **charged-per-record search, filter and aggregate tool** over all **2,441,527 accounts** in the state.

**Built for:** comparable-sales work, portfolio and portfolio-adjacent screening, land-assembly research, exemption and homestead analysis, zoning and land-use studies, and anyone who needs **the State's own assessment record** rather than a third-party estimate of it.

### What it covers

**2,441,527 accounts** across **all 24 Maryland jurisdictions** - the 23 counties plus Baltimore City, which is a separate jurisdiction from Baltimore County and is never merged with it. One account = one row; every row is a distinct account number, with no fan-out by owner or by building.

Each record carries:

- **assessment values** - current total, the land / improvements split, the prior year's total, and the base-cycle (pre-reassessment) values
- **what stands on the land** - year built, structure area, dwelling units, land area, dwelling type, building style, grade and construction code
- **classification** - land use, zoning code, owner-occupancy code, subdivision, and the map / grid / parcel / block references that identify the parcel on the county plat
- **the most recent recorded sale** - transfer date, consideration, seller name, how conveyed, transfer number, and the market land / improvement values the State assigned at that sale
- **taxation and relief** - homestead qualification code and date, exempt class, and the county and state exemption assessments

### The fine print that matters

**222 columns is not 222 fields.** This is a flattened MDP export and a large block of it carries nothing. Measured on the full 2,441,527-row corpus: **25 numeric columns are 0 on every row where they are populated** (the assessment-credit and tax-roll value blocks), and **8 more columns are entirely empty**. Others are filled on every row but hold a single value - `phase_in_value` was verified to carry the *same number* as the total assessment on **all 2,441,527 rows**, and `stories`, `dwelling_condition` and the municipal exemption assessment repeat one value on 97-99%+ of rows. Those columns are **not returned**, and neither is the tax-roll block (`taxYear` is `0000` on every row; the billing date likewise). What this actor returns is the set of fields that actually vary.

**The assessment roll runs ahead of the calendar.** `assessment_cycleYear` is **2027 on 2,433,372 of 2,441,527 accounts (99.6%)**, because the State publishes the next cycle's values during the current one. If you are comparing to a tax bill you are holding, check the cycle year rather than assuming it matches the year on the paper.

**Sale dates are text, and they carry absence markers.** The transfer date is stored as `YYYY.MM.DD` text, not a date type, and **131,214 accounts (5.4%)** carry `0000.00.00` there instead of a sale - an account with no recorded sale in the file, not a sale in year zero. Those are stripped on the way out, and every date-range filter fences them so that "sold since 2020" cannot sweep them in. The homestead date carries the same kind of marker (`1899.12.30` / `1900.01.01` on 1,140,602 rows) and is stripped the same way.

**Not every sale is a market sale.** `salePrice` is `0` on 781,317 accounts - the deed was conveyed without consideration. The `conveyanceType` field says which kind of transfer it was, and on the full corpus **901,893 records are non-arms-length** (foreclosure, gift or auction) against **1,397,265 arms-length** ones. A comparable-sales run that ignores this field is averaging foreclosures into its comps.

**Exemptions are the exception, not the rule - and that is the point of returning them.** The county and state exemption assessments are non-zero on **~132,500 accounts (5.4%)**, and `exemptClass` is populated on **142,810 (5.8%)**. Those are the parcels whose taxable base differs from their assessed value, and they are the ones a plain property-value list gets wrong.

### Typical questions

- "Every account in **Montgomery County**, or in **Baltimore City**."
- "Accounts whose **most recent sale** was in the last six months, with the price and the seller."
- "**Comparable sales**: arms-length, improved, over $100,000."
- "Parcels **assessed over $500,000**."
- "**Exempt** property - churches, government, nonprofits - by exempt class."
- "**Homestead-qualified** owner-occupied property in a ZIP."
- "A single account by its **account number**."
- "Aggregate by **county**, **city**, **ZIP**, **land use**, **owner occupancy** or **dwelling type**."

### Inputs

| Input | What it does |
|---|---|
| `mode` | `rows` (default) / `aggregate` |
| `accountId` | exact State account number |
| `county` | one of the 24 jurisdictions (spelled either way the source uses) |
| `city` / `zip` | situs city substring / ZIP prefix |
| `address` | situs street-address substring |
| `landUse` / `zoning` | land-use class substring / exact zoning code |
| `ownerOccupancy` | `H` homeowner occupied, `N` not, `D` divided |
| `cycleYear` | assessment cycle the values belong to |
| `saleFrom` / `saleTo` | range on the account's most recent recorded sale |
| `minAssessment` | minimum current total assessment |
| `groupBy` | aggregate over county / city / zip / landUse / ownerOccupancy / dwellingType / cycleYear |
| `maxResults` | cap records (default 50) |

**Default run = 50 accounts** - a plain read of the whole roll, fast enough for the daily auto-test. For a targeted query add a filter; for a broad view use `aggregate`.

### Example inputs

**One account by number** - the exact State account id.

```json
{ "accountId": "04111108005540" }
```

**A county's accounts.** Either spelling works; `Baltimore` and `Baltimore City`
are matched as the separate jurisdictions they are.

```json
{ "county": "Montgomery", "maxResults": 5 }
```

**Recent recorded sales** - accounts whose most recent sale fell in this window.

```json
{ "saleFrom": "2026-01-01", "saleTo": "2026-09-27", "maxResults": 5 }
```

**High-value property in a ZIP prefix**

```json
{ "zip": "208", "minAssessment": "750000", "maxResults": 5 }
```

**How the state splits by land use** - one row per classification.

```json
{ "mode": "aggregate", "groupBy": "landUse" }
```

### Low cost

**From $0.00005 per record, down to $0.00003 at Gold** Pay-per-event: you are charged per record delivered, and nothing for the query. Cost scales with what you pull, not with the size of the file, and each record is metered individually - pulling one ZIP's worth of accounts costs a fraction of a cent.

### Example output

**One account** - `accountId=04111108005540` returns the assessment, the structure
description and the last recorded sale:

```json
{
  "platform": "md-property-assessments",
  "source": "maryland-real-property-assessments",
  "mode": "rows",
  "groupKey": "",
  "groupCount": "",
  "groupBy": "",
  "accountId": "04111108005540",
  "countyName": "Baltimore",
  "countyNameRaw": "Baltimore",
  "jurisdictionCode": "BACO",
  "address": "5215 BUSH ST",
  "city": "WHITE MARSH",
  "zip": "21162",
  "unit": "",
  "cycleYear": "2027",
  "totalAssessment": "198300",
  "landValue": "83500",
  "improvementValue": "114800",
  "priorYearTotalAssessment": "198300",
  "baseCycleLandValue": "83500",
  "baseCycleImprovementValue": "114800",
  "yearBuilt": "1952",
  "structureAreaSqFt": "768",
  "dwellingUnits": "1",
  "landArea": "4000.000",
  "landAreaUnit": "S",
  "dwellingType": "DWEL Standard Unit (0001)",
  "buildingStyle": "STRY 1 Story With Basement (0002)",
  "dwellingGrade": "Average (4)",
  "constructionCode": "Siding (001)",
  "landValuationUnit": "SQ",
  "landUse": "Residential (R)",
  "zoning": "",
  "ownerOccupancy": "H",
  "subdivision": "0000",
  "townCode": "",
  "legalDescription": "IMPS (Improvements).",
  "map": "0073",
  "grid": "0001",
  "parcel": "0341",
  "block": "K",
  "section": "",
  "saleDate": "2026-08-28",
  "salePrice": "0",
  "sellerName": "BRUCHEY JOHN F",
  "conveyanceType": "Private non-arms-length transfer such as a foreclosure, gift or auction (4)",
  "transferNumber": "000000",
  "saleMktLandValue": "83500",
  "saleMktImprovementValue": "111200",
  "homesteadCode": "A",
  "homesteadDate": "2009-01-27",
  "exemptClass": "",
  "countyExemptAssessment": "0",
  "stateExemptAssessment": "0",
  "lastActivityDate": "2026-08-24",
  "sourceUpdatedAt": "2026-09-03"
}
```

**An aggregate row** - `{"mode": "aggregate", "groupBy": "county"}` returns one
row per jurisdiction (24 rows, spelling variants merged), with the record fields
blank:

```json
{
  "platform": "md-property-assessments",
  "source": "maryland-real-property-assessments",
  "mode": "aggregate",
  "groupKey": "Montgomery",
  "groupCount": "355368",
  "groupBy": "county",
  "accountId": "",
  "countyName": "",
  "totalAssessment": "",
  "sourceUpdatedAt": "2026-09-03"
}
```

### Notes

- **Source:** Maryland Department of Assessments and Taxation, published on
  `opendata.maryland.gov` as public open data. No login, no key.
- **Freshness:** the portal stamps a batch refresh date on the whole file; every
  record carries it as `sourceUpdatedAt`.
- **Coverage:** all 24 jurisdictions. The file is the State's real-property roll;
  personal property, business filings and business licenses are separate state
  datasets and are not included here.

# Actor input Schema

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

rows = accounts matching your filters (default). aggregate = one count row per group (see groupBy).

## `accountId` (type: `string`):

Exact Maryland property account number, e.g. '04111108005540'. One account = one row. Blank = any.

## `county` (type: `string`):

One of Maryland's 24 jurisdictions, e.g. 'Montgomery', 'Baltimore City', "Prince George's". Note 'Baltimore' and 'Baltimore City' are separate jurisdictions. Blank = all.

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

Situs city substring, e.g. 'BETHESDA'. Blank = any.

## `zip` (type: `string`):

Situs ZIP, matched as a prefix (e.g. '207' for all 207xx ZIPs). Blank = any.

## `address` (type: `string`):

Situs street-address substring, e.g. 'HOWARD RD'. Blank = any.

## `landUse` (type: `string`):

Land-use class substring, e.g. 'Residential', 'Town House', 'Commercial', 'Agricultural', 'Industrial'. Blank = any.

## `zoning` (type: `string`):

Exact zoning code, e.g. 'R-1'. Blank = any.

## `ownerOccupancy` (type: `string`):

Owner-occupancy code as the State records it: H = homeowner occupied, N = not owner occupied, D = divided. Blank = any. Codes are reported as published; their full legal meaning is not restated here.

## `cycleYear` (type: `string`):

Assessment cycle the values belong to, e.g. '2027'. Blank = any.

## `saleFrom` (type: `string`):

Earliest recorded sale date (YYYY-MM-DD) for the account's most recent sale, e.g. '2025-01-01'. Blank = any.

## `saleTo` (type: `string`):

Latest recorded sale date (YYYY-MM-DD) for the account's most recent sale. Blank = any.

## `minAssessment` (type: `string`):

Only accounts whose current total assessment is at least this amount (USD), e.g. '500000'. Blank = any.

## `groupBy` (type: `string`):

aggregate mode only: the dimension to count by.

## `maxResults` (type: `integer`):

Maximum rows to return in rows mode (1-10000). Default 50.

## Actor input object example

```json
{
  "mode": "rows",
  "accountId": "",
  "county": "",
  "city": "",
  "zip": "",
  "address": "",
  "landUse": "",
  "zoning": "",
  "ownerOccupancy": "",
  "cycleYear": "",
  "saleFrom": "",
  "saleTo": "",
  "minAssessment": "",
  "groupBy": "county",
  "maxResults": 50
}
```

# Actor output Schema

## `recordsUrl` (type: `string`):

Maryland property assessment records or aggregates - as JSON

## `datasetUrl` (type: `string`):

No description

## `runUrl` (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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("j0401/md-property-assessments").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("j0401/md-property-assessments").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 '{}' |
apify call j0401/md-property-assessments --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,j0401/md-property-assessments"
        }
    }
}
```

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/LfvEhxyQjkGrpSkYC/builds/IbR85jg2bdtu1chkM/openapi.json
