# New Homeowner & Business Leads (Certificates of Occupancy) (`finaldynamics/new-homeowner-leads`) Actor

New homeowner and new business leads: certificates of occupancy from Accela Citizen Access cities, one row per newly finished home or business premises.

- **URL**: https://apify.com/finaldynamics/new-homeowner-leads.md
- **Developed by:** [Final Dynamics](https://apify.com/finaldynamics) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $6.00 / 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.

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

## New Homeowner & Business Leads (Certificates of Occupancy)

A certificate of occupancy is the record a building department issues when a new or renovated
building is cleared to be lived in or opened for business. This actor turns those records into new
homeowner and new mover leads from any city or county that runs Accela Citizen Access, the
permitting portal used by hundreds of US jurisdictions. Give it an agency and a date range; get one
row per certificate with the address, record type, status, date and a link to the public record.

Built for people who sell to households and businesses in their first weeks at a new address:
movers, furniture and appliance retailers, security and alarm companies, insurance agents,
landscapers, signage and point-of-sale vendors, commercial cleaners, and the lead sellers and
brokers who supply them. A certificate of occupancy usually lands days before the move-in or the
grand opening, which is earlier than any change-of-address or business directory picks it up.

### What you get

One row per record. Every field is flat, so the dataset drops straight into Google Sheets, Airtable
or a CRM.

| Field                                                         | Example                                                                              |
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| `permitNumber`                                                | `COO-2026-00152`                                                                     |
| `permitType`                                                  | `Certificate of Occupancy`, `Temporary Certificate of Occupancy`, `Occupancy Permit` |
| `status`                                                      | `Issued`, `Plan Review`, `In Progress`                                               |
| `appliedDate`                                                 | `2026-09-14`                                                                         |
| `addressFull`, `street`, `addressCity`, `addressState`, `zip` | `975 E BIRCH St, B, BREA CA 92821`                                                   |
| `description`                                                 | Project description as published                                                     |
| `detailUrl`                                                   | Direct link to the public record, when one exists                                    |
| `declaredValuationUsd`, `parcelId`, `issuedDate`, `finalDate` | From the record page, with `includeDetails`                                          |
| `contractorName`, `contractorLicense`, `contractorPhone`      | From the record page, with `includeDetails`                                          |
| `agencyKey`, `agencyName`, `city`, `state`, `module`          | Which portal the row came from                                                       |
| `isNewSinceLastRun`, `previousStatus`, `statusChanged`        | Set by the incremental and monitoring modes                                          |
| `scrapedAt`                                                   | UTC timestamp                                                                        |

Anything the portal does not publish is `null`. Owner names are off by default because owners are
usually private individuals; turn on `includeOwnerName` together with `includeDetails` if you need
them and your use allows it.

### How to use it

Pick an agency, set a date range, run. The default filter keeps every record whose type contains the
word "occupancy", which covers certificates of occupancy, temporary certificates of occupancy and
occupancy permits on every portal checked so far.

```json
{
  "agency": "BREA",
  "dateFrom": "2026-09-02",
  "dateTo": "2026-09-16",
  "permitTypeContains": ["occupancy"],
  "maxResults": 200
}
```

Both dates are inclusive. `maxResults` caps the rows you pay for.

Most Accela portals let you pick a record type on their own search form, and the actor uses that: it
reads the agency's record-type list, finds the types whose names match `permitTypeContains`, and
asks the portal for those types only. On Hillsborough County a 90-day occupancy search reads about a
dozen result pages that way instead of several hundred. When the agency publishes no list, or
nothing on it matches your filter, the actor reads the whole date range and filters the rows itself,
and the run log says which of the two it did.

To choose the types yourself, set `recordTypes` to names copied from the portal's **Record Type**
menu, such as "Commercial Temp Certificate of Occupancy" and "Residential Temp Certificate of
Occupancy" on Hillsborough County. A name the agency does not offer stops the run and lists what it
does offer.

`maxPages` (60 by default) is the ceiling on result pages one run reads, across all record types. A
run that hits it stops, delivers everything it found, and says so; raise `maxPages`, narrow the
dates, or set `recordTypes`. Rows are written to the dataset page by page as they are found, so a
run that is aborted or times out keeps everything except the page it was reading.

**New certificates every week:** set `lastDays: 7` and schedule the task weekly. The window moves
automatically on every run, overriding `dateFrom` and `dateTo`. Use a window no wider than the
schedule so consecutive runs do not overlap.

**Know when a certificate is actually issued:** many portals open the record at application and
change its status to `Issued` or `Final` when the building is cleared. Create a key-value store in
Apify Console, select it as `statusStateStoreId`, and schedule the task. The first complete run
delivers every matching record and stores its status (you pay for those rows once); later runs
deliver only records whose status changed, with `previousStatus` filled in. That status change is
the move-in signal.

**Homes only, or businesses only:** narrow the filter to the portal's wording, for example
`["residential certificate of occupancy"]` or `["non-residential"]` on Monterey Park. A record is
kept when its type contains any one of the listed phrases as a whole, case-insensitive.

**Contractor and valuation:** set `includeDetails: true` to open each record page and add the
contractor's name, licence and phone, the parcel number and the declared valuation where the portal
publishes them.

**Any other Accela portal on `aca-prod.accela.com`:** set `agency` to the portal's agency code and
`portalUrl` to its base address, for example `https://aca-prod.accela.com/CITYNAME/`. Portals a city
hosts on its own domain are not supported yet.

### Quick start

No code needed; about two minutes from input to spreadsheet. For a longer walkthrough with example
output, read the guide
[Research occupancy records and track approvals](https://finaldynamics.com/occupancy-guide.html).

1. Open the example "Certificates of occupancy, Brea CA, last 30 days" on this actor's page, or
   paste one of the inputs above into the input form.
2. Click **Start** and wait until the run shows **Succeeded**.
3. Open the **Output** tab to see the certificates as a table.
4. Click **Export**, choose CSV or Excel, and download the file.
5. In Google Sheets, choose **File > Import > Upload** and select the file.

### Automate it

**A weekly lead pull:** keep `lastDays` in the task (for example 7 or 30) so the date window moves
with every run. Click **Save as a new task**, then add a schedule for that task under **Schedules**
in Apify Console (for example every Monday at 07:00). To get only certificates you have not received
before, turn on `sinceLastRun` and select a key-value store as `stateStoreId`.

**Into Google Sheets with n8n:** add the **Apify** node with the **On new Apify Event** trigger,
choose your task and the succeeded event, then add an **Apify** node with **Get Dataset Items**
using the run's `defaultDatasetId`, and finish with a **Google Sheets** node that appends the rows.
Every scheduled run then adds its certificates to the same sheet. Make and Zapier have Apify
integrations that work the same way.

### Pricing

Pay per event. There are no platform usage charges on top.

| Event                    |  Price | When                                                      |
| ------------------------ | -----: | --------------------------------------------------------- |
| `actor-start`            |  $0.01 | Once per run                                              |
| `permit-record`          | $0.006 | Per row delivered                                         |
| `contractor-detail-page` | $0.004 | Per record page fetched, only when `includeDetails` is on |

Examples: 50 certificates without details cost $0.31. Fifty with contractor details cost $0.51. A
run that finds nothing costs $0.01. You pay for the rows that match the filter, not for the permits
the actor reads and discards on the way.

Set a maximum charge on the run if you want a hard ceiling; the actor stops cleanly at the cap.

### Coverage

These bundled portals publish certificates of occupancy as their own record type in the Building
search, so the default filter returns rows. Checked live on 2026-09-16.

| Agency key     | Jurisdiction        | State | What the portal calls them                                                    |
| -------------- | ------------------- | ----- | ----------------------------------------------------------------------------- |
| `BREA`         | Brea                | CA    | Certificate of Occupancy, Temporary Certificate of Occupancy                  |
| `CRYSTALLAKE`  | Crystal Lake        | IL    | Occupancy Permit (rare: about one a week)                                     |
| `HCFL`         | Hillsborough County | FL    | Commercial and Residential Temp Certificate of Occupancy                      |
| `MILWAUKEE`    | Milwaukee           | WI    | Occupancy, Conditional Occupancy, Temporary Occupancy                         |
| `MONTEREYPARK` | Monterey Park       | CA    | Residential and Non-Residential Certificate of Occupancy, plus temporary      |
| `TAMPA`        | Tampa               | FL    | Temporary Certificate of Occupancy, Residential Temp Certificate of Occupancy |

The other bundled portals work with this actor but do not label certificates of occupancy as a
separate record type in their Building search, so the default filter finds nothing there: Anaheim
(`ANAHEIM`), Culver City (`CULVERCITY`), DeLand (`DELAND`), Downey (`DOWNEY`), Fontana (`FONTANA`),
Goodyear (`GOODYEAR`), Hartford (`HARTFORD`), Hermosa Beach (`HERMOSABEACH`), Loveland (`LOVELAND`),
Manteca (`MANTECA`), Missoula (`MISSOULA`), Palo Alto (`PALOALTO`), Reno (`RENO`), Sacramento
(`SACRAMENTO`), Stockton (`STOCKTON`), Thurston County (`THURSTONCO`) and Visalia (`VISALIA`). On
those portals the certificate is a step inside the building permit; clear `permitTypeContains` to
get every permit, or use the Accela Building Permit Scraper by the same publisher with the status
monitor to catch permits reaching `Final`. Atlanta (`ATLANTA_GA`) has only a "Historical Certificate
of Occupancy" type, an archive record rather than a new certificate, so the default filter returns
old records there.

Any other portal on `aca-prod.accela.com` works with `portalUrl`. Run it once with a short window to
see what that agency calls its occupancy records.

### Good to know

- The actor reads every permit in the date window and keeps the ones that match, so busy portals
  take longer: a two-week window on Tampa or Hillsborough County reads about a hundred result pages
  and takes a few minutes. Narrow the window or schedule smaller runs if that matters.
- Record pages differ by portal. Brea's certificate pages carry no parcel, valuation or contractor,
  so leave `includeDetails` off there; Milwaukee's records that still have a temporary number have
  no record page yet.
- Temporary certificates count. A temporary certificate of occupancy means the building is in use
  while final items are finished, which is still a move-in.
- Public records only. The actor reads the same pages a visitor sees, honours the portal's robots
  rules, and never logs in, solves CAPTCHAs or uses proxies. If a portal blocks access, the run
  fails with a clear message rather than returning an empty result.
- Requests are polite: at most two at a time per portal, with retries and backoff on errors.
- Dates are the portal's local calendar dates. A record's applied date is what the agency shows in
  the results grid; on some portals that is the last update date.
- This is the same code as the Accela Building Permit Scraper by the same publisher, listed
  separately so certificate-of-occupancy searches are easy to find. The actor is not affiliated with
  Accela or with any listed agency. Public records can be incomplete or corrected later.

### FAQ

**Where do new homeowner leads come from here?**

From certificates of occupancy: a city issues one when a new or renovated home or commercial space
is cleared for people to move in or a business to open.

**Can I search certificates of occupancy by city?**

Yes, for cities and counties that run Accela Citizen Access and publish occupancy as its own record
type. The Coverage table lists the ones checked live.

**Does it include the new owner's name?**

Only when the portal shows it and you turn on `includeOwnerName`. Many portals show the address and
contractor but not the buyer.

### Related actors

- [Florida Property Sales & New Owner Leads](https://apify.com/finaldynamics/florida-property-sales-leads):
  recorded sales with the new owner's mailing address.
- [Accela Building Permit Scraper](https://apify.com/finaldynamics/accela-permit-scraper): building
  permits with contractor details from Accela cities and counties.
- [Florida Business Leads by Industry](https://apify.com/finaldynamics/florida-business-leads):
  Florida businesses by NAICS code and county.

Questions or a portal that misbehaves? Open an issue on the actor page and include the agency and
date range.

# Actor input Schema

## `agency` (type: `string`):

Accela agency key. See agencies.json for 24 verified portal URLs.

## `portalUrl` (type: `string`):

HTTPS base URL ending with the agency key and a slash. Bundled deployment permits aca-prod.accela.com.

## `module` (type: `string`):

Citizen Access module using General Search: Building, Enforcement or Planning. Known names are trimmed and normalized; other module names retain their casing. Availability varies by agency; unsupported modules fail clearly.

## `dateFrom` (type: `string`):

Inclusive applied date, YYYY-MM-DD.

## `dateTo` (type: `string`):

Inclusive applied date, YYYY-MM-DD.

## `lastDays` (type: `integer`):

Search the last N calendar days ending today, instead of fixed dateFrom and dateTo. Leave at 0 to use the fixed dates.

## `recordTypes` (type: `array`):

Names from the portal's own "Record Type" list on its General Search page, for example "Commercial Temp Certificate of Occupancy". The portal then returns only those record types, which is far faster and cheaper than reading every record in the date range. Leave empty and the actor picks the matching record types itself from "Permit type contains"; it falls back to reading everything when the agency publishes no list. A name the agency does not offer stops the run and lists what it does offer.

## `permitTypeContains` (type: `array`):

Case-insensitive substring matching; any listed string may match. The default matches every certificate of occupancy, temporary certificate of occupancy and occupancy permit type. Emptying the list returns every permit.

## `minValuation` (type: `number`):

Zero disables filtering. A positive value excludes unknown valuations; enable includeDetails to read valuation when published.

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

The most rows one run delivers. When a run stops at this limit, the "only new permits" memory is not updated, so nothing is skipped next time.

## `maxPages` (type: `integer`):

Ceiling on search result pages one run reads, across all record types. On reaching it the run stops tidily, delivers everything found so far and says so. Raise it only when a run reports that it stopped here; a wide date range on a busy agency is usually better narrowed instead.

## `includeDetails` (type: `boolean`):

Fetch public detail links for contractor, parcel, dates and valuation. Owner names additionally require includeOwnerName. Each fetched detail page costs $0.004, even if a valuation filter later excludes the record.

## `includeOwnerName` (type: `boolean`):

Include the property owner's name from the permit detail page. Off by default because owners are usually private individuals.

## `sinceLastRun` (type: `boolean`):

Deliver only permits applied for after the newest one a previous run delivered. Needs a key-value store selected below as stateStoreId; give each schedule its own. A run that stops early does not move the marker forward.

## `stateStoreId` (type: `string`):

Key-value store that remembers the newest applied date delivered by sinceLastRun runs. Create one in Apify Console and select it here; each schedule should have its own.

## `statusBaseline` (type: `array`):

Optional list of permit numbers and statuses from an earlier run, to compare against. Permit numbers match without regard to case.

## `statusChangesOnly` (type: `boolean`):

Deliver only permits from the list above whose status is different now. Needs a non-empty list.

## `statusStateStoreId` (type: `string`):

A key-value store the actor uses to remember every permit's status between scheduled runs. The first complete run delivers everything and saves the statuses; later runs deliver only permits whose status changed. Use one store per schedule and let runs finish one at a time.

## `statusMonitorMaxPages` (type: `integer`):

Safety limit on search result pages when watching status changes. If the limit is reached, the run stops and the saved statuses stay as they were.

## `statusMonitorMaxRecords` (type: `integer`):

Safety limit on permits read when watching status changes. If the limit is reached, the run stops and the saved statuses stay as they were.

## Actor input object example

```json
{
  "agency": "BREA",
  "portalUrl": "https://aca-prod.accela.com/BREA/",
  "module": "Building",
  "dateFrom": "2026-09-02",
  "dateTo": "2026-09-16",
  "lastDays": 0,
  "recordTypes": [],
  "permitTypeContains": [
    "occupancy"
  ],
  "minValuation": 0,
  "maxResults": 200,
  "maxPages": 60,
  "includeDetails": false,
  "includeOwnerName": false,
  "sinceLastRun": false,
  "statusBaseline": [],
  "statusChangesOnly": false,
  "statusMonitorMaxPages": 100,
  "statusMonitorMaxRecords": 5000
}
```

# Actor output Schema

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

Crawled data stored in default dataset

## `files` (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 = {
    "agency": "BREA",
    "portalUrl": "https://aca-prod.accela.com/BREA/",
    "module": "Building",
    "dateFrom": "2026-09-02",
    "dateTo": "2026-09-16",
    "lastDays": 0,
    "recordTypes": [],
    "permitTypeContains": [
        "occupancy"
    ],
    "minValuation": 0,
    "maxResults": 200,
    "maxPages": 60,
    "includeDetails": false,
    "includeOwnerName": false,
    "sinceLastRun": false,
    "statusBaseline": [],
    "statusChangesOnly": false,
    "statusMonitorMaxPages": 100,
    "statusMonitorMaxRecords": 5000
};

// Run the Actor and wait for it to finish
const run = await client.actor("finaldynamics/new-homeowner-leads").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 = {
    "agency": "BREA",
    "portalUrl": "https://aca-prod.accela.com/BREA/",
    "module": "Building",
    "dateFrom": "2026-09-02",
    "dateTo": "2026-09-16",
    "lastDays": 0,
    "recordTypes": [],
    "permitTypeContains": ["occupancy"],
    "minValuation": 0,
    "maxResults": 200,
    "maxPages": 60,
    "includeDetails": False,
    "includeOwnerName": False,
    "sinceLastRun": False,
    "statusBaseline": [],
    "statusChangesOnly": False,
    "statusMonitorMaxPages": 100,
    "statusMonitorMaxRecords": 5000,
}

# Run the Actor and wait for it to finish
run = client.actor("finaldynamics/new-homeowner-leads").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 '{
  "agency": "BREA",
  "portalUrl": "https://aca-prod.accela.com/BREA/",
  "module": "Building",
  "dateFrom": "2026-09-02",
  "dateTo": "2026-09-16",
  "lastDays": 0,
  "recordTypes": [],
  "permitTypeContains": [
    "occupancy"
  ],
  "minValuation": 0,
  "maxResults": 200,
  "maxPages": 60,
  "includeDetails": false,
  "includeOwnerName": false,
  "sinceLastRun": false,
  "statusBaseline": [],
  "statusChangesOnly": false,
  "statusMonitorMaxPages": 100,
  "statusMonitorMaxRecords": 5000
}' |
apify call finaldynamics/new-homeowner-leads --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,finaldynamics/new-homeowner-leads"
        }
    }
}
```

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/bbwDzhUdhmQYoDQuS/builds/1h0j2Wz3efahxabcJ/openapi.json
