# NZ Building Consents Monitor - New Consent Lead Alerts (`datadeltas/nz-building-consents`) Actor

Monitor NEW New Zealand building consents from official council open-data services. Filter by keyword (new dwelling, re-roof, solar, pool) and minimum job value in NZD, with coordinates on every consent. Emits only consents it has never seen - schedule it and feed your construction lead pipeline.

- **URL**: https://apify.com/datadeltas/nz-building-consents.md
- **Developed by:** [Daniel Matthee](https://apify.com/datadeltas) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $12.00 / 1,000 new consent detecteds

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

## NZ Building Consents Monitor

**New Zealand building consents from official council open data — only the ones
you have not seen before.** Every run checks three district-council services,
emits consents that are new since your last run, and gives you the job
description, value, address and map coordinates. Built to be scheduled: run it
daily and it becomes a construction lead feed.

Roughly **35 new consents a day** across the three councils, straight from the
councils' own systems. No API key, no scraping of council web forms.

### Example output

One real record, trimmed:

```json
{
  "council": "queenstown_lakes",
  "councilName": "Queenstown Lakes District Council",
  "consentNumber": "BC240701",
  "description": "Construction of 3 x detached dwellings (House A, B and C)",
  "address": "53 ANGELO DRIVE FRANKTON QUEENSTOWN 9300",
  "valueNZD": 1800000.0,
  "floorAreaM2": 629.0,
  "dwellingUnits": 3,
  "consentType": "BCNwRelDw",
  "status": "Completed",
  "lodgedDate": null,
  "grantedDate": "2024-09-01",
  "sourceDate": "2026-08-04",
  "sourceDateField": "created_date",
  "latitude": -45.0163741,
  "longitude": 168.7107218,
  "legalDescription": null,
  "valuationNumber": null,
  "sourceUrl": "https://services1.arcgis.com/.../query?where=CONSENT_ID%3D%27BC240701%27",
  "detectedAt": "2026-09-01T04:22:10Z"
}
```

### Councils covered

| Council | New consents | Job value | Street address | Coordinates | Freshness |
|---|---|---|---|---|---|
| Queenstown Lakes District Council | ~31/day | ✅ 100% | ✅ 100% | ✅ 100% | same day |
| Far North District Council | ~3.5/day | — | — | ✅ 100% | same day |
| Timaru District Council | ~1.2/day | ✅ 77% | ✅ 100% | ✅ 100% | ~2–3 weeks in arrears |

Coverage percentages are measured, not estimated — they come from a live run
over a 30-day window.

**Two things worth knowing before you buy:**

1. **Far North publishes no job value and no street address.** It gives a legal
   description, a valuation number and exact coordinates instead. If you set
   `minValueNZD`, no Far North consent can ever match — the run log says so.
2. **Timaru publishes in arrears.** Its consents appear about two to three
   weeks after they are granted. The actor automatically widens Timaru's
   lookback to 45 days so it is never silently empty, no matter what you set
   `sinceDays` to.

No New Zealand council in this set publishes the applicant's or the builder's
name, so this feed does not contain one. Everything here is data the council
already publishes openly.

### Input options

| Field | Type | Default | What it does |
|---|---|---|---|
| `councils` | array | all three | Which councils to check. |
| `keywords` | array | *(empty)* | Only emit consents whose description, consent type or address contains one of these. Case-insensitive **substring** match, so `roof` also matches `re-roofing` and `pool` matches `swimming pool`. Empty means everything. |
| `minValueNZD` | integer | `0` | Only emit consents worth at least this much. Excludes Far North entirely (it publishes no value). |
| `sinceDays` | integer | `30` | How far back to scan. On the first run this is your backfill; after that only never-seen consents are emitted. |
| `maxItems` | integer | `200` | Cap on consents emitted per run. The cap is split fairly across councils, so one busy council cannot eat it; anything over the cap arrives next run. |
| `stateStoreName` | string | `nz-building-consents-state` | Named key-value store holding the consent numbers already delivered. Change it to reset the monitor and re-emit the backfill. |

### Output fields

| Field | Type | Notes |
|---|---|---|
| `council` / `councilName` | string | Which council, as a key and as a full name. |
| `consentNumber` | string | The council's own consent number. Unique within a council. |
| `description` | string | The council's own free-text description of the work. |
| `address` | string | null | Site address. Null for Far North. |
| `valueNZD` | number | null | Stated job value in NZD. Null for Far North and for Timaru consents with no published cost. |
| `floorAreaM2` / `dwellingUnits` | number | null | Floor area and number of dwelling units where published. |
| `consentType` | string | null | The council's own consent category. |
| `status` | string | null | The council's own status wording. |
| `lodgedDate` / `grantedDate` | date | null | Lifecycle dates, where that council publishes them. |
| `sourceDate` + `sourceDateField` | date + string | The date this council's window filters on, **and which lifecycle moment it is** — Queenstown Lakes stamps first-published, Far North stamps lodged, Timaru stamps granted. They are not the same event, so the field is labelled rather than flattened. |
| `latitude` / `longitude` | number | null | WGS84 site coordinates. Present on effectively every consent, including the ones with no address. |
| `legalDescription` / `valuationNumber` | string | null | Far North and Timaru; the property identifiers those councils publish. |
| `sourceUrl` | string | Link straight back to this consent on the council's own service. |
| `detectedAt` | date-time | When this feed first saw the consent. |

### Use cases

**"Find me every new house going up in Queenstown before my competitors do."**
Schedule it daily with `keywords: ["dwelling"]` and `minValueNZD: 400000`. In a
30-day window that is ~390 consents — new builds and multi-unit developments,
each with an address, a value and a map pin, the day the council publishes it.

**"I install heat pumps / solar / pools and I want the jobs, not the noise."**
Set `keywords` to your trade (`["solar"]`, `["pool"]`, `["heat"]`,
`["re-roof","roof"]`). The substring match catches the way council staff
actually type, and consents you have already been sent never come back.

**"I need consent volumes for a market report."** Run it with no filters and a
long `sinceDays` for a clean backfill, then daily for the running feed. Every
row carries the council, the value, the floor area and the dwelling count, so
it aggregates directly into pipeline and market-share analysis.

### How the monitor works

- **First run** scans your `sinceDays` window and emits everything matching
  your filters — that is your backfill.
- **Every run after that** emits only consents it has never seen. A rerun five
  minutes later emits nothing and costs you nothing beyond the start fee.
- **State** lives in the named key-value store `stateStoreName`. Change the
  name to start over.
- **A council being down degrades the run, it does not fail it** — you get the
  other councils plus a warning, and nothing from the failed council is marked
  as delivered, so it arrives on the next successful run.
- **Consents are pushed to the dataset before anything is recorded as
  delivered**, so a failed push means you get the consent next run rather than
  never.

Recommended schedule: **daily**. There is no benefit to running more often than
the councils publish.

### Pricing (pay per event)

| Event | Price (USD) |
|---|---|
| Actor start | $0.005 |
| New consent detected | $0.012 |
| Council checked | $0.001 |

A quiet day costs under a cent. A typical unfiltered day (~35 consents) is
about $0.43; a trade-filtered feed is a fraction of that because you only pay
for consents that match.

### Maintenance promise

The council field mappings are checked against the live services and the actor
warns in its own run log the moment a council renames or drops a field, rather
than silently emitting blanks. Schema checked on each release.

### Changelog

#### 0.1 — first release

- Queenstown Lakes, Far North and Timaru district councils.
- Keyword, minimum-value and per-council filtering; fair `maxItems` split
  across councils.
- WGS84 coordinates on every consent, including councils that publish no
  street address.
- Marlborough District Council was evaluated and left out: it publishes no
  usable date or sequence field, so a correct feed would have to re-read all
  55,000 of its rows every run and would bill a 55,000-consent backlog on the
  first one. It is a candidate for a future release under a silent-seed design.

# Actor input Schema

## `councils` (type: `array`):

Which council open-data services to check. Default: all of them.

## `keywords` (type: `array`):

Only emit consents whose description, consent type or address contains one of these (case-insensitive substring match, so 'roof' also matches 're-roofing'). Leave empty for all consents.

## `minValueNZD` (type: `integer`):

Only emit consents with a stated job value of at least this many NZD. IMPORTANT: only Queenstown Lakes and Timaru publish a job value - setting this excludes every Far North consent.

## `sinceDays` (type: `integer`):

How many days of consents to scan. On the first run this is your backfill; later runs emit only consents never seen before. Timaru publishes 2-3 weeks in arrears, so its own lookback is always widened to at least 45 days.

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

Cap on new consents emitted in a single run (cost control). The cap is split fairly across the councils that returned data, and anything over it is picked up on the next run.

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

Named key-value store holding the consent numbers already delivered. Change it to reset the monitor and re-emit the backfill.

## Actor input object example

```json
{
  "councils": [
    "queenstown_lakes",
    "far_north",
    "timaru"
  ],
  "keywords": [
    "dwelling",
    "solar"
  ],
  "minValueNZD": 0,
  "sinceDays": 30,
  "maxItems": 200,
  "stateStoreName": "nz-building-consents-state"
}
```

# Actor output Schema

## `records` (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 = {
    "keywords": [
        "dwelling",
        "solar"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("datadeltas/nz-building-consents").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 = { "keywords": [
        "dwelling",
        "solar",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("datadeltas/nz-building-consents").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 '{
  "keywords": [
    "dwelling",
    "solar"
  ]
}' |
apify call datadeltas/nz-building-consents --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,datadeltas/nz-building-consents"
        }
    }
}
```

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/GWMHaVNPK2QBseH0w/builds/G5fUwd2tTKy5ZqXcY/openapi.json
