# MD Baltimore County Permits - Building & Trade (`j0401/md-baltco-permits`) Actor

Baltimore County, MD building & trade permit registers (~310k, public open data): who pulled a permit for what and where. Building/construction (~165k, est. cost + depth), electrical (~64k), gas (~42k), plumbing (~39k). Search by permit number, owner, address, status, date; aggregate by type/status.

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

## Pricing

$0.30 / 1,000 md baltimore county permit records

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Baltimore County (MD) Building & Trade Permits

Who pulled a permit for what, where, in Baltimore County, Maryland - the
building / construction, electrical, gas and plumbing permit registers in one
query API.

### Data

Four county permit registers (public government open data) unified under one
schema - **~310,000 permits**:

| Register | Records | What it holds |
|---|---|---|
| **building** | ~165,000 | building & construction permits - new dwellings, alteration/addition, decks, fences, sprinklers, accessory structures, commercial work. The deep register: **owner, estimated cost, assessor parcel, existing/proposed use, district**, full work description |
| **electrical** | ~64,000 | electrical permits (residential + commercial) |
| **gas** | ~42,000 | gas permits |
| **plumbing** | ~39,000 | plumbing permits |

Every permit carries the owner, the issued / applied / occupancy dates, the
permit type and subtype, and the full scope-of-work text. Street address is
published on essentially every building permit and on roughly half of the
trade registers; the assessor parcel is on the building register. Work dates
run current - the register is live to this week.

### Low cost

**From $0.0003 per record.**

Digging county permit records usually means trading between four separate
registers that disagree with each other. The building layer speaks a different
status vocabulary than the trade layers (`ISSUE` / `CANCELLED` there, `ISSUED`
/ `CANCEL` here), costs only exist on the building register, and each layer's
\~65-column depth is mostly empty scaffolding. We normalized all four onto one
schema with per-register status matching, real date parsing, and empty-field
handling - so `permitNumber` / `owner` / `status` / `workDescription` /
`address` / `district` / `zip` / date-window filters just work, and aggregates
by type / status / subtype / district / land use come back clean. You query one
clean register; the archaeology stays with us.

**Every record is one flat JSON object** - no HTML, no pagination to chase, no
shape-shifting between registers.

### Use cases

- **Construction & trade leads** - find permits by type, district, or date
  window; track new dwelling construction in a council district
- **Diligence** - check whether a property or owner has active or recent
  permits, by address, owner, or exact permit number
- **Market analysis** - aggregate what kind of work is being pulled, where,
  over time

### Inputs

| Field | What it does |
|---|---|
| `source` | building / electrical / gas / plumbing register |
| `permitNumber` | exact permit lookup (e.g. `R26-05266`) |
| `owner` / `address` / `zip` | find permits by property or owner |
| `workDescription` | search the scope-of-work text |
| `status` | filter by exact status, or `issued`/`open`/`closed`/`expired`/`cancelled`/`complete` |
| `issuedFrom` / `issuedTo` / `appliedFrom` / `appliedTo` | date windows |
| `aggregate` + `groupBy` | summary counts by `type` / `status` / `subtype` / `district` / `landUse` |

### Output fields

`permitNumber` / `status` / `type` / `subtype` / `workDescription` / `ownerName`
/ `address` / `district` / `zip` / `estCost` / `applyDate` / `issuanceDate` /
`occupancyDate` / `assessorNumber` / `proposedUse` / `existingUse` / `landUse` /
`totalUnits` / `floorArea` + register depth fields.

> Note on honesty: the source publishes the **contractor on no register** (that
> column exists but is empty everywhere) - this API does not invent it. Building
> permits carry estimated cost on ~14% of rows, districts on ~14%; trade
> permits carry neither. Those fields are returned only where the county
> actually records them.

Source: Baltimore County government open data (public permit registers).

# Actor input Schema

## `source` (type: `string`):

building = building & construction permits (~165k, new dwelling / alteration / fence / deck / sprinkler ...; owner + assessor parcel on most rows, estimated cost + council district on ~14%). electrical (~64k), gas (~42k), plumbing (~39k) = the trade registers (owner, description, dates; street address ~55%).

## `permitNumber` (type: `string`):

Exact permit number, e.g. R26-05266 (case-insensitive).

## `owner` (type: `string`):

Property owner name substring. Filled on ~86-99% of permits.

## `status` (type: `string`):

Filter by exact source status. Building: ISSUE / CLOSED / OPEN / CANCELLED / EXPIRED. Trades: ISSUED / COMPLETE / CANCEL / ISSUE / EXPIRED / CLOSED / ON-HOLD / FEESDUE / BL-SUSPEND. Or a word: 'issued' / 'open' (the issued/in-play slice), 'closed', 'cancelled', 'expired', 'complete'. Blank = any status.

## `workDescription` (type: `string`):

Substring of the scope-of-work text (e.g. 'deck', 'sprinkler', 'sewer').

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

Street address substring (e.g. 'Aberdeen').

## `district` (type: `string`):

Baltimore County council district number. Populated on the building register (~14%); blank on most trade rows.

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

ZIP code (prefix match).

## `issuedFrom` (type: `string`):

Only permits issued on/after this date (YYYY-MM-DD).

## `issuedTo` (type: `string`):

Only permits issued on/before this date (YYYY-MM-DD).

## `appliedFrom` (type: `string`):

Only permits applied on/after this date (YYYY-MM-DD).

## `appliedTo` (type: `string`):

Only permits applied on/before this date (YYYY-MM-DD).

## `aggregate` (type: `boolean`):

When on, returns one summary record per group (see groupBy) with a count instead of individual permits.

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

Which dimension to aggregate over. type = the permit class (Res. Permit - New Dwelling / Alteration / Fence / Sprinkler ... or RESIDENTIAL ELECTRICAL / GAS / PLUMBING). subtype = the detailed sub-description. district only meaningful on the building register.

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

Cap the number of records pushed (0 = up to ~10k per run; each record is metered individually, so there is no per-run charge cap). An aggregate has at most a few hundred groups.

## Actor input object example

```json
{
  "source": "building",
  "permitNumber": "",
  "owner": "",
  "status": "",
  "workDescription": "",
  "address": "",
  "district": "",
  "zip": "",
  "issuedFrom": "",
  "issuedTo": "",
  "appliedFrom": "",
  "appliedTo": "",
  "aggregate": false,
  "groupBy": "status",
  "maxResults": 200
}
```

# Actor output Schema

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

Baltimore County building & trade permit 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-baltco-permits").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-baltco-permits").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-baltco-permits --silent --output-dataset

```

## MCP server setup

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

```

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/iTRp89uF6GdJjPeeX/builds/dhQwbrA60gGtD3DSp/openapi.json
