# Contractor Permit Signals: New & Busy Contractors (`sullivan-ventures/contractor-permit-signals`) Actor

Building permits from city open data (Mesa, Austin, Chicago, Cincinnati, New Orleans): new and busy contractors, expired and expiring home permits, and an optional free property file.

- **URL**: https://apify.com/sullivan-ventures/contractor-permit-signals.md
- **Developed by:** [Michael Sullivan](https://apify.com/sullivan-ventures) (community)
- **Categories:** Lead generation, Business, Real estate
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$4.00 / 1,000 permits

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?

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

## Building Permit Data: New & Busy Contractors

Contractor Permit Signals is an Apify Actor by Sullivan Ventures that pulls building permits from official city open-data portals (Austin, Chicago, Cincinnati, New Orleans and Mesa, AZ) and flags contractors that are new to the city in the last 12 months or busy this week. It's built for suppliers, insurers, lenders and staffing firms that sell to contractors, and it returns business contractors only.

Find contractors the week they show up. This Actor reads official city building-permit open data and tells you, for every permit:

- **New contractor**: the company pulled no permit in that city in the past 12 months (you pick the lookback).
- **Busy contractor**: the company pulled 3 or more permits in your window (you pick the threshold).
- **Lien match (Phoenix metro)**: Mesa, AZ contractors that also appear in your [Construction Distress Feed](https://apify.com/sullivan-ventures/construction-distress-feed) history of Maricopa County mechanics liens and lis pendens.

The "new" flag works on your **first run**. It is computed from the city's own permit history, not from your past runs.

### Who uses it

- Building-material suppliers, lumber yards and equipment rental: reach contractors new to your city before your competitors do.
- Subcontractors: find general contractors that just got busy.
- Sureties, insurers, factoring and contractor software: watch for fast growth, or for growth plus liens.

### Cities (official sources, no login, no captcha)

| City | Dataset | License |
|---|---|---|
| Mesa, AZ (Phoenix metro) | citydata.mesaaz.gov `dzpk-hxfb` | City of Mesa Open Data Terms of Use (open license, commercial use allowed; source: City of Mesa) |
| Austin, TX | data.austintexas.gov `3syk-w9eu` | Public Domain |
| Chicago, IL | data.cityofchicago.org `ydr8-5enu` | City of Chicago Data Terms of Use (disclaimer below) |
| Cincinnati, OH | data.cincinnati-oh.gov `uhjb-xac9` | Public Domain |
| New Orleans, LA | data.nola.gov `rcm3-fn58` | CC0 1.0 Public Domain Dedication |

In a test week (Oct 15-21, 2026) the five cities issued about 2,470 permits. About 1,900 had a business contractor, and the run found 147 new contractors and 162 busy ones.

### Privacy by design

- From the permit records, only contractor-type contacts are read; applicant names on permits are not stored.
- The optional property file adds the owner name as printed on the public assessor roll (only from sources whose terms allow reuse). No phones, emails or skip tracing for owners.
- Contractor names that look like a private person (no business words) are dropped and counted, not listed. Person names that cities add after a company name ("ACME LLC, John Smith") are removed.
- Job site address is given for commercial permits only. Residential addresses are withheld.
- Business phone and license number are included when the city publishes them.

### Optional: property file (checkbox `includePropertyFile`)

Adds free public property facts to each permit row, at no extra charge:

| Field | What it is |
|---|---|
| owner_name, owner_type | Owner as printed on the public assessor roll; type = individual, trust/estate, business/government |
| absentee_owner | yes = owner mailing address is elsewhere (house-number match) |
| market_value_usd, year_built, home_age_years | From the assessor roll (Chicago: assessed value x 10 for homes) |
| owned_since_year, owned_years_approx | From the last deed or sale date |
| code_cases_5yr, last_code_case_date | City code-enforcement cases at the address in the last 5 years |
| census_tract, tract\_\* | Tract median income, home value, year built and owner share (Census ACS 5-year) |
| flood_zone, in_100yr_floodplain | FEMA flood map |

Coverage (local test, Oct 24 2026, 150 rows each; parcel match): Austin 85.3%, Chicago 94.7%, Cincinnati 99.3% (no year built in the free Hamilton County layer). Any city under an 80% parcel match on a run is left blank (`city_dropped_low_match`).
Not covered: Mesa (Arizona A.R.S. 39-121.03 needs a commercial-purpose request to resell assessor data) and New Orleans (the free parcel layers have no owner, value or year data).
Limit with `propertyFileMaxRows` (default 500).

**Use rules:** marketing use only. Not for credit, insurance, employment or housing/tenant decisions (FCRA). No phones or emails for owners, no skip tracing. You are responsible for your own TCPA / Do Not Call compliance.

### Input

| Field | Default | What it does |
|---|---|---|
| `cities` | `["mesa_az"]` | Any of mesa_az, austin_tx, chicago_il, cincinnati_oh, new_orleans_la |
| `lastNDays` | 7 | Permits issued in the last N days, ending yesterday |
| `startDate` / `endDate` | | Optional fixed range (YYYY-MM-DD) |
| `lookbackDays` | 365 | History used for the "new contractor" flag |
| `busyThreshold` | 3 | Permits in the window that make a contractor "busy" |
| `onlySignals` | false | Output only permits with a new or busy contractor (or a lien match) |
| `permitClass` | all | all, commercial or residential |
| `minValuation` | 0 | Skip small jobs |
| `joinDistressFeed` | true | Join Mesa contractors with your Construction Distress Feed state |

Example: weekly Phoenix-metro and Austin signals only:

```json
{ "cities": ["mesa_az", "austin_tx"], "lastNDays": 7, "onlySignals": true }
```

Schedule it weekly. `is_new_permit` and `seen_in_prior_runs` use a named key-value store in your account (`contractor-permit-state`).

### Output

**Dataset**: one row per permit. Sample:

```json
{
  "permit_number": "PMT26-13069",
  "issue_date": "2026-10-15",
  "city": "Mesa, AZ",
  "state": "AZ",
  "permit_class": "commercial",
  "permit_type": "Tenant Improvement (Interior Only)",
  "description": "Tenant improvement in existing 4,557 SF, type VB construction, B occupancy suite for offices. Permit includes previous unpermitted construction of interior partitions and ceilings for (2) single user restroom on SE, (8) offices, (1) kitchenette area, and open office area and associated MEP to be permitted under this permit. New modifications include enlargement of (1) restroom for ADA compliance, new partitions to create additional office spaces and a foyer area. Deferred fire sprinklers. Approved with comments. Results in CoO.",
  "valuation_usd": 1156760.08,
  "job_address": "6913 E REMBRANDT AVE",
  "job_zip": null,
  "contractors": [
    "WAY CONSTRUCTION & DEVELOPMENT"
  ],
  "contractor_keys": [
    "WAY CONSTRUCTION AND DEVELOPMENT"
  ],
  "has_new_contractor": true,
  "has_busy_contractor": false,
  "new_contractors": [
    "WAY CONSTRUCTION & DEVELOPMENT"
  ],
  "busy_contractors": [],
  "contractor_permits_in_window": 1,
  "contractor_permits_prior_lookback": 0,
  "contractor_with_maricopa_liens": null,
  "contractor_license": "315255 KB-1",
  "contractor_phone": "480-529-1054",
  "contractor_city": "MESA",
  "contractor_state": "AZ",
  "is_new_permit": true,
  "source": "Mesa, AZ open data (citydata.mesaaz.gov, dataset dzpk-hxfb)",
  "source_url": "https://citydata.mesaaz.gov/d/dzpk-hxfb",
  "license": "City of Mesa Open Data Terms of Use (open license, commercial use allowed, credit the City of Mesa)",
  "fetched_at": "2026-10-23T16:58:00.000Z"
}
```

**Key-value store**:

- `CONTRACTORS` / `CONTRACTORS_CSV`: one row per contractor with signal (new_and_busy, new, busy), permits this window, prior permits, value, license, phone and permit numbers.
- `SUMMARY`: counts per city, top signals, rate-limit log.

`company_key` uses the same normalization as the Construction Distress Feed, so you can join the two datasets yourself in a spreadsheet.

### Pricing

Pay per event: **$4.00 per 1,000 permit rows** ($0.004 per permit). There is no start fee. You only pay for rows in the dataset. The contractor roll-up and summary are included free.

- One city, one week is usually 40 to 1,200 permits, so $0.16 to $4.80. With `onlySignals` it costs much less.
- Set **Max cost per run** in the run options. The Actor stops at your limit and keeps signal permits first.

### Limits

- Contractor names are as typed by city staff, so the same company can appear under two spellings. The roll-up merges common suffix and punctuation differences.
- "New contractor" means new to that city's permits in the lookback window. It does not mean a newly formed company.
- If a city renames a column, the run fails loudly with "SCHEMA CHANGE DETECTED" instead of returning bad data.
- Chicago has no residential/commercial field, so the permit class is null.

### FAQ

#### Where can I get a list of new contractors in my city?

If your city is Austin, Chicago, Cincinnati, New Orleans or Mesa, AZ, run this Actor. It reads the city's official permit data and marks any contractor company that pulled no permit there in the past 12 months. In the week of Oct 15-21, 2026 it found 147 new contractors across the five cities.

#### Is building permit data free and legal to use?

The permit data comes from official city open-data portals. Austin and Cincinnati publish it as public domain, New Orleans as CC0, and Mesa and Chicago under their open-data terms. The Actor keeps business contractors only and drops individuals. You're responsible for following marketing laws like CAN-SPAM when you contact anyone.

#### How do I find contractors who are busy right now?

Set the busy threshold (default 3 permits in your window). Every contractor at or above it is flagged busy. In the Oct 15-21, 2026 test week, 162 contractors were flagged busy across about 2,470 permits.

#### How much does contractor permit data cost?

$4.00 per 1,000 permit rows ($0.004 each) plus Apify platform usage. One city for one week is usually 40 to 1,200 permits, so $0.16 to $4.80. Turn on onlySignals to get only the new and busy contractors and pay less.

#### Does it cover my city?

Today it covers Austin, Chicago, Cincinnati, New Orleans and Mesa, AZ. Many other US cities and counties, and some cities abroad, publish permit data, and we add them on request when the data is public and allowed for reuse. Coverage: we confirm data availability for your area before you run it; ask in the Issues tab.

### Source disclaimers

Data comes from official open-data portals. The cities make no warranty about it.

Chicago: "This site provides applications using data that has been modified for use from its original source, www.cityofchicago.org, the official website of the City of Chicago. The City of Chicago makes no claims as to the content, accuracy, timeliness, or completeness of any of the data provided at this site. The data provided at this site is subject to change at any time. It is understood that the data provided at this site is being used at one's own risk."

Mesa: source City of Mesa, Arizona (Open Data Terms of Use License Agreement).

### Stalled and expired home permits mode

Set `mode` to `stalled` to get home permits that expired, expire within the next N days, or were issued long ago and still have no final in the city data. Built for licensed contractors who want to find jobs that another contractor left unfinished.

- Cities: Austin TX and Cincinnati OH (expired and expiring soon), New Orleans LA (expired and open with no final), Mesa AZ (open with no final). Chicago is skipped in this mode.
- Output: job address and zip, permit number, type, description, valuation, issue date, status, status date, expiration date, the business name of the contractor of record, and the official source link.
- Never output: owner or applicant names, and no phone numbers of any kind.
- Limits: status comes from each city system and can lag. "Open with no final" means just that, not a confirmed abandoned job. These datasets don't publish failed-inspection results.
- Price: the same per-permit row event as signals mode.

Example input: `{"mode":"stalled","cities":["austin_tx"],"expiringWithinDays":30}`

# Actor input Schema

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

Stalled mode lists permits that expired, expire soon, or sit open with no final inspection. Austin, Cincinnati, New Orleans and Mesa only (Chicago has no address field here). Addresses only: no owner names, no phone numbers.

## `stalledTypes` (type: `array`):

Which stalled signals to include. Default: all three.

## `stalledPermitClass` (type: `string`):

Stalled mode lists homes by default.

## `expiringWithinDays` (type: `integer`):

Active permits whose expiration date falls in the next N days (Austin, Cincinnati).

## `openNoFinalDays` (type: `integer`):

Issued at least this long ago and still not finaled (Mesa, New Orleans).

## `issuedWithinDays` (type: `integer`):

Only look at permits issued in the last N days.

## `includePropertyFile` (type: `boolean`):

Adds to each permit: owner name from the public assessor roll and owner type, absentee-owner flag (mailing address elsewhere), value, year built and home age, years owned, code cases in the last 5 years, census-tract income, home value and owner share, and FEMA flood zone. Austin, Chicago and Cincinnati. Mesa and New Orleans are not covered (see README). Marketing use only: not for credit, insurance, employment or housing decisions (FCRA). No phones or emails. You handle your own TCPA / Do Not Call compliance.

## `propertyFileMaxRows` (type: `integer`):

Enrichment makes 2 map lookups per row, so large runs take longer. Rows past the cap are kept but marked over_row_cap.

## `cities` (type: `array`):

Official city permit datasets to read. Mesa is in the Phoenix metro and can be joined with the Construction Distress Feed.

## `lastNDays` (type: `integer`):

Permits issued in the last N days, ending yesterday. Ignored if Start date is set.

## `startDate` (type: `string`):

Optional, YYYY-MM-DD (issue date).

## `endDate` (type: `string`):

Optional, YYYY-MM-DD. Defaults to yesterday.

## `lookbackDays` (type: `integer`):

A contractor is 'new' if it pulled no permit in that city during this many days before the window.

## `busyThreshold` (type: `integer`):

Permits in the window that mark a contractor as busy.

## `onlySignals` (type: `boolean`):

Output only permits with a new or busy contractor (or a Maricopa lien match). You pay only for rows output.

## `permitClass` (type: `string`):

Chicago has no class field; its permits are dropped when you pick commercial or residential.

## `minValuation` (type: `integer`):

Skip permits below this declared job value. 0 = keep all.

## `maxRecords` (type: `integer`):

Upper limit on permit rows output (each row is one charge).

## `joinDistressFeed` (type: `boolean`):

If you also run the Construction Distress Feed (Maricopa liens and lis pendens) in this account, flag Mesa contractors that appear in its company history.

## `distressStateStoreName` (type: `string`):

Named key-value store used by the Construction Distress Feed.

## `stateStoreName` (type: `string`):

Named key-value store that remembers permits and contractors from your earlier runs (for is_new_permit and seen_in_prior_runs).

## `updateState` (type: `boolean`):

Save this run's permits and contractors to the state store.

## `socrataAppToken` (type: `string`):

Optional free token from the city portal. Only needed if you run very large ranges often.

## `requestDelayMs` (type: `integer`):

Pause between API calls to the city portal. Keep the default to stay polite.

## Actor input object example

```json
{
  "mode": "signals",
  "stalledTypes": [
    "expired",
    "expiring_soon",
    "open_no_final"
  ],
  "stalledPermitClass": "residential",
  "expiringWithinDays": 30,
  "openNoFinalDays": 365,
  "issuedWithinDays": 730,
  "includePropertyFile": false,
  "propertyFileMaxRows": 500,
  "cities": [
    "mesa_az",
    "austin_tx"
  ],
  "lastNDays": 7,
  "lookbackDays": 365,
  "busyThreshold": 3,
  "onlySignals": false,
  "permitClass": "all",
  "minValuation": 0,
  "maxRecords": 5000,
  "joinDistressFeed": true,
  "distressStateStoreName": "construction-distress-state",
  "stateStoreName": "contractor-permit-state",
  "updateState": true,
  "requestDelayMs": 250
}
```

# Actor output Schema

## `permits` (type: `string`):

One row per permit with new/busy contractor flags.

## `contractors` (type: `string`):

One row per contractor: signal, permits this window, prior permits, value, license, phone.

## `contractorsCsv` (type: `string`):

Same contractor list as CSV.

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

Counts per city, top signals, rate-limit log, data limits.

# 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 = {
    "cities": [
        "mesa_az",
        "austin_tx"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("sullivan-ventures/contractor-permit-signals").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 = { "cities": [
        "mesa_az",
        "austin_tx",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("sullivan-ventures/contractor-permit-signals").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 '{
  "cities": [
    "mesa_az",
    "austin_tx"
  ]
}' |
apify call sullivan-ventures/contractor-permit-signals --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,sullivan-ventures/contractor-permit-signals"
        }
    }
}
```

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/aa5LeeZEmQwIcVGMC/builds/wx5oaijrEfnUj8g1m/openapi.json
