Research occupancy records and track approvals
Build a spreadsheet of occupancy records, check which ones have been issued, and follow status changes in the records you care about.
What you’ll make
A spreadsheet of occupancy-related records with addresses, record types, statuses, dates, and source links. You’ll need an Apify account and a spreadsheet app. The example uses Brea, California, where the Building search includes certificate-of-occupancy record types.
The output can include applications, temporary certificates, and issued records. An occupancy record alone does not establish a property sale, a new owner, or a business opening. Check its status and source details before interpreting it as a completed approval.
1. Choose an agency and date window
Open New Homeowner & Business Leads (Certificates of Occupancy) on Apify. Switch to JSON input and paste this small sample.
{
"agency": "BREA",
"module": "Building",
"lastDays": 30,
"permitTypeContains": [
"occupancy"
],
"includeDetails": false,
"maxResults": 20
}lastDays: 30 searches 30 calendar days ending today in UTC, including today. permitTypeContains keeps types containing “occupancy,” without regard to case, including temporary certificates. maxResults limits the output to 20 rows; it does not limit how many search results the actor must read to find them.
Leave includeDetails off for Brea: its certificate pages do not provide the parcel, valuation, or contractor fields that a detail fetch might add elsewhere. Review the actor’s current pricing and set a maximum charge before running.
2. Check how your portal labels occupancy
For another agency, check the coverage section on the actor’s Apify page first. Its documentation identifies Brea, Crystal Lake, Hillsborough County, Milwaukee, Monterey Park, and Tampa as portals with separate occupancy record types. The wording and available records vary by jurisdiction.
Some portals record occupancy as a step inside a building permit. The default filter can return nothing there even when building permits exist. Clearing permitTypeContains returns all matching permits, so it changes the meaning of the export. For broader permit research, use the permit spreadsheet guide.
To narrow residential or commercial records, inspect the portal’s exact type labels. Each filter phrase is an alternative substring match. For example, “residential” also occurs in “non-residential,” so review the resulting types before splitting your sheet into homes and businesses.
3. Separate applications from issued records
After a successful run, inspect the dataset and export CSV or Excel. In Google Sheets, use File → Import → Upload. Keep agencyKey, permitNumber, permitType, status, appliedDate, addressFull, and detailUrl visible.
| permitNumber | permitType | status |
|---|---|---|
| DEMO-CO-001 | Certificate of Occupancy | Plan Review |
| DEMO-CO-002 | Temporary Certificate of Occupancy | Issued |
| DEMO-CO-003 | Certificate of Occupancy | Final |
Filter by status, then open the source record to confirm what that agency means by “Issued” or “Final.” Keep temporary certificates distinguishable from final ones. appliedDate is the portal’s search-grid date, sometimes a last-update date; it is not necessarily the issuance date.
Where available, includeDetails: true can add issuedDate, finalDate, parcelId, valuation, and contractor fields. Unpublished values remain null. Owner names are off by default and are not needed for this workflow.
4. Follow a selected group through approval
Create a dedicated key-value store in Apify Console and replace the placeholder below with its ID. Change the fixed dates to cover the group of applications you want to follow. Save this as a separate task and run it once before adding a schedule.
{
"agency": "BREA",
"module": "Building",
"lastDays": 0,
"dateFrom": "2026-09-01",
"dateTo": "2026-09-30",
"permitTypeContains": [
"occupancy"
],
"includeDetails": false,
"maxResults": 200,
"statusStateStoreId": "REPLACE_WITH_YOUR_STORE_ID"
}Both dates are inclusive, and lastDays: 0 keeps this window fixed. The first complete run delivers matching records and stores their statuses. Later runs deliver status changes to known records, including previousStatus and statusChanged. New records encountered after initialization are saved as baselines, not reported as status transitions.
Keep a separate recent-records task to discover new applications. A fixed window helps revisit older applications; a short rolling window may stop checking them before approval. If a portal changes the displayed date when a record is updated, it can move outside even a fixed window. Choose a window that continues to include your records and periodically reconcile it against a full export.
Use one state store per monitor and prevent overlapping runs. Do not combine statusStateStoreId with sinceLastRun, statusBaseline, or statusChangesOnly. Check the log for result, spending, or scan limits: an incomplete monitor does not advance the baseline, and an empty output alone does not prove that nothing changed.
5. Keep the spreadsheet current
For a recurring recent-records export, save the sample configuration with a rolling lastDays window. Match agencyKey and permitNumber when merging exports. Overlapping windows are useful for reconciliation, but simply appending them creates duplicates.
Keep scrapedAt so you know when the source was checked. If you also import monitor results, use them to update existing rows and preserve the earlier status in a history tab. Check the public record before treating a transition as evidence that a space is ready for occupancy.
If the output is empty
Check the agency’s occupancy coverage and type labels, then try a wider date window. Busy portals may require a narrower window to finish scanning. Review the log for incomplete runs or blocks. If you need help, send the agency and date range through an issue on the actor’s Apify page.
Start with a few occupancy records.
Check their types and statuses before choosing a date window for ongoing monitoring.
Open the actor on Apify