# Maricopa Building Permits Scraper (`automation-lab/maricopa-county-building-permit-records`) Actor

Export official Maricopa building permits with project addresses, parcels, status, type, descriptions and application/issuance dates. Filter the county GIS inventory for recurring project research; excludes municipal completeness and private contacts.

- **URL**: https://apify.com/automation-lab/maricopa-county-building-permit-records.md
- **Developed by:** [Automation Lab](https://apify.com/automation-lab) (community)
- **Categories:** Real estate
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $9.60 / 1,000 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.
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

## Maricopa Building Permits Scraper

Refresh a project inventory from official **Maricopa building permits** in the county's public GIS. Export permit numbers, status, type, project address, parcel, description, and application/issuance dates into a structured dataset.

This Actor serves property researchers, construction analysts and teams refreshing county project inventories. It reads the county Building Permits GIS view, not every municipality's permit portal. It does not provide private contacts, contractor enrichment or a complete historical workflow log.

### Who is it for?

Construction analysts, property researchers and data teams can refresh county permit project inventories, compare status snapshots and cite official permit evidence.

### What does it do?

The Actor queries the official public county GIS inventory, applies your filters, deduplicates permits and saves normalized records to the default Apify dataset. Optional date filters use application dates. Leave filters unset for an unfiltered inventory sample.

### Why use this Actor?

- Reuse one consistent permit schema for recurring inventory refreshes.
- Join project permits to parcel research using public parcel identifiers.
- Keep source query links for checking project evidence.
- Filter by a known permit, status, type, address substring or application date.
- Avoid downloading weekly spreadsheets when you need the current GIS inventory.

County public data can omit fields, change classifications or lag actual project activity. Verify important conclusions against the county's records.

### Getting started

1. Open the Actor's Input tab.
2. Set `maxItems` to 20 for a first sample.
3. Optionally enter filters; every supplied filter combines with AND.
4. Run the Actor and open the dataset.
5. Download JSON, CSV, Excel or another Apify dataset export format.

Example input:

```json
{"maxItems":20}
```

### Input parameters

| Parameter | Meaning |
|---|---|
| `maxItems` | Global unique-permit limit, default 100, range 1–100000; no zero/unlimited mode. |
| `permitNumber` | Exact whole-field permit number, case-insensitive; trims outer whitespace. |
| `status` | Exact whole-field county status, case-insensitive; trims outer whitespace. |
| `permitType` | Exact whole-field county classification, such as `Building (Commercial)`. |
| `addressContains` | Case-insensitive literal substring of street address only; trims outer whitespace; rejects `%` and `_`. |
| `appliedFrom` | Inclusive application-date lower bound, YYYY-MM-DD in UTC. |
| `appliedTo` | Inclusive application-date upper bound, YYYY-MM-DD in UTC. |

No filter searches the project description. Date filters exclude records without application dates. Invalid dates, reversed ranges, unknown input fields and malformed limits fail the run rather than silently ignoring the request. A valid search with no matching permits succeeds with an empty dataset.

### Filtering examples

Find a known permit:

```json
{"permitNumber":"B202306811","maxItems":1}
```

Search a completed commercial project cohort:

```json
{"status":"Complete","permitType":"Building (Commercial)","addressContains":"Bell","appliedFrom":"2023-01-01","appliedTo":"2023-12-31","maxItems":25}
```

The address filter is a substring, not geocoding or a whole-word query. `Bell` can match any address containing that sequence. Multiple filters narrow the same county source; there are no separate URL/discovery modes.

### Extracted data

| Field | Meaning |
|---|---|
| `objectId` | GIS object ID; useful for source provenance. |
| `permitNumber`, `permitCenterId` | Public permit identifiers. |
| `permitType`, `workClass`, `status` | County classifications and current status. |
| `address`, `zipCode`, `parcelNumber` | Project location and public parcel reference. |
| `description` | Source project description, potentially abbreviated. |
| `applicationDate`, `issuedDate` | Source application and issuance dates, ISO UTC timestamps. |
| `expirationDate` | GIS ExpirationDate field; metadata aliases it CreateDate, so do not assume compliance meaning without confirmation. |
| `workflowStep`, `workflowStepStatus`, `workflowStepDate` | Current workflow step snapshot. |
| `workflowAction`, `workflowActionDate` | Displayed workflow action and date. |
| `sourceUrl`, `scrapedAt` | Official record-query link and extraction timestamp. |

Unavailable values are null. ZIP codes are text. Workflow fields represent the current displayed state, not a sequence of historical events.

### Output example

A representative observed permit, shortened to core fields:

```json
{
  "objectId":111765,
  "permitNumber":"B202306811",
  "permitType":"Building (Commercial)",
  "workClass":"Accessory",
  "status":"Complete",
  "address":"12302 W Bell RD",
  "parcelNumber":"232-08-682A",
  "description":"ELECTRICAL ENCLOSURE // GOTO B202303207",
  "applicationDate":"2023-06-27T00:00:00.000Z",
  "issuedDate":"2023-10-02T00:00:00.000Z",
  "workflowStep":"Issue Permit",
  "workflowStepDate":null
}
```

The default dataset has a permit overview table. The `SUMMARY` key-value record reports saved count, `budgetReached`, requested filters and coverage. Export formats are provided by Apify rather than separate generated files.

### How much does it cost to export Maricopa building permits?

A one-time **$0.005 start event** applies to each valid run, including a valid no-result query. Each unique permit saved has an `item` event. Rejected or duplicate records have no item event. There are no separate charges for workflow fields or the summary.

| Apify Store spend tier | Per permit |
|---|---:|
| FREE | $0.0184 |
| BRONZE | $0.016 |
| SILVER | $0.01248 |
| GOLD | $0.0096 |
| PLATINUM | $0.0096 |
| DIAMOND | $0.0096 |

At BRONZE, estimated Actor charges are $0.021 for one permit, $0.325 for 20, and $1.605 for 100. Spend tiers follow qualifying aggregate monthly Apify Store spending, not this Actor's private volume ladder. The Actor checks charging capacity before querying pages and saving permits. If the budget cannot cover the start fee, it returns no records without querying the source; if it cannot cover another permit, it stops with partial results and `SUMMARY.budgetReached=true`. The saved count reflects delivered records, and undelivered records are not charged. Billing limits can therefore stop an export before the requested result limit. Consult the current pricing panel for applicable charges and platform terms.

### Pagination and coverage

Results use ascending GIS object-ID keyset pages, up to 500 records per request, stopping at the global unique-permit limit or source exhaustion. Deduplication uses permit-center identity when available, otherwise permit number. A local 501-record check exercised more than one page.

The source can update during a run, so this is not a transactionally frozen snapshot. There is no guarantee of complete county history, municipal completeness or real-time updates. Record volume reflects the current public view and your filters.

### Integrations

- Schedule an Apify Task with an overlapping application-date window and compare records by `permitCenterId` or `permitNumber` in your database.
- Export CSV to a spreadsheet for permit inventory review.
- Send dataset rows through an Apify integration or webhook into a warehouse.
- Join parcel identifiers to assessor research; permits and ownership records are different evidence types.

The Actor does not keep a cross-run change ledger, send alerts or infer new permits automatically. Recurring comparison is performed in your downstream workflow.

### API usage

Replace `APIFY_TOKEN` with an environment variable containing your own token; never share it in an input or dataset.

```bash
curl -X POST "https://api.apify.com/v2/acts/automation-lab~maricopa-county-building-permit-records/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"maxItems":20}'
```

JavaScript:

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/maricopa-county-building-permit-records').call({ maxItems: 20 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

Python:

```python
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/maricopa-county-building-permit-records').call(run_input={'maxItems': 20})
items = client.dataset(run['defaultDatasetId']).list_items().items
print(items)
```

### MCP usage

For Claude Code:

```bash
claude mcp add --transport http apify "https://mcp.apify.com?tools=automation-lab/maricopa-county-building-permit-records"
```

Claude Desktop, Cursor, and VS Code clients supporting HTTP MCP can use this actor-scoped configuration and their authenticated Apify connection:

```json
{
  "mcpServers": {
    "apify": {
      "url":"https://mcp.apify.com?tools=automation-lab/maricopa-county-building-permit-records"
    }
  }
}
```

Example prompts: “Get 20 county building permits with status Complete.” “Find county permit B202306811 and summarize its published workflow snapshot.” Actor availability depends on publication and your access permissions; do not paste credentials into prompts.

### Legality and data handling

This independent Actor is not affiliated with or endorsed by Maricopa County or Apify. It uses public county GIS records and Apify's standard terms. No AI model is used during extraction and no scraped data is sent to a model provider. The icon is an original generated illustration, not a county seal.

Only public permit/project fields are collected; no owner-contact scraping, private contacts, sessions or residential proxies are used. Project addresses and descriptions may still contain personal information. Use data appropriately and avoid unlawful targeting, harassment or unsolicited spam.

Results and the summary remain in your Apify storage under your account's retention settings until deleted. This Actor does not implement a separate retention timer, external cache or private database. Delete runs/datasets/key-value records through Apify when no longer needed. Logs contain counts and error metadata, not project rows or credentials. The county GIS receives filter requests; Apify processes input, output, logs and billing. No other runtime paid service is required.

### Troubleshooting and FAQ

**Why did a valid query return no records?**
Every filter combines with AND. Check a known permit with only `permitNumber`, then add filters individually. Source classifications are exact whole-field values.

**Why is a date null?**
The public GIS omitted it. Null issuance date is not proof that a project never received a permit elsewhere.

**Can I get every city's permits?**
No. County GIS is not a substitute for Phoenix, Mesa, Tempe or other municipal portals.

**Can I search issuance dates or descriptions?**
Not in this version. Application-date and address filters are the supported date/text controls.

**What happens when the source fails?**
Network timeouts, HTTP 429 and temporary server errors have up to three attempts per page with bounded backoff. Permanent GIS error envelopes fail immediately. A failed run may retain already-delivered partial rows and their charges; inspect its status before treating an export as complete.

**Does it need login or a proxy?**
No user login or proxy is required for the public GIS route. There is no automatic paid fallback.

### Related Actors and support

For complementary parcel ownership and valuation research, see [Maricopa County Property Records Scraper](https://apify.com/automation-lab/maricopa-county-property-records-scraper). For a different county corpus, see [Hillsborough County Building Permits Scraper](https://apify.com/automation-lab/hillsborough-county-building-permits-scraper).

Report Actor problems through the Apify Store Issues tab with the run link and reproduction input, excluding secrets. County record accuracy questions require checking the official source, not inferring missing fields from this export.

# Changelog

This Actor's version history is a separate document: https://apify.com/automation-lab/maricopa-county-building-permit-records/changelog.md

# Actor input Schema

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

Global limit on unique permits saved across all pages. Default 100; range 1–100000. Zero and unlimited are not supported. Upstream coverage and billing limits may reduce results.

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

Exact whole-field permit-number match, case-insensitive with outer whitespace trimmed. Combines with every other supplied filter.

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

Exact whole-field status match, case-insensitive and outer whitespace trimmed. Examples: Complete, Denied. No substring matching.

## `permitType` (type: `string`):

Exact whole-field county classification, case-insensitive with outer whitespace trimmed. Example: Building (Commercial).

## `addressContains` (type: `string`):

Case-insensitive literal substring within FullStreetAddress only. Outer whitespace trimmed; internal whitespace preserved. SQL wildcards % and \_ rejected. Not project-description matching.

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

Inclusive lower bound on application date in YYYY-MM-DD UTC. Missing application dates do not match. Must not follow appliedTo.

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

Inclusive upper bound on application date in YYYY-MM-DD UTC (implemented as before next midnight). Not an issuance-date filter.

## Actor input object example

```json
{
  "maxItems": 20
}
```

# Actor output Schema

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

Normalized public county GIS permit records.

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

Saved count and filters.

# 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 = {
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/maricopa-county-building-permit-records").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 = { "maxItems": 20 }

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/maricopa-county-building-permit-records").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 '{
  "maxItems": 20
}' |
apify call automation-lab/maricopa-county-building-permit-records --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/maricopa-county-building-permit-records"
        }
    }
}
```

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/PkiKnIcWAcKk99xnB/builds/4j4pfRuTfMnmSadN2/openapi.json
